Desired State and the Dependency Graph
Scenario: Three resource blocks are typed in the "wrong" order in the file, yet Terraform still builds them correctly - and even creates two unrelated resources at the same time to save minutes.
How does Terraform know what order to create things in, if not from the order you typed them? The answer is a dependency graph: internally, Terraform represents every resource as a node, and draws an edge between two nodes whenever one resource's configuration references another resource's attribute - for example, network_id = fakecloud_network.app.id tells Terraform "the server depends on the network, because it needs the network's real ID before it can be created". This produces a directed acyclic graph (DAG) - directed because edges point one way (server depends on network, not the other way), acyclic because a dependency loop would make no sense (a graph engineer would call it undecidable which one to build first).
Terraform walks this graph, creating every resource whose dependencies are already satisfied, and it does this concurrently wherever possible: two resources with no relationship to each other build in parallel, which is why applies are often faster than the resource count alone would suggest. When you destroy infrastructure, Terraform walks the same graph in reverse - dependents are destroyed before what they depend on, so you never try to delete a network while a server still references it.
One notation you will see constantly in plan output is (known after apply). It means: this value genuinely does not exist yet - it's typically an ID or address the provider generates only once the real object is created - so Terraform shows a placeholder and defers the real value until apply time. This is a normal, expected part of planning, not an error.
Analogy: A spreadsheet recalculates cell B2 whenever cell A1 changes because B2's formula references A1 - it does not matter that B2 happens to sit above A1 on the sheet. Terraform resolves resources by the same kind of reference tracking, completely independent of where each block physically sits in the file.
A worked configuration
resource "fakecloud_server" "api" {
name = "api"
network_id = fakecloud_network.app.id
}
output "server_id" {
value = fakecloud_server.api.id
}
Because the graph is built from real references, graph quality is a genuine production concern, not just a performance detail. Clear, direct attribute references (fakecloud_network.app.id rather than a copy-pasted string) give you correct create/destroy ordering automatically, let terraform graph produce an accurate architecture diagram, and keep the blast radius of any one change small and visible. Hidden dependencies - relationships Terraform cannot see because they're not expressed as a reference - are one of the most annoying categories of automation bug: an apply fails intermittently because object B was created before object A finished propagating, then a retry succeeds because A happened to be ready by then. It looks flaky, but the real cause is a missing edge in the graph.
Common mistakes in production
- Copy-pasting an ID string as a literal value instead of writing the real attribute reference - this looks identical on day one but severs the dependency edge, so Terraform no longer knows to wait for or recreate the dependent object.
- Assuming file order controls execution order and reordering blocks to "fix" a build problem, when the real fix is almost always adding or correcting a reference.
- Panicking at
(known after apply)in a plan - it is expected for computed attributes and does not mean something is broken.
Goal: Treat the plan as a change contract: understand every create, update, replacement, and destroy before approving it. Reinforce this lesson in the tf-dependency-ordering Terraform lab. Open/labs/terraformand choose slugtf-dependency-ordering; the lab runs real Terraform against the offline FakeCloud provider.