What happens when two tasks depend on each other, and how does Gradle report it?
answer
- cycle => no topological order
- fails at resolution, before execution
- 'Circular dependency between the following tasks'
- trace path with (*)
- break: invert / ordering-only / extract shared task
basics
~10 sGradle cannot order a cycle, so it fails the build with a 'Circular dependency between the following tasks' error listing the tasks in the cycle. It detects this during graph resolution, before executing anything.
solid answer
~50 sA cycle (task A depends on B and B transitively depends back on A) means no valid topological order exists, so Gradle **cannot** build an execution plan. During graph resolution — at the end of configuration, before execution — it detects the cycle and throws, printing a **`Circular dependency between the following tasks`** message that traces the path, e.g. `:a -> :b -> :a`. Because detection happens up front, no tasks in the cycle ever run. The fix is to break the cycle: remove or invert one `dependsOn` edge, replace a hard dependency with an *ordering-only* rule (`mustRunAfter`/`shouldRunAfter`) where appropriate, or restructure so the shared work lives in a third task both depend on. Inferred dependencies (one task consuming another's output Provider) can also create surprising cycles, so the trace path is the key debugging artifact.
code
kotlin · 5 lines// This wiring is a cycle -> build fails before execution
tasks.register("a") { dependsOn("b") }
tasks.register("b") { dependsOn("a") }
// Circular dependency between the following tasks:
// :a --- :b --- :a (*)go deeper
Know that mutual dependencies cause a build failure and Gradle names the tasks involved.
Explain why no topological order exists, that detection is up front, and the basic ways to break a cycle.
Diagnose transitive and inferred-dependency cycles from the trace; choose between inverting an edge vs. ordering-only vs. extraction.
Establish conventions/lints to prevent cyclic wiring across modules and review cross-project task graphs for loop risk.
## Why a cycle is fatal Gradle's execution plan is a **topological sort** of the task DAG. A topological order exists **only if the graph is acyclic**. If task `:a` depends on `:b` and `:b` (directly or transitively) depends back on `:a`, there is no linear order where each task comes after all of its dependencies — so Gradle gives up and fails the build. ## When and how it is reported Detection happens during **graph resolution**, at the end of configuration and **before any execution**. Gradle raises a build failure with a message of the form: ``` FAILURE: Build failed with an exception. * What went wrong: Circular dependency between the following tasks: :a \--- :b \--- :a (*) ``` The printed path is the cycle trace — follow the arrows to see exactly which edges close the loop. The `(*)` marks the task that re-appears, closing the cycle. ## Common causes - A pair of explicit `dependsOn` declarations that point both ways. - A **transitive** loop spanning several tasks (`a -> b -> c -> a`) — harder to spot. - **Inferred** dependencies: wiring task X's input to task Y's output while Y also (transitively) consumes X's output. - Cross-project task wiring that loops back. ## How to break it 1. **Remove or invert** one dependency edge — usually one direction is wrong. 2. **Downgrade to ordering-only**: if you only need *order*, not a build dependency, `mustRunAfter`/`shouldRunAfter` express order without forcing the other task into the graph, avoiding the hard cycle. 3. **Extract shared work** into a third task that both depend on, so neither depends on the other. Use `--dry-run` after the fix to confirm the graph now resolves and the order is what you expect.
- At what phase is a circular dependency detected?During task-graph resolution at the end of configuration, before any task executes — so no task in the cycle runs.
- How can mustRunAfter help resolve an accidental cycle?If you only need ordering rather than a hard build dependency, mustRunAfter imposes order without adding a dependency edge, so it cannot create a dependsOn cycle.
- How do you locate the offending edges?Read the cycle trace in the error; the arrow path and the (*) marker show exactly which tasks close the loop.
saying these in an interview costs you the question
- Saying Gradle runs the tasks in some 'best effort' order despite the cycle
- Claiming the cycle is only detected when the looping task executes
- Suggesting mustRunAfter always fixes any cycle (it only helps when a hard dependency wasn't actually needed)