Design Debt: Silent Killer in Systems
This article is based on my book — Designing Trading Systems: Trade Booking, Lifecycle, and Flow - a hands-on guide for folks in financial engineering industry.
TL;DR
Design debt is the structural weakness that accumulates when systems are built with unclear boundaries, tangled logic, and no room for future extension. Unlike technical debt, which is visible and easy to clean up, design debt hides deep in the architecture and resurfaces only when the system hits scale, stress, or new business demands. The cost is non-linear: everything works until it suddenly doesn’t. Minimizing design debt early—through clear domain boundaries, flow-first thinking, extension points, and built-in observability—preserves long-term velocity and prevents the system from collapsing under its own weight.
Context
Every engineering organization accumulates some amount of design debt. The danger is when you don’t notice it until the system starts to resist you.
In this post, I want to explore design debt through real-world analogies and practical engineering examples. The goal is simple: understand why design debt forms, how it sabotages systems over time, and how to minimize it early.
What Exactly Is Design Debt?
We often talk about technical debt — shortcuts in implementation:
- Lack of tests
- Hard-coded values
- No refactoring
- Duplicated logic
Design debt is deeper. It’s structural.
Technical debt is messy furniture. Design debt is load-bearing walls in the wrong place.
Technical debt is annoying. Design debt is painful.
Design debt is when:
- Boundaries are unclear
- Modules don’t have extension points
- Data models collapse multiple domains into one
- Logic becomes entangled
- Flows don’t map cleanly to the architecture
- Systems cannot evolve without major surgery
Real-World Analogies: Where Design Debt Lives
Great systems thinking begins with physical analogies. They make the hidden visible.
Below are a few you can use to frame the idea.
1. Houses and Foundations: When the Basics Are Wrong
If you build a house with:
- Poor foundation
- Cheap plumbing
- Unlabeled wiring
- No shut-off valves
The house may stand. It might even look good. But every repair becomes horrifyingly expensive.
A single leaking pipe often requires breaking an entire wall.
Design debt is like bad plumbing hidden behind drywall. Invisible… until it ruins everything around it.
Software equivalent:
- Core module with 10 different responsibilities
- No clear data boundaries
- Domain logic living in controllers
- No versioning strategy for schemas
Early on, everything “just works.” Later, every change feels like cutting open walls.
2. Cities Without Planning: Organic Today, Gridlock Tomorrow
Cities that grow without planning develop:
- Narrow streets
- Congested intersections
- No arterial roads
- No reserved spaces for future expansion
In year one, it feels fine. In year twenty, it collapses under its own weight.
Retrofitting a subway line into an old city is exponentially harder than planning it from day one.
Software equivalent:
- A system with no defined “core services”
- Everything calling everything
- No observability across services
- Data inconsistencies across bounded contexts
The system works at small scale. Then traffic spikes… and suddenly nothing works.
3. Factories With Rigid Assembly Lines
Imagine a factory where:
- Conveyors are bolted permanently
- Machines are packed tightly
- Workflow is rigid
- No bypass routes exist
At low volume: fine. At high volume: catastrophic.
One machine failing halts the entire line.
Software equivalent:
- Hard-coded workflows
- No plugin architecture
- No “workflow as data”
- One service acting as the orchestrator for everything
The cost of “just add this one more step” becomes massive.
4. Trading Systems: The God Object Trap
Let’s bring it closer to real engineering.
In many trading platforms:
A core
Tradeobject is passed everywhere.Every group adds fields:
- Risk adds Greeks
- Finance adds accounting metadata
- Reporting adds presentation fields
Over time, the
Tradebecomes a God object.
The side effects:
- Changes require cross-team coordination
- Schema evolution becomes dangerous
- Pipelines are forced to process more than they need
- Debugging becomes nearly impossible
This is classic design debt: collapsing multiple bounded contexts into one universal structure.
The Nature of Design Debt: Why It’s a Silent Killer
Design debt doesn’t hurt right away. In fact, early on it often feels like you’re “moving fast”:
- No need to over-engineer
- Everything is in one place
- No interfaces to maintain
- No abstractions to respect
But design debt has two sinister qualities:
1. Its cost grows non-linearly
- From 10 features to 50 features: manageable
- From 50 features to 200 features: impossible without redesign
Design debt doesn’t scale linearly. It compounds.
2. It surfaces under stress
Consider these:
Production outages Regulatory changes Performance spikes New asset classes New business flows
Suddenly:
- Bugs multiply
- Debugging becomes archaeology
- Delivery slows to a crawl
- Engineers fear touching certain modules
A line I personally like:
Design debt waits for the worst possible moment to send the bill.
Enjoying this post? Check out my book — Designing Trading Systems: Trade Booking, Lifecycle, and Flow for deeper insights into real-world system design.
Common Patterns That Create Design Debt
You will recognize these quickly.
1. God Modules
A module that eventually grows to 5,000 lines because:
- “Everything important happens here”
- “Just add one more condition”
These are the hardest to refactor.
2. No Clear Contracts
Functions that take:
{ "data": { "whatever": "we pass around" } }Or worse, generic untyped blobs.
The caller must remember what’s inside. Tribal knowledge becomes the interface.
3. Leaky Abstractions Everywhere
Upper layers know:
- DB column names
- Queue names
- Internal IDs
- Internal state transitions
This is system-level spaghetti.
4. No Extension Points
Systems built with no thought for change:
- Workflows cannot be altered
- No plug-in mechanism
- Adding a new type means forking the entire flow
Future changes become invasive surgery.
5. No Observability by Design
When production breaks:
- No correlation IDs
- No consistent logging format
- No system-wide context propagation
Design debt expresses itself as visibility debt.
Symptoms: How to Know Your System Has Design Debt
A few unmistakable signs:
- Adding a small feature requires touching 5–10 unrelated modules
- No one can accurately draw the system architecture
- Everything feels fragile
- Bugs reappear in different places
- Debugging takes longer than coding
- Engineers say: “I hate touching that part of the system”
- Onboarding new devs takes months, not weeks
If these feel familiar, you’re dealing with accumulated design debt.
How to Minimize Design Debt Early
No system avoids it entirely, but you can reduce it significantly if you design intentionally.
Here are the strategies I use in large systems — especially trading architectures.
1. Define Boundaries Explicitly
Ask:
- “What are the bounded contexts?”
- “Which domain owns which data?”
- “Which flows interact with which components?”
Even a simple diagram helps break monolith thinking.
2. Think in Flows, Not Functions
Flows define the behavior of connected systems.
- Trade booking
- Lifecycle transitions
- Pricing
- Risk calculations
- Reporting
Design flows first. Then place components along them.
3. Build Extension Joints
Examples:
- Plugin points
- Hook interfaces
- Event emission points
- Configuration-driven workflows
These don’t require over-engineering. You just need to leave space for the future.
4. Standardize IDs and Context Passing
This reduces debugging time dramatically.
- Shared correlation IDs
- Consistent domain IDs
- Trace propagation across service boundaries
You cannot add observability later without pain.
5. Thin Vertical Slices Early
Instead of delivering horizontal layers:
Deliver one end-to-end flow that works correctly.
Use it as the “exemplar pattern” for the rest of the system.
6. Review for Scalability—Not Just Correctness
During design reviews, ask:
- “What happens when we add 10 more workflows?”
- “What if we support 10 more asset types?”
- “What if traffic grows by 10x?”
- “Where will this design break first?”
This type of thinking catches design debt before it hardens.
Closing Thoughts
Design debt forms quietly. It forms when we’re moving fast, under pressure, or trying to hit deadlines.
But in systems thinking — whether in software, cities, factories, or trading platforms — the same truth holds:
Good structure compounds. Bad structure compounds faster.
Design debt isn’t about perfection. It’s about intentionally shaping a system that future you and teams can extend without fear.
Minimize design debt early, and you gain long-term velocity. Ignore it, and the system eventually resists every change you try to make.
Enjoyed this post? Check out my book — Designing Trading Systems: Trade Booking, Lifecycle, and Flow for deeper insights into real-world system design.