2.1.2 Laziness & Slicing
P.2.4 Five Properties named laziness and slicing before explaining the machinery behind them. That machinery is the target graph from 2.1.1 Nodes, Edges & Acyclicity: Bazel starts from the target you asked for, follows its transitive dependency closure, and keeps the rest of the repository outside the request. A build is not "the whole repo". It is the reachable part of the DAG for this command.1,2,3
Packages on this path load BUILD files for //foo and //common only.
Top-level target for this command
Direct dependency
Direct dependency
Transitive dependency beneath //foo:lib
No path from the request into this subgraph. Not loaded or analyzed for this build.
Reachability carves out the slice
bazel build //foo:foo means "work backward from //foo:foo and everything it needs," not "inspect every target in the workspace." If a branch has no path into that closure, it is outside the build. That is the core idea behind Bazel's laziness: reachability gives Bazel a precise boundary for the current request, so it can do the minimal work needed for the outputs you asked for.1,3,4
Slicing starts at package loading
Because 0.1.3 Package is the unit Bazel loads from disk, slicing shows up before any compiler or test runner starts. During loading, Bazel loads the necessary BUILD files for the initial targets and their transitive closure of dependencies. During analysis, it decides what work to do while still reading only BUILD files at that stage. The phase names come in 2.2.1 Loading, Analysis & Execution. The important point here is simpler: unreachable packages never enter the request in the first place.1,2
Laziness is different from incrementality
Laziness answers "what is in scope for this command?" Incrementality answers "what changed since the last command?" Skyframe starts from the top-level build request, then each SkyFunction asks for the nodes it needs until the dependency graph involved in that build has been discovered. 2.4.1 Skyframe & Incrementality is the next layer of the story: it explains how Bazel remembers and recomputes prior work, while laziness explains why unreachable branches never become part of the current request at all.2,5
A quick way to inspect the slice without producing outputs is to query either the reachable targets or the BUILD files behind them:
bazel query 'deps(//foo:foo)'
bazel query 'buildfiles(deps(//foo:foo))' --output package
deps() shows the reachable target closure. buildfiles(deps(...)) --output package is a practical way to see which packages Bazel needs in order to understand that closure. The beginner tour of these commands comes next in 2.1.5 Inspecting the Graph — Query Preview.6
Laziness is reachability applied to a build request. Slicing is the visible result: Bazel loads only the packages whose BUILD files sit on the requested target's dependency closure, analyzes only reachable targets, and later executes only the actions needed for requested outputs. Big repository, small request, small slice.1,2,7
Requested outputs are sliced too
The same reachability rule shows up one layer later in the action graph. When a target has several outputs, Bazel only builds the requested files and the files they directly or indirectly depend on, so only the reachable actions execute. That is why BUILD declarations are recipes, not a script of mandatory steps: Bazel can work backward from requested outputs instead of eagerly running every action a target could possibly produce. 2.2.1 Loading, Analysis & Execution goes deeper into that action-level view.7,8,9
Check your understanding · 3 questions
1.What does Bazel's laziness guarantee when you run bazel build //foo:foo?
Select one answer
2.True or false: laziness vs incrementality in Bazel.
Choose True or False for each sentence
3.Which command shows the set of packages Bazel must load to understand the dependency closure of //foo:foo?
Select one answer
Footnotes
-
Intro to Bazel — Bazel loads the
BUILDfiles relevant to the target ↩1 ↩2 ↩3 ↩4 -
Build programs with Bazel — loading phase loads the necessary
BUILDfiles for the initial targets and their transitive dependency closure, and loading/analysis read onlyBUILDfiles to determine work ↩1 ↩2 ↩3 ↩4 -
Dependencies — Bazel inspects the entire transitive closure of a target's dependencies during a build ↩1 ↩2
-
Bazel Training 101 (Part 3): Mental model — Bazel as a lazy transformation that constructs the minimal work needed for the outputs you asked for ↩
-
Skyframe — evaluation starts from the top-level build request and each
SkyFunctionrequests the nodes it needs to finish that request ↩ -
Query guide —
deps()for reachable closure andbuildfiles(deps(...)) --output packagefor the package set behind that closure ↩ -
Rules — Bazel only builds requested files and the reachable transitive dependencies of those requested outputs ↩1 ↩2
-
Sponsored Session: Writing Bazel Rules - Instructor: Jay Conrod — action laziness as "subset of the graph, subset of actions, then only uncached actions run" ↩
-
Building Monorepo gRPC Services with Bazel — BUILD files as declarative recipes and Bazel working backward from the requested output ↩