5.3.1 Graph Theory Foundations

recommended

When one dependency seems to join two teams, a target sits on every visible route, or a closure keeps growing, the useful question is not merely “how big is the graph?” You need to know whether the structure suggests an architecture boundary, a choke point, an ordering constraint, or a broad reachability set.

2.1.1 Nodes, Edges & Acyclicity established the basic target DAG. At consultant depth, a graph finding is useful only after you name the graph: the same Bazel label can participate in the unconfigured target graph, become several configured-target nodes, and register zero or more actions. Connected components, bridges, topological order, and transitive closure answer different questions on each layer. Choose the layer and scope before interpreting the result.

Fix the Graph Contract First

For the examples below, write a target edge as

//app:server -> //lib:protocol

meaning “//app:server depends on //lib:protocol.” This is the direction used by Bazel query graphs: an edge points from a target to one of its prerequisites. deps(//app:server) therefore walks with the arrows, while reverse-dependency analysis walks against them.1

The same small graph makes the four structural questions easier to keep separate:

One target graph, four different questions
Every arrow points from a dependent to its prerequisite. Change the question, not the edge direction.
WEAK COMPONENT 1 · also deps(//app:server)
//app:server
//api:facade articulation vertex
//lib:model
//lib:auth
//base:types
WEAK COMPONENT 2 · //tools
:lint
:rules
WEAK COMPONENT 1 · arrows point to prerequisites
//app:server
//api:facade
DEPENDS ON BOTH
//lib:model
//lib:auth
BOTH DEPEND ON
//base:types
SEPARATE WEAK COMPONENT
:lint
:rules
Reachability · transitive closure deps()

Which targets can server reach by following arrows?

Answer: server, facade, model, auth, and types — including the seed itself.

Weak components ignore direction

Which groups stay connected if arrows become lines?

{server … types}
{lint, rules}
Cut structure ignore direction

Which one vertex or edge would split a component?

Vertex: facade
Bridge: server → facade
Valid dependency order one valid order

Which listing puts every dependent before its dependency?

server · lint · facade · rules · model · auth · types

Closure follows arrows; components and cuts temporarily ignore them; topological order respects them — but does not describe Bazel scheduling or cache behavior.

That target graph is the right default for questions about declared architecture. It is not the only Bazel graph:

QuestionNode identityEvidence
What can the BUILD declarations depend on?unconfigured targetquery
What does this build configuration actually select?label plus configurationcquery
What compile, link, or generation work is planned?action and artifactaquery
What ran, waited, or hit a cache in one invocation?observed execution eventprofile, execution log, or BEP

query runs after loading and conservatively includes all possible select() branches. cquery runs after analysis, resolves those branches, and can contain more than one configured node for the same label.2 aquery exposes the actions and artifacts produced by analysis. It does not report what executed or whether an action was a cache hit. 2.1.5 Inspecting the Graph — Query Preview introduces this layer choice, while 5.2.1 bazel query — Static Graph Analysis, 5.2.2 bazel cquery — Configured Graph, and 5.2.3 bazel aquery — Action Graph provide the operating details.

Also declare the scope and projection. Results change when you include source file targets, implicit dependencies, external repositories, or only one configured universe. For classic connected-component and bridge algorithms, you must additionally say that direction is being ignored. Without those choices, “the graph has three components” is not a reproducible finding.

Connected Components: Candidate Boundaries

In an undirected graph, a connected component is a maximal set of nodes joined by paths. For a Bazel dependency graph, the useful analogue is usually a weakly connected component: take the selected directed graph, temporarily ignore edge direction, then compute its components.3 Strongly connected components ask a different question—mutual directed reachability—and are not a useful decomposition of an acyclic configured build graph.

Suppose //payments/... and //search/... fall into different weak components after you restrict the graph to first-party rule targets. That is evidence that neither area has a target-dependency path to the other under this projection. It makes them candidates for separate ownership, extraction, or independently scoped validation. It is not yet proof that they are operationally independent: including a shared generated target, resolved toolchain, external repository, or configuration-specific edge may connect them on another layer.

Nor does a component count predict runtime parallelism. Two disconnected target subgraphs can still compete for CPU, memory, workers, or remote capacity. A single connected component can expose abundant action-level concurrency. Use components to propose architecture boundaries. Use the analyzed action graph and a profile to evaluate scheduling and elapsed time.

When exporting query results for an algorithm, preserve node identity and edges. The default Graphviz output may factor topologically equivalent nodes, which is helpful for pictures but unsuitable as an exact algorithm input. disable factoring or consume a structured output that lets the analysis tool reconstruct the graph.1

Articulation Points and Bridges: Structural Choke Points

On the same undirected projection, an articulation point is a vertex whose removal increases the number of connected components. A bridge is an edge whose removal does so.3 They answer: “Which single vertex or dependency link holds these regions together?”

That is not the same as fan-in or change impact:

  • A bridge can connect two small regions and have few reverse dependencies.
  • A heavily used foundation can have high fan-in without being an articulation point because alternative undirected paths keep the graph connected.
  • Removing a target is a hypothetical graph operation. Editing its sources does not remove the node or its edges.

Use a bridge finding to inspect a cross-domain dependency: should the consumer depend on a smaller public interface, should shared code move to a neutral package, or is this an intentional boundary that needs explicit ownership and compatibility policy? Use an articulation finding to locate a target that mixes otherwise separate domains and may deserve decomposition. Then verify the specific directed relationship. If //app:server must stop reaching //legacy:api, inspect allpaths(//app:server, //legacy:api): breaking one apparent edge is insufficient when another directed path remains.4

If the real question is “what could be affected by changing this library?”, compute reverse reachability with rdeps(universe, library) instead. That result is a structural candidate set, not a promise that all members rebuild or retest. Configuration, action inputs, pruning, and cache state decide what work an invocation actually performs.

Topological Order: Constraints, Not a Schedule

A topological order linearizes a DAG while respecting its directed edges, but many valid orders may exist. The direction convention matters. Because a Bazel query edge points from a dependent to its prerequisite, --order_output=deps prints dependents first and dependencies afterward.1 This is a valid topological order for that edge orientation. It is the reverse of a “build-ready” listing that puts prerequisites before their consumers.

Do not read ordinary lexical output as graph order: current query defaults can print labels lexicographically, and nodes unrelated by reachability have no dependency-imposed order.1 Even a dependency-ordered target list is not Bazel's execution schedule. Targets are not actions, and the target-to-action mapping is not one-to-one. Resource limits, execution strategies, cache reuse, and action durations all affect the observed schedule.

Topological reasoning is still useful for finding constraints. A long chain in the target graph suggests architectural layering to inspect. A long chain in the action DAG can constrain execution. To make a performance decision, examine the latter with action-graph and profile evidence. A target ordering alone cannot show how much parallelism was available or used.5

Transitive Closure: Reachability with a Direction

The forward transitive closure of a target is everything reachable by following dependency edges. Bazel's deps(x) computes the reflexive transitive closure, so the result includes x itself. Its optional depth bounds the traversal. rdeps(u, x) computes reverse reachability from x, but only within the transitive closure of the chosen universe u.1

These two closures support different decisions:

  • deps(service) describes the declared dependency footprint to review for unexpected libraries, policy violations, licensing exposure, or a proposed extraction.
  • rdeps(protected_universe, library) describes the consumers that may need migration, compatibility review, or targeted validation when an API changes.
  • cquery deps(service) asks the forward question for a particular configured context, where select() and transitions matter.2

Closure size is a signal, not a diagnosis. A growing target closure may justify investigating dependency bloat, but it does not by itself measure configured targets, registered actions, action inputs, cache keys, cache hits, or executed work. A large closure can be mostly reusable. A small closure can contain one expensive action. Investigate suspected analysis breadth with configured-target counts and profiles, and inspect planned work with action counts. Confirm rebuild or cache behavior with profiles and execution logs—not with wc -l over query output.5

Finally, keep the Bzlmod module graph separate. bazel mod graph explains external module resolution. It does not replace query for target dependencies, cquery for configured targets, or aquery for actions.6

key takeaway

Apply graph theory only after declaring the Bazel layer, node identity, edge direction, universe, and projection. Weak components suggest architecture boundaries. Articulation points and bridges identify single structural connections, not high-impact changes. Topological order expresses dependency constraints, not an execution schedule, and transitive closure measures reachability, not rebuilds or cache effectiveness. Escalate from query to cquery, aquery, and invocation evidence as the decision moves from declared structure to configured structure, planned work, and observed execution.

Check your understanding · 4 questions

1.A first-party query projection places //payments/... and //search/... in separate weakly connected components. Which conclusions are justified?

Select all that apply

2.A library has many direct dependents, but alternate undirected paths still connect the graph when the library node is removed. How should a consultant classify it?

Select one answer

3.bazel query --order_output=deps lists a consumer before its prerequisite. What does that ordering establish?

Select one answer

4.Match each graph concept to the structural question it answers most directly:

Drag each answer onto the matching prompt, or click an answer and then click a prompt

Answers
Weakly connected components
Bridge
Topological order
Forward transitive closure
0 of 4 answered

Footnotes

  1. The Bazel Query Reference — graph datatype, edge direction, closure operators, graph factoring, and output-order semantics 1 2 3 4 5

  2. Configurable Query (cquery) — configured-target identity, resolved select() branches, and universe scope 1 2

  3. Introduction to the dependency graph — connected components, articulation points, bridges, transitive closure, and topological order 1 2

  4. Query guidesomepath, allpaths, reverse dependencies, and dependency-path analysis

  5. Extracting build performance metrics — distinct target, configured-target, action, profile, and execution evidence 1 2

  6. mod Command — the external module-resolution graph and its path-analysis commands