The Dependency Graph
Underneath every plan and apply, Terraform builds a directed acyclic graph of resources and walks it. This is not trivia. The graph is why Terraform runs independent resources in parallel, why a dependency loop is a hard error rather than a warning, and why resources get created in an order you never wrote down. When an apply does something in an order that confuses you, the graph is the answer.
You can mostly ignore it until the day you can't — a surprising apply order, a cycle error, an apply that hangs on teardown. On that day, knowing the graph exists and how to print it turns a baffling problem into a five-minute diagnosis.
What the Graph Contains
The graph has a node for every resource, data source, provider, input variable, and output. The edges are the dependencies — every reference you write becomes an edge, and so does every depends_on. A provider node is a parent of every resource that uses it, because the provider must be configured before any of its resources can be created. The graph is the complete, machine-readable picture of what depends on what, assembled from your configuration before any API is called.
Parallelism
Terraform walks the graph concurrently. Any two nodes with no path between them are independent and can be processed at the same time, which is why a config with twenty unrelated S3 buckets applies far faster than twenty sequential creates would. The default concurrency is 10 operations at once, tunable with -parallelism=N. This parallelism is the practical reason implicit dependencies matter so much — every unnecessary depends_on you add removes an opportunity for Terraform to do work in parallel.
Cycles
The graph must be acyclic — the "A" in DAG. If two resources reference each other, directly or through a chain, Terraform cannot decide which to build first and fails with a cycle error that names the resources in the loop. Cycles usually sneak in through module wiring: a module's output feeds another module whose output feeds back. The error output below names the two edges, which is exactly what you need to break the loop.
# Error: Cycle: aws_security_group.a, aws_security_group.b # sg.a references sg.b in an ingress rule, and # sg.b references sg.a in its own ingress rule — # neither can be created first.
The fix is never to force an order with depends_on — that cannot resolve a true cycle. You break the loop by restructuring: pull the mutual reference into a separate resource. For the two security groups above, the answer is a standalone aws_security_group_rule (or aws_vpc_security_group_ingress_rule) that references both groups, so each group no longer references the other.
Inspecting the Graph
terraform graph emits the graph in DOT format, which you pipe into Graphviz to render a diagram. On a small config it is overkill; on a large one with a confusing apply order, seeing the actual edges is faster than reasoning about them. The command below renders the graph to an SVG you can open and trace.
# emit DOT and render with Graphviz terraform graph | dot -Tsvg > graph.svg
Graph and Destroy
Destruction reverses every edge in the graph. A resource that depended on another during creation is destroyed before it, so the dependency is satisfied in reverse. This is why a wrong dependency does not only break creation — it can make teardown fail or hang, because Terraform tries to delete something that still has a dependent the reversed graph did not account for. Getting the edges right, mostly through references, is what makes both directions of the graph walk safely.
- Creating a dependency cycle — two resources each referencing the other — and not understanding the error until you map the two edges by hand.
- Cranking
-parallelismvery high to speed up an apply and hitting AWS API rate limits, which makes applies slower and flakier, not faster. - Assuming resources apply top-to-bottom in file order — order is derived from the graph, and file position is irrelevant.
- Introducing a hidden cycle through a module's input and output wiring that stays invisible until the apply fails.
- Trying to break a real cycle with
depends_on, which cannot resolve it — only restructuring the references can.
- Use
terraform graphpiped to Graphviz when an apply order is surprising or a cycle appears, instead of reasoning about edges by hand. - Break cycles by introducing an intermediate resource or moving an argument out — for mutual security-group references, a standalone rule resource.
- Leave
-parallelismat its default of 10 unless you have a measured reason; lower it when you hit API throttling. - Design module interfaces so data flows in one direction, which keeps accidental cycles from forming.
- Express ordering through references rather than
depends_on, so the graph stays maximally parallel.
Knowledge Check
Why can Terraform create some resources at the same time?
- Resources with no path between them in the graph are independent and walked concurrently
- Terraform creates absolutely everything in parallel at once and then reconciles any conflicts afterward
- Resources declared in the same
.tffile are batched and applied together as a group - The AWS provider itself decides the parallelism, scheduling the work rather than Terraform
What is a cycle in the dependency graph, and what does Terraform do about it?
- A dependency loop with no valid order; Terraform fails with an error naming the resources in the loop
- A non-fatal warning that the apply will simply run slower than usual but will still complete successfully in the end
- A transient signal to retry the apply, which usually resolves itself on the second attempt
- A normal state that Terraform breaks automatically by picking an arbitrary creation order
If not file position, what determines the order resources are created in?
- The dependency graph, derived from references and
depends_on - The strict top-to-bottom order in which the blocks are written within each
.tffile - The plain alphabetical order of each resource's full address
- The chronological order in which resources were first added to version control
What is the risk of raising -parallelism well above its default?
- Hitting cloud API rate limits, which makes applies slower and flakier rather than faster
- Corrupting the state file because concurrent parallel writes overlap and clobber one another
- Causing Terraform to ignore its dependency edges and apply resources in the wrong order
- Forcing every resource in the graph to be replaced instead of updated in place
You got correct