Empryoempryo.beta
← Glossary
definition

Code dependency graph

A code dependency graph is a directed graph whose nodes are units of code (files, modules, functions, classes, or symbols) and whose edges represent "depends on" relationships such as imports, calls, or references. It models how parts of a codebase are wired together so you can reason about what affects what.

In a code dependency graph, an edge from A to B means A depends on B — A imports the module B, calls the function B, or references the type B. The granularity varies by use case: a module/import graph connects files or packages, a call graph connects functions by who-calls-whom, and a symbol-level graph connects individual declarations. Most real systems combine several of these. The graph is typically directed, often cyclic (mutual recursion, circular imports), and weighted in more advanced versions to capture how strong or frequent a dependency is.

These graphs power a lot of everyday tooling. Build systems use them to decide what to recompile and in what order (a topological sort of the graph). Bundlers walk the import graph to tree-shake unused code. Linters and refactoring tools use reference edges to rename a symbol everywhere it is used. Static analyzers traverse call edges to track how data flows. The shared idea is that structure beats text search: knowing that SessionStore is imported by nine files is a fact about the graph, not something you reliably get by grepping for a string.

A key derived metric is blast radius (or impact set): the set of nodes reachable by following dependency edges backward from a change. If you modify a function, its blast radius is everything that transitively depends on it — the code most likely to break. Centrality measures like PageRank or betweenness identify "hub" nodes that many other nodes depend on; these are the high-risk, high-value parts of a codebase. Building an accurate graph requires real parsing (an AST or compiler front end) rather than regex, because dependencies hide behind aliases, re-exports, dynamic imports, and overloaded names.

Dependency graphs do have limits. Fully static graphs miss dynamic dispatch, reflection, dependency injection, and runtime-resolved imports, so they tend to over- or under-approximate the true call relationships. Keeping the graph fresh as code changes is also non-trivial: a stale graph gives confidently wrong answers. Production-grade implementations re-index incrementally and lean on language servers for the resolution cases parsing alone can't settle.