When you run `gradle test`, how does Gradle decide the complete set and order of tasks it executes?
answer
- requested tasks -> transitive closure
- DAG = directed acyclic graph
- topological sort
- edges: dependsOn / inferred / finalizers / ordering
- graph built before execution
basics
~10 sGradle 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 sFrom 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// 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
Know that requested tasks pull in their dependencies and run in dependency order.
Explain transitive closure, DAG, topological sort, and that the graph is built before execution.
Distinguish dependency edges from ordering-only edges; explain whenReady timing and how this enables parallelism.
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