Glossary · Structure and dead code

What is a call graph?

A call graph is a directed graph of a program in which each node is a function or method and each edge means one function calls another. A static call graph is built from source code without running it. A dynamic call graph is recorded while the program runs, and only shows the calls that actually happened.

Last reviewed: November 2026

Also called Function call graph, Call tree, Caller-callee graph

How a call graph is measured

A call graph is built, not measured, but its quality can be judged by precision (how many edges are real) and recall (how many real calls are present). The build has three steps:

  1. Parse each file and find every call expression.
  2. Resolve each call to the function it names.
  3. Add an edge from the calling function to the target.

Step 2 is where graphs differ. A call to a plain function in the same file resolves with certainty. A method call on a variable depends on the variable's type, and with inheritance one call site can reach several targets.

CallResolution
helper() defined in the same fileCertain
from billing import charge; charge(x)Follows the import
obj.save() where obj has a declared typeNeeds type information
getattr(obj, name)() or handlers[name]()Cannot be resolved statically

A static graph over-approximates (it includes calls that never happen) or under-approximates (it misses dynamic ones), depending on how conservative the analyzer is. A dynamic graph, recorded by a profiler or tracer, is exact for that run and silent about every path the run did not take.

Why call graphs matter

The call graph answers questions that imports cannot. Before changing a function you want its callers, not only the files that import its module. Tests that reach a change are found by walking call edges to test functions. Code with no incoming call edges is a dead code candidate. Walking callers outward from a change gives its blast radius. Because many edges are inferences, the useful graphs also say how sure they are.

How to measure a call graph yourself

  • Python: pyan3 generates static call graphs, and python -m cProfile plus a viewer such as snakeviz or gprof2dot gives a dynamic one.
  • Go: go-callvis renders a program's call graph interactively, and deadcode uses the same kind of analysis (Rapid Type Analysis) from main.
  • C and C++: Doxygen with CALL_GRAPH = YES and CALLER_GRAPH = YES draws call and caller graphs through Graphviz (HAVE_DOT = YES).
  • Java, C#, Kotlin: the IDE's call hierarchy view (IntelliJ, Visual Studio) shows callers and callees for one symbol on demand.
  • Any language, dynamic: a sampling profiler or tracer records the calls that actually ran.

An IDE call hierarchy is the right tool for one function in one language. A standalone graph tool fits a diagram or a one-off audit. Neither feeds the graph into risk scoring, test selection and cleanup queries.

How repowise measures call graphs

repowise builds a resolved call graph at index time from tree-sitter parses, with no LLM calls. It covers the Full and Good language tiers (26 languages: see the language support reference). Symbols and call edges live in the same graph as file imports, so you can walk from a file to its symbols to their callers.

Every calls edge carries one of 39 resolution origins from a fixed vocabulary, and each origin has one fixed confidence:

ConfidenceExample originsMeaning
0.95same_file, self_scopeDefined in the calling file, or a method on the caller's own class
0.90import_scoped, same_packageResolved through an import or a sibling file
0.75receiver_globalThe class and method pair exists somewhere in the repo
0.50global_uniqueThe name is unique repo-wide: a guess, labelled as one
  • MCP: get_context with include=["callers"] or ["callees"] returns call edges, and an empty callers list comes with a *_basis explaining why. get_symbol returns one symbol with its edges. get_dead_code and get_risk read the same graph.
  • CLI: repowise impacted-tests main...HEAD lists the tests that reach a diff.

FREQUENTLY ASKED

Call graph: questions

What is the difference between a call graph and a dependency graph?

A call graph has functions as nodes and calls as edges. A dependency graph usually has files or packages as nodes and imports as edges. The call graph is finer-grained: two files can share one import while one function calls the other many times.

What is the difference between a static and a dynamic call graph?

A static call graph is derived from source and includes every call that could happen, plus some that never do. A dynamic call graph is recorded from a real run and includes only the calls that happened, so it is exact but incomplete.

Why are call graphs hard to build?

Many calls are not decided until runtime: virtual methods, interface calls, function pointers, callbacks, reflection and string-built names. A static analyzer has to guess the target or return several candidates.

What are call graphs used for?

Finding who calls a function before changing it, selecting the tests that reach a change, finding code nothing calls, tracing execution flows, and spotting recursion or deep call chains that are hard to reason about.

See it on your own repository.

Index a repository free and read the result for your own code and history. Rolling it out to a team? We will walk you through it.