FIG. 03 — Software Architecture · System Design

Boundaries: The art of deciding what not to couple

The best architecture decisions are rarely about what to connect. They're about what to deliberately keep apart — and having the discipline to leave a seam where it's tempting to build a shortcut.

July 2026 / 7 min read / By Vimal Maheedharan

Every system design conversation eventually arrives at the same fork: connect these two things directly, right now, and ship faster — or draw a boundary between them and pay for it later in extra plumbing. The direct connection almost always wins the room. It's faster this sprint, it's fewer files touched, and nobody has to write an interface for something that "will obviously never change."

Then it changes.

Why coupling feels productive

Coupling is what happens when two parts of a system reach into each other's internals instead of talking through a defined seam. It's seductive because it's fast — one extra database join, one shared struct, one direct function call across a module boundary, and the feature ships today. Every individual coupling decision looks small. The debt shows up later, all at once, the day two teams need to deploy those "obviously never going to change" pieces independently and discover they can't.

Four shapes coupling takes

Data coupling

Shared tables

Two services reading and writing the same database table. Neither can change its schema without coordinating a migration across both.

Temporal coupling

Deploy ordering

Service B breaks unless Service A is deployed first. The dependency is invisible until a release goes out of order.

Semantic coupling

Shared assumptions

Two teams both assume a field means the same thing, with nothing enforcing it. The drift is silent until it isn't.

Deployment coupling

Shared release trains

Independent services bundled into one deploy pipeline. A change to one forces every other team to ship on its schedule.

The migration that a boundary made boring

The clearest case for a boundary shows up in hindsight, during a migration. A payments service that talked to the rest of the platform only through a defined event contract — never a shared table, never a direct internal call — turned what should have been a six-team, multi-week coordination effort into a change made by one team, on one schedule, that nobody else even had to notice. The boundary had been drawn a year earlier, for reasons that felt almost unnecessary at the time. It stopped being unnecessary the moment the system needed to change.

A boundary is a bet that this part of the system will need to change on its own schedule someday. You rarely lose that bet — you just don't know yet which boundary was the one that mattered.

Where boundaries go too far

Over-decoupling is a real failure mode, not a hypothetical one. Splitting two things that always change together into separate services just because "boundaries are good practice" adds network calls, retry logic, and failure modes to a relationship that was never actually independent. The cost of a boundary is real — it's paid in latency, in operational surface area, in one more thing that can time out. A boundary only earns its cost when the two sides genuinely have separate reasons to change.

The practical test isn't "could this be a boundary" — almost anything could. It's "do these two pieces have different owners, different release cadences, or different reasons to exist." If the honest answer is no, the shortcut isn't technical debt. It's just correctly modeling that these things are, in fact, one thing.

The takeaway

Coupling isn't a mistake you make once and fix later — it's a decision made dozens of times a week, mostly by default, mostly under deadline pressure. The architects whose systems age well aren't the ones who avoided every shortcut. They're the ones who could tell, in the moment, which shortcut they could afford and which one they'd be explaining in a postmortem eighteen months from now.