The moment you slice something, you create a dependency. Treating it as a surprise is the first mistake.
Draw a boundary around a team, a domain, or a product and you have not just divided a system. You have created a relationship across the cut. Something on one side will need something on the other. That relationship existed before you drew the line. The line made it visible. What you do next is the question.
Most product and engineering organisations answer that question the same way: they open a tracking tool and log it.
What tracking gets wrong
I have sat in Program Board sessions, the dependency planning ceremony at the heart of the Scaled Agile Framework (SAFe), one of the most widely adopted scaling frameworks in large enterprises. Teams spend half a day mapping dependencies onto a physical or digital board. Sticky notes. Red lines. Owners assigned. Dates committed. The board fills up. Everyone photographs it. The session ends.
The board is not the problem. Making dependencies visible is useful. The problem is what happens next: the dependency becomes a ticket, the ticket gets a status, and the status gets reviewed in a fortnightly ceremony. The dependency is now being managed.
Managing a dependency treats it as something that arrived from outside, an accident of the current planning cycle, to be resolved and removed from the board. It does not ask why the dependency exists. It does not ask whether it was inevitable, whether it could have been designed differently at the moment the slice was made, or whether it should exist at all. It asks: who owns it, and when will it be done?
That is the wrong question. And asking it consistently, at scale, is how large product and engineering organisations accumulate coordination overhead that no amount of process improvement will clear, with planning cycles that spend more time negotiating red lines than making decisions about the work itself.
Dependencies are not accidents
Every slice of a whole creates a relationship across the cut. This is not a failure of planning or a gap in execution. It is the direct and predictable consequence of dividing something that was connected.
In Most team boundaries are drawn the same way empires carved up continents, I wrote about fracture planes, the natural seams in a system where a boundary can be drawn without cutting across the grain. In Six ways a clean org chart produces a broken system, every failure mode we examined left the same residue: dependencies that were not planned for, not designed, and not chosen. Inherent complexity, the trade of one type of complexity for another when you split a monolith or restructure a division, produces dependencies between newly separated pieces. Coordination paralysis is what happens when the cost of managing those dependencies between teams grows faster than the value those teams are creating together. Structure debt is what happens when the dependencies calcify into the org chart itself and nobody has the mandate to redesign them.
The pattern is consistent. The teams that struggle most with dependencies are not the ones with the most of them. They are the ones that treat them as surprises.
A dependency that was chosen, understood, and designed into the system is fundamentally different from one that appeared as a red line on a Program Board in week nine of a quarter. The former is architecture. The latter is debt. The distinction begins at the moment of the slice, not after it.
Blocking vs. asynchronous
The first design decision at the moment of a cut: does this dependency need to be resolved before either side can move, or can both sides proceed independently and reconcile later?
A blocking dependency sets the ceiling on delivery speed. If team A cannot ship until team B has delivered, the combined delivery cadence is the slower of the two, always. This is not a coordination failure. It is a structural consequence of where the boundary was drawn.
I have seen this made concrete in a mobile app release process that required sign-off from four teams before any deployment could proceed: the mobile team, the backend API team, the security team, and the infrastructure team. Each sign-off was manual. Each required a synchronous confirmation. A release that should have taken a day took a week, consistently, because the process encoded four blocking dependencies into every single deployment.
The fix was not better coordination. It was a shared deployment pipeline with contract tests between the mobile client and the backend, automated security scanning integrated into the pipeline, and infrastructure provisioned as code. The dependencies between those four teams did not disappear. The mobile team still needed a stable API contract. Security requirements still needed to be met. Infrastructure still needed to exist. But each of those relationships became asynchronous, resolvable without human synchronisation on every deployment. The dependency remained. The cost dropped to near zero.
The design question at the moment of the cut is not “how will we manage this dependency?” It is “does this dependency need to be blocking, and if not, what would make it asynchronous?”
Coordination vs. automation
Human coordination is expensive. It does not scale. When two teams must synchronise before either can act, the cost of that synchronisation is subtracted from the value both are trying to create, every time, not just when something goes wrong.
The design question here is whether the coordination required by a dependency can be encoded into the system rather than performed by people. Trunk-based development encodes the coordination overhead of branch management into a shared working agreement and tooling. Contract testing encodes the coordination overhead of API compatibility into an automated check. Feature flags encode the coordination overhead of release timing into a runtime decision. These are not engineering best practices in isolation. They are dependency design decisions, choices made at the boundary between teams about what humans will coordinate and what the system will handle.
The teams that ship fastest are not the ones that coordinate best. They are the ones that have encoded the most coordination into their systems, leaving human judgment for the decisions that actually require it.
Centralisation vs. duplication
This tension has no clean resolution. Both ends are valid. The reality always lies in the middle, and where the middle falls is a design decision that needs to be made explicitly, not discovered through accumulation.
A centrally managed data platform: a single team owns the infrastructure, the governance model, and the release cadence. Every product team that needs data capabilities depends on it. High governance, low duplication, high autonomy cost. When the central team’s release cadence cannot match the delivery speed of the teams depending on it, the dependency becomes a bottleneck. Teams wait. Roadmaps slip. Eventually, some teams stop waiting and build their own.
Three product teams building their own data products in parallel is not a failure of governance. It is the market clearing price of a centralisation decision that did not account for autonomy cost. But the over-duplication failure is equally real: inconsistent data models, diverging governance standards, and the cost of maintaining three versions of what should have been one capability, paid not once but continuously, in every sprint, by every team that now owns infrastructure they did not set out to own.
Neither end serves anyone well. The design question is not “should we centralise or duplicate?” It is “how stable is this capability, how critical is autonomy for the teams that need it, and what release cadence does the centralised option need to sustain for the dependency to remain acceptable?” Answer those three questions before drawing the boundary. Not after the board fills up with red lines.
Dependencies are design material
Dependency management is the wrong job description for this work.
Managing implies containment: log it, assign it, close it. What the work actually requires is design: understanding why a dependency exists, whether it was inevitable given the slice that created it, whether it can be made asynchronous, whether coordination can be encoded into the system, and whether the centralisation or duplication decision that produced it was made with full awareness of the trade-off.
The difference between a product and engineering organisation that ships with confidence and one that spends its planning cycles negotiating red lines is not process maturity. It is whether dependencies are treated as accidents or as the direct and predictable output of every slicing decision made upstream.
If a dependency surprises you, something went wrong earlier, at the moment a boundary was drawn without thinking about therelationship it was creating. The surprise is not the dependency. The surprise is the signal.
Good slicing produces dependencies that were chosen. Dependencies that were made asynchronous where possible, automated where the cost was worth it, and centralised or distributed based on an honest accounting of autonomy and governance. Dependencies that two teams can look at and say: we knew this was here, we designed it to work this way, and we can change it if the system changes around it.
Every split has a cost. The ones worth making are the ones you designed.
One more question worth sitting with
The three tensions in this article, blocking vs. asynchronous, coordination vs. automation, centralisation vs. duplication, were developed in a world where the agents crossing those dependencies were humans and systems. As AI agents take on more of the work that teams previously coordinated around: data pipelines, integration layers, decision logic, they do not eliminate the dependencies. They inherit them, accelerate them, and create new ones at a speed and volume that existing slicing frameworks were not designed for.
The question I keep returning to is whether the way we design dependencies today is keeping pace, and what it would mean for team topology, domain design, and organisational autonomy if it is not.
That thinking lives at jbpt.de. If any part of this series resonated, or if you are sitting with a slicing problem in your own organisation, I would be glad to continue the conversation there.
What causes dependencies between software teams?
Dependencies between software teams are the direct result of slicing, drawing boundaries around teams, domains, or systems that were previously connected. Every boundary creates a relationship across the cut: something on one side will need something on the other. Dependencies are not caused by poor planning or weak coordination. They are the inevitable consequence of dividing a socio-technical system. The question is not how to eliminate them but whether they were chosen consciously, designed to be asynchronous, automated, or centralised based on explicit trade-offs, or whether they appeared as surprises in a planning session.
What is the difference between a blocking and asynchronous dependency?
A blocking dependency requires one team to wait for another before it can proceed. The combined delivery cadence defaults to the slower of the two, every time. An asynchronous dependency allows both teams to proceed independently, with reconciliation happening through a stable interface, an automated check, or a runtime decision rather than a synchronous human confirmation. The distinction is not discovered after a slice is made. It is a design decision made at the moment of the cut. Converting a blocking dependency to an asynchronous one typically requires encoding the coordination into the system: contract tests, shared pipelines, feature flags, or versioned interfaces.



