Dependency Mapping: How to Know What Will Break Before You Break It
The question that costs engineering teams the most time is deceptively simple: "What will break if we change this?"
In a monolith from five years ago, you could grep for references and get a reasonable answer in ten minutes. In a modern distributed codebase with 40 services, shared libraries, async event handling, and database dependencies that cross service boundaries, the answer can take days to map — and even then, someone will miss something.
At Salesken, I learned this the expensive way. An engineer changed the timeout configuration on our STT (speech-to-text) client. Simple change. One config value. But the STT client was imported by the coaching engine, which fed the event processor, which fed the analytics pipeline. The timeout change caused the coaching engine to fail faster, which sent a burst of error events that overwhelmed the analytics queue. Four services affected. One config change. We didn't know the dependency chain existed until we were debugging the incident at 2 AM.
That incident is why I think dependency mapping isn't optional for any team running more than a handful of services.
Two Different Things Called "Dependency Mapping"
The term "dependency mapping" means different things in different contexts, and conflating them causes confusion.
Infrastructure/service dependency mapping is what ITSM tools like ServiceNow and SolarWinds do. It maps relationships between servers, load balancers, databases, and deployed services. This is useful for operations teams managing infrastructure.
Code-level dependency mapping is what engineering teams need during development. It maps how modules import each other, which functions call which, which services make API calls to which other services, and which code shares database tables or event topics.
When I talk about dependency mapping in this post, I mean code-level. This is the layer that answers "what breaks if I change this function" or "which teams need to coordinate on this refactor."
Why This Matters More Than It Used To
Three trends have made dependency mapping increasingly critical.
Microservices created more surfaces. A monolith has internal dependencies that are visible through import statements. Microservices have the same dependencies, but they're encoded as API calls, event subscriptions, and shared data stores. At Salesken, our monolith had about 200 internal module dependencies — visible in the import graph. When we moved to microservices, we had roughly the same number of dependencies, but they were scattered across HTTP calls, Kafka topics, and shared PostgreSQL schemas. The dependencies didn't decrease. They became harder to see.
AI coding tools accelerate dependency creation. Engineers using Cursor and Copilot generate code faster, which means they create new dependencies faster. At Salesken, after adopting Cursor, our import graph grew 40% in 6 months. Not because the team was careless — because they were productive. Every new feature adds dependencies. Faster features means faster dependency growth.
Team growth distributes knowledge. When 5 engineers know the whole system, dependencies are in shared mental models. When 40 engineers each know their corner, nobody has the full picture. At UshaOm, where I grew a team from 5 to 27, the transition from "everyone knows everything" to "nobody knows everything" happened around engineer 15. After that, dependency surprises became a monthly occurrence.
What a Dependency Map Shows
A useful code-level dependency map captures multiple layers:
Module dependencies. Which files import which other files. This is the foundation — derivable from static analysis of import/require/include statements.
Service dependencies. Which services call which other services, through what protocols (HTTP, gRPC, message queues). At Salesken, our service dependency map revealed that our "independent" analytics service actually made synchronous calls to three other services during its startup sequence. When any of those three were slow, analytics startup took 5 minutes instead of 30 seconds.
Data dependencies. Which services share database tables, Redis keys, or other data stores. These are the sneakiest dependencies — no import statement, no API call, just two services reading from the same table.
At UshaOm, our product catalog and inventory modules had no direct code dependencies. But they both read from the same products table and assumed the same SKU format. When the catalog team changed the SKU format, inventory broke. No static analysis tool would have caught it because the dependency was in the data, not the code.
Ownership overlay. Who owns each node in the dependency graph. This transforms a technical map into an operational one. Knowing that Module X depends on Module Y is useful. Knowing that Module Y is owned by a single engineer who's on vacation next week is actionable.
How to Do Dependency Mapping in Practice
For module-level dependencies: Use language-specific static analysis. madge for JavaScript/TypeScript, pydeps for Python, jdeps for Java. These generate import graphs automatically. At Salesken, we ran madge on every PR as part of CI — it flagged circular dependencies before they merged.
For service-level dependencies: Use distributed tracing (Jaeger, Zipkin) or service mesh telemetry (Istio, Linkerd). These derive service call graphs from actual traffic rather than from documentation. After our STT cascade incident, we added Jaeger tracing across all services. Within a week, we discovered 3 dependency chains we didn't know about.
For data dependencies: This is the hardest layer. No automated tool reliably detects all shared data access. At Salesken, we manually cataloged which services read from and wrote to which database tables. It took two days and was immediately valuable — we found 4 services sharing a table that should have had independent schemas.
For ownership: Derive from git history. Who commits most to each module? Who reviews PRs for it? At Glue, this is one of the layers we build automatically — bus factor and ownership derived from actual contribution patterns, not org charts.
The Blast Radius Question
The practical payoff of dependency mapping is answering the blast radius question: "If I change X, what else is affected?"
Before any significant change, ask:
- What directly depends on this code? (First-order blast radius)
- What depends on those dependents? (Second-order blast radius)
- Who owns the affected code? (Coordination requirements)
- What's the test coverage on the affected code? (Risk assessment)
At Salesken, after the STT incident, we made blast radius analysis a standard step in our design review process. Before any change to a shared module, the engineer maps the dependency chain and identifies affected services. For a small change with a well-understood blast radius, this takes 30 seconds. For a significant refactor, it might take an hour. Either way, doing it before the change prevents discovering the blast radius during an incident.
Common Mistakes
Mapping only what you know about. It's easy to map dependencies you understand and miss the ones you don't. Implicit dependencies — shared databases, event topics, data format assumptions — cause the worst incidents precisely because they weren't on the map. At UshaOm, our worst incidents always involved dependencies that were invisible in the import graph.
Drawing it once and not updating it. A dependency map six months out of date creates false confidence. If you can't maintain a manual diagram, use tooling that derives the map from current code. I wrote about this same problem in Software Architecture Documentation — derived maps beat documented maps because they're always current.
Ignoring ownership. A dependency map without human ownership is half the picture. Which team needs to review changes to a shared interface? Who gets paged when a dependent service breaks? These are the questions that prevent breakage in practice.
Treating it as an audit artifact. Maps created for compliance reviews sit in wikis and aren't used operationally. The maps that prevent incidents are the ones engineers consult before making changes — fast to access, always current, integrated into the workflow.
FAQ
What is dependency mapping in software?
The process of identifying and visualizing how different parts of a codebase depend on each other. Includes module imports, service API calls, shared database dependencies, and package dependencies. The goal is understanding blast radius and structural architecture.
What tools are used for code-level dependency mapping?
For external dependencies: package managers (npm, pip, Maven) and security tools (Snyk, Dependabot). For internal module structure: madge (JS/TS), pydeps (Python), jdeps (Java). For living dependency maps with ownership and blast radius: codebase intelligence platforms like Glue.
How do you create a dependency map for a large codebase?
Manual mapping doesn't scale. Use automated static analysis for module-level dependencies, service mesh telemetry for service-level patterns, and codebase intelligence tools for a queryable dependency graph. Start with the highest-impact modules (most incoming dependencies) rather than trying to map everything at once.
Related Reading
- Code Dependencies: The Complete Guide
- Software Architecture Documentation: The Part That Always Goes Stale
- Incident Management: From Alert to Resolution to Prevention
- Code Refactoring: The Complete Guide to Improving Your Codebase
- Observability: Beyond Monitoring
- The CTO's Guide to Product Visibility