By Arjun Mehta
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 forty services, shared libraries, async event handling, and database dependencies that cross service boundaries, the answer can take days to fully map - and even then, someone will miss something.
Dependency mapping is the discipline of answering that question systematically. Done well, it's the difference between confident changes and "fingers crossed" deploys.
Two Very Different Things Called "Dependency Mapping"
If you search for dependency mapping software, most results will show you tools like Device42, SolarWinds, and Faddom. These are infrastructure dependency mapping tools - they map networks, servers, application components, and cloud resources. They answer questions like "which servers does this application run on" and "what network paths connect these services." Extremely useful for operations, cloud migration planning, and ITSM workflows.
That is not what software engineers typically need.
Code-level dependency mapping answers different questions: Which modules import this one? Which services call this API? Which parts of the codebase share this data model? If I change this function signature, what breaks? If I deprecate this library, which teams do I need to notify?
The distinction matters because the tools and techniques are almost entirely different. An infrastructure dependency map is built from network scanning and configuration data. A code-level dependency map is built from static analysis, import graphs, call graphs, and package manifests. They visualize different layers of the same system.
This guide is about code-level dependency mapping - what it is, why it matters, and how to actually do it.
Why Dependency Mapping Matters More Than It Used To
Three things have made code-level dependency mapping significantly more important over the last five years.
Microservices distribution. When most code lives in a monolith, dependencies are relatively transparent. You can trace a call chain with a debugger. In a distributed system, service-to-service dependencies are implicit in API calls, event subscriptions, and shared data stores. A change to a service's event schema can break three other services that nobody remembered were consuming that event. The blast radius of any change is harder to see and easier to underestimate.
AI-assisted code generation. Engineers using Copilot or Cursor can generate code that calls functions, imports modules, and creates new dependencies faster than they can fully reason about what they're depending on. Generation velocity has outpaced comprehension velocity for many teams. The result is codebases where dependency relationships grow faster than they can be tracked. I wrote more about this dynamic in AI Code Assistant vs Codebase Intelligence.
Team scale. When your team grows past fifteen engineers, nobody holds a complete mental model of the codebase. Dependencies that feel obvious to the engineer who wrote a module are invisible to the engineer working in a different area. Without explicit dependency mapping, you're relying on individual knowledge islands to prevent cross-module breakage - and those islands have gaps.
What a Code-Level Dependency Map Actually Shows
A code-level dependency map has multiple layers, each answering different questions.
Module-level dependencies. Which packages, libraries, and internal modules does each part of the codebase depend on? This is the most granular layer - essentially a visualization of your import graph. It's useful for understanding which modules are highly coupled (lots of incoming dependencies), which are isolated (few connections), and which are "load-bearing walls" that many other things depend on. Changing a load-bearing module has a large blast radius; changing an isolated module has a small one.
Service-level dependencies. Which services call which other services, and through what interfaces? This layer is critical for distributed systems. It shows you the API dependency graph: if Service A calls Service B's endpoint, then Service A depends on Service B. Any change to that endpoint is a potentially breaking change for Service A. Service-level dependency maps are often drawn manually in architecture diagrams, which means they go stale. Deriving them from actual API call patterns is more reliable.
Data dependencies. Which services read and write the same databases, tables, or event topics? Data dependencies are often the most dangerous and the hardest to see. Two services might have no code-level coupling but be tightly coupled through a shared database table. A schema change affects both - but neither service's code makes the dependency explicit. This is where incident post-mortems often identify the root cause: "we didn't know Service X was reading from that table."
Ownership mapping. Who owns which modules and services? This is a human layer on top of the technical dependency map. When you know a change will affect Module Y, you also need to know who to loop in before making that change. Ownership can be derived from git blame patterns and PR history - the engineers who most frequently modify a module are typically its de facto owners.
How to Do Dependency Mapping in Practice
There are three practical approaches, ranging from lightweight to comprehensive.
Package manifests as a starting point. Every language ecosystem has package management that encodes external dependencies explicitly. package.json, requirements.txt, go.mod, Gemfile - these are machine-readable dependency graphs. Tools like Snyk, Renovate, and Dependabot parse these to track what version of what library you're running. This handles external dependencies well but tells you nothing about internal module coupling or service-to-service dependencies.
Static analysis for internal structure. For internal module dependencies, static analysis tools can parse your codebase and produce import graphs. Language-specific tools like pydeps (Python), madge (JavaScript/TypeScript), and jdeps (Java) generate dependency graphs from source code. These are most useful for identifying highly-coupled modules and potential circular dependencies. The limitation is that they require running and interpreting tool output rather than giving you an always-on map.
Codebase intelligence tools for living maps. A newer category of tooling - including tools like Glue - reads the codebase continuously and maintains living dependency maps that update as the code changes. Instead of generating a dependency graph as a one-time artifact, they treat the dependency structure as a queryable property of the codebase: "show me everything that depends on this module," "what's the blast radius of changing this API," "which teams need to know about this refactor." This approach is more useful for day-to-day engineering decisions than a static diagram that gets outdated.
The Blast Radius Question
The most practical application of dependency mapping is answering the blast radius question before making changes.
A blast radius analysis for a proposed change works in three steps. First, identify the direct dependencies: what immediately calls or imports the thing you're changing. Second, trace the transitive dependencies: what depends on the things that depend on your change. Third, identify the ownership of each affected component: who needs to be notified or consulted.
For a small change with a well-understood blast radius, this takes thirty seconds. For a significant refactor, it might take an hour of careful analysis. Either way, doing it before the change prevents the alternative: discovering the blast radius after an incident.
The teams that do this well treat blast radius analysis as a standard part of engineering design - not a step you add when something looks risky, but a default question asked about any change that touches shared code. See Understanding Code Dependencies and Code Dependency Analysis for deeper coverage of the technical foundations.
Common Dependency Mapping Mistakes
Mapping only what you know about. It's easy to map the dependencies you already understand and miss the ones you don't. Implicit dependencies - shared databases, common event topics, implicit data format assumptions - often cause the worst incidents precisely because they weren't on the map.
Drawing it once and not updating it. A dependency map that's six months out of date can be worse than no map, because it creates false confidence. If you're going to maintain a manual dependency diagram, build in a process for updating it when architecture changes. If that's not realistic, use tooling that derives the map from the current code.
Ignoring the ownership layer. A dependency map that shows technical connections but not human ownership is only half the picture. Knowing that Module X depends on Module Y is useful. Knowing that Module Y is owned by Team Z and any changes to its public interface need Team Z's review is what actually prevents breakage.
Treating it as an audit artifact rather than an engineering tool. Dependency maps created for compliance or architecture reviews often sit in wikis and aren't used operationally. The maps that actually prevent incidents are the ones engineers consult before making changes - which means they need to be fast to access, always current, and integrated into the engineering workflow.
FAQ
What is dependency mapping in software?
Dependency mapping in software is the process of identifying and visualizing how different parts of a codebase or software system depend on each other. This includes module-to-module import relationships, service-to-service API calls, shared database dependencies, and package dependencies on external libraries. The goal is to understand the blast radius of changes and the structural architecture of the system.
What tools are used for code-level dependency mapping?
For external dependencies, package managers (npm, pip, Maven) and security tools (Snyk, Dependabot) provide dependency graphs. For internal module structure, language-specific static analysis tools like madge (JS/TS), pydeps (Python), and jdeps (Java) generate import graphs. Codebase intelligence platforms like Glue provide living dependency maps that stay current as the code changes, with features like blast radius analysis and ownership mapping.
How is dependency mapping different from service mapping?
Service mapping (as used in ITSM and infrastructure tools like ServiceNow or SolarWinds) maps the relationships between IT services, infrastructure components, and business services. Code-level dependency mapping maps the relationships within and between software systems at the code level. Both are called "dependency mapping" but address different audiences and questions - operations teams for service mapping, engineering teams for code-level dependency mapping.
How do you create a dependency map for a large codebase?
For large codebases, manual dependency mapping is not practical. The scalable approaches are: automated static analysis for module-level dependencies, API gateway or service mesh telemetry for service-level call patterns, and codebase intelligence tools that continuously analyze the codebase and maintain a queryable dependency graph. Starting with the highest-impact modules (the ones with the most incoming dependencies) is more useful than trying to map everything at once.
Related Reading
- What Is Story Point Estimation?
- What Is Agile Estimation?
- Story Points Are Useless
- Velocity Doesn't Tell You How Far You Need to Go
- Glue for Engineering Planning