skip to content

Task Graph (DAG) Construction

The TaskExecutionGraph computed once configuration finishes, and how dependsOn, finalizedBy, and declared inputs wire it together. Interviewers ask because it is the boundary where configuration decisions become an execution plan.

on this pageshow

questions

6

What is the task execution graph in Gradle, and at what point in the build lifecycle is it constructed?

level: juniorimportance: must knowfreq 70%

answer

  1. three phases: init, config, execution
  2. DAG built between configuration and execution
  3. nodes=tasks, edges=dependencies
  4. acyclic or build fails
  5. requested tasks walked transitively

basics

~10 s

It's a directed acyclic graph (DAG) of the tasks Gradle will run for the requested build, ordered by their dependencies. Gradle builds it after the configuration phase finishes and before execution starts.

solid answer

~40 s

Gradle runs in three phases: initialization, configuration, then execution. The task execution graph (a DAG) is computed at the boundary between configuration and execution. During configuration, every task is created and its relationships (`dependsOn`, `finalizedBy`, declared input/output wiring) are registered. Once configuration finishes, Gradle takes the tasks you requested on the command line and walks their dependencies transitively to build a complete, ordered, deduplicated graph. It's *acyclic* — a cycle between task dependencies is a build error. Only after the full graph exists does execution begin, running each task once in an order that respects all edges. This 'plan everything, then run' model is why the graph is available before any task action executes.

code

kotlin · 6 lines
kotlin
tasks.register("compile") { doLast { println("compiling") } }
tasks.register("test") {
    dependsOn("compile")
    doLast { println("testing") }
}
// gradle test  ->  graph: compile, test  ->  runs compile then test

go deeper

for a junior

Name the three phases and state that the DAG is built after configuration, before execution.

for a middle

Explain transitive walking of requested tasks and why the graph must be acyclic.

for a senior

Connect plan-then-run to up-to-date checks, caching, and parallelism reasoning over the whole plan.

for a principal

Discuss how the global graph view enables build-wide optimizations and how configuration cost trades against this planning model.

## The three lifecycle phases Every Gradle build moves through three phases in strict order: 1. **Initialization** — Gradle decides which projects participate (reads `settings.gradle(.kts)`) and creates the `Project` objects. 2. **Configuration** — Gradle evaluates every build script, *creating and configuring* tasks and registering their relationships. No task *actions* run yet. 3. **Execution** — Gradle runs the selected tasks' actions in dependency order. ## Where the graph fits The **task execution graph** is computed *between* configuration and execution. It is a **DAG (directed acyclic graph)**: nodes are tasks, edges are dependency relationships, and it must contain no cycles. During configuration, relationships are declared but not yet resolved into an ordering. Once configuration completes, Gradle: 1. Takes the **requested tasks** (what you typed, e.g. `gradle build`). 2. Walks their dependencies **transitively** — `dependsOn`, implicit dependencies inferred from declared inputs/outputs, and `mustRunAfter`/`shouldRunAfter` ordering hints. 3. Produces a single, deduplicated, topologically-sortable graph of every task that must run, plus any `finalizedBy` finalizers. ## Why acyclic matters If task A depends on B and B (transitively) depends on A, there is no valid execution order, so Gradle fails with a *circular dependency* error before any action runs. This guarantees a deterministic order. ## Plan-then-run Because the **whole** graph is built first, Gradle knows up front exactly what will run. This enables features like up-to-date checking, the build cache, and parallel execution to reason about the entire plan instead of discovering work as it goes. ```kotlin tasks.register("compile") { doLast { println("compiling") } } tasks.register("test") { dependsOn("compile") // edge: test -> compile doLast { println("testing") } } // `gradle test` builds a graph [compile, test] and runs compile, then test. ```

  • Why must the graph be acyclic?
    A cycle means there is no valid topological order — Gradle can't decide which task to run first, so it fails fast with a circular dependency error instead of running anything.
  • Can task actions run during configuration?
    No. Configuration only creates and wires tasks. Actions (`doFirst`/`doLast`/the task's work) run only during execution, after the graph exists.

Like a recipe: you read the whole recipe and lay out every step in order (the graph) before you start cooking — not improvising step-by-step.

saying these in an interview costs you the question

  • Saying the graph is built during configuration of each script (it's built after all configuration completes).
  • Claiming the graph can contain cycles or that order is non-deterministic.

context

open as a page

How do dependsOn, finalizedBy, mustRunAfter, and shouldRunAfter each shape the task graph, and how do they differ?

level: middleimportance: must knowfreq 65%

basics

~20 s

dependsOn adds a real edge that pulls the dependency into the graph. finalizedBy schedules a task to run after another, even on failure. mustRunAfter/shouldRunAfter only constrain ordering — they don't add the task to the graph.

open as a page

How does declaring a task's inputs and outputs create implicit dependency edges in the task graph?

level: middleimportance: should knowfreq 50%

basics

~10 s

If task B consumes a file produced as task A's declared output, Gradle infers that B depends on A and adds that edge automatically — you don't need an explicit dependsOn.

open as a page

When you run `gradle build`, how does Gradle decide which tasks end up in the execution graph, and how are duplicates handled?

level: middleimportance: should knowfreq 45%

basics

~10 s

Gradle starts from the tasks you named (the requested tasks), walks their dependencies transitively, adds any finalizers, and deduplicates so each task appears once. The result is the ordered execution graph.

open as a page

Gradle fails graph construction with a circular dependency error. What does this mean, and how do you diagnose and resolve it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

It means the dependency edges form a cycle, so no valid execution order exists. Read the printed cycle path, then break it — usually by replacing a wrong dependsOn with an ordering hint or by extracting shared work into a third task.

open as a page

Why does Gradle compute the entire task graph before executing any task, and what capabilities does this 'plan-then-run' model enable?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Knowing the whole graph up front lets Gradle schedule independent tasks in parallel, decide up-to-date/cached status correctly, fail fast on cycles, and report progress — because it sees all the work and its ordering before running anything.

open as a page