skip to content

When you run `gradle test`, how does Gradle decide the complete set and order of tasks it executes?

level: middleimportance: must knowfreq 70%

answer

  1. requested tasks -> transitive closure
  2. DAG = directed acyclic graph
  3. topological sort
  4. edges: dependsOn / inferred / finalizers / ordering
  5. graph built before execution

basics

~10 s

Gradle starts from the requested tasks, transitively pulls in all their dependencies into a directed acyclic graph (DAG), then topologically sorts that graph so every task runs after the tasks it depends on.

solid answer

~40 s

From the **requested tasks** on the command line, Gradle walks all dependency edges transitively to build a **directed acyclic graph (DAG)** of every task that must run. Edges come from explicit `dependsOn`, finalizers, ordering rules, and **inferred dependencies** (when one task consumes another's output via Providers). It then performs a **topological sort** so each task is scheduled after everything it depends on. Tasks with no ordering relationship between them may run in any relative order (and, with `--parallel` / project isolation, concurrently). The graph is fully built at the end of the configuration phase, before any execution starts — which is why `gradle.taskGraph.whenReady {}` can inspect the complete plan. If a cycle exists among the reachable tasks, resolution fails before execution with a circular dependency error.

code

kotlin · 5 lines
kotlin
// Inspect the fully resolved graph before execution starts
gradle.taskGraph.whenReady {
    println("Will run ${allTasks.size} tasks")
    if (hasTask(":test")) println("test is in the plan")
}

go deeper

for a junior

Know that requested tasks pull in their dependencies and run in dependency order.

for a middle

Explain transitive closure, DAG, topological sort, and that the graph is built before execution.

for a senior

Distinguish dependency edges from ordering-only edges; explain whenReady timing and how this enables parallelism.

for a principal

Reason about graph determinism, reproducibility, and how task-graph inspection feeds CI gating or partial-build strategies.

## From request to execution plan Gradle builds an **execution plan** in two conceptual steps: 1. **Selection**: parse the command line, map task name arguments to actual `Task` instances (across the relevant projects), giving the *requested* tasks. 2. **Closure**: starting from those, transitively follow every dependency edge to collect the full set of tasks that must run — the **task graph**. ## What creates edges - **Explicit** `dependsOn` declarations. - **Inferred** dependencies: when a task's input `Provider` is wired to another task's output, Gradle adds the producing task automatically. - **Finalizers** (`finalizedBy`) and **ordering constraints** (`mustRunAfter`/`shouldRunAfter`) refine *order* among already-selected tasks. ## The graph must be a DAG A valid task graph is a **directed acyclic graph**: directed (A depends on B), acyclic (no task transitively depends on itself). Gradle computes a **topological ordering** — a linear order respecting all edges. Unconstrained tasks can be ordered freely; that freedom is what enables parallel execution. ## Timing The full graph is resolved at the **end of configuration, before execution**. This is why hooks like `gradle.taskGraph.whenReady { graph -> ... }` and `graph.hasTask(...)` see the complete plan. It also means a missing or cyclic edge is detected up front, not midway through a long build. ## Quick model ``` requested: [test] test -> testClasses -> compileTestJava, processTestResources testClasses also needs classes -> compileJava, processResources resolved order (one valid topo sort): compileJava, processResources, classes, compileTestJava, processTestResources, testClasses, test ```

  • At what point is the complete task graph known?
    At the end of the configuration phase, before any task executes. That is when taskGraph.whenReady fires with the full plan.
  • Why can the relative order of two unrelated tasks vary between runs?
    Because a topological sort only constrains tasks linked by edges; unrelated tasks are free to be ordered (or parallelized) however Gradle's scheduler chooses.

saying these in an interview costs you the question

  • Saying Gradle decides order purely by declaration order in the build script
  • Claiming the graph is built incrementally during execution rather than up front
  • Confusing the task graph with the dependency-management (artifact) graph

context