skip to content

Terraform's documentation calls depends_on a last resort. What does adding one actually cost you compared with an ordinary attribute reference?

level: seniorimportance: should knowfreq 42%

answer

  1. reference is typed, depends_on is not
  2. tool must assume everything upstream matters
  3. more (known after apply) in the diff
  4. every edge is a serialisation point
  5. no comment means nobody dares remove it

basics

~20 s

An explicit edge is whole-object and untyped: Terraform cannot see which value matters, so it must plan conservatively, marking more attributes as "(known after apply)". It also serialises work that could have run concurrently and hides the real reason for the ordering.

solid answer

~50 s

An attribute reference tells Terraform two things — that there is an ordering *and* which single value flows across it. `depends_on` tells it only the first. Because the edge is whole-object, Terraform has to assume any change to the upstream resource might matter to the downstream one, so it plans more defensively and shows more attributes as `(known after apply)` than a reference would. On a big configuration that turns a readable diff into an unreviewable one. It also costs concurrency: every edge is a serialisation point in the graph walk, and edges you did not need lengthen the critical path. And it is static — you cannot compute or conditionally apply it — so it tends to be added once and never revisited. The last cost is human: the code no longer says why. A reference is self-documenting; a `depends_on` needs a comment, and usually does not have one.

go deeper

for a junior

Know that depends_on is not simply a tidier way of writing a reference, and that adding one where a reference already exists is redundant. Prefer referencing the value you actually need.

for a middle

Explain that a reference carries which value flows while depends_on carries only ordering, so Terraform plans more defensively and shows more attributes as (known after apply). Mention that each edge also serialises part of the graph walk.

for a senior

Weigh the cost against the failure it prevents, and show the review angle: a plan full of unknowns is a plan nobody actually reads. Be able to audit a repository for stale entries and to justify keeping the ones that encode a real provider constraint.

for a principal

Set the team standard: implicit references by default, explicit edges only with a comment naming the invisible constraint, and periodic pruning. Recognise that graph shape is a delivery-time and reviewability property of the estate, not a detail of one module.

## Two edges that look the same and are not Both an attribute reference and a `depends_on` entry create an edge in Terraform's graph, and both guarantee ordering. The difference is what else Terraform learns. When you write `subnet_id = aws_subnet.app.id`, Terraform records a *typed* dependency: this argument, of this resource, takes this attribute of that object. It can reason about the single value. If the subnet already exists unchanged, the value is known and the downstream plan is fully concrete. If the subnet is being created, exactly one attribute is unknown and everything else about the instance still plans normally. When you write `depends_on = [aws_subnet.app]`, Terraform records only "after". It has no idea which part of the subnet the instance cares about — for all it knows, the answer is "all of it". ## Cost one: conservative plans and unknown values Because the edge is untyped, Terraform must assume the worst. If the upstream object has changes pending, the downstream node's plan becomes less certain, and more of its attributes render as `(known after apply)` than would with a precise reference. The Terraform documentation makes exactly this point when it recommends `depends_on` as a last resort: it produces a more conservative plan than necessary because the tool is uncertain what will change. The practical consequence lands on review. A plan you can read is one where the interesting lines stand out; a plan where fifty attributes are `(known after apply)` because a `depends_on` sits upstream of half the graph is one that reviewers scroll past and approve on faith. You have not made the apply riskier so much as you have made the *review* worthless, which amounts to the same thing. ## Cost two: concurrency and the critical path Terraform walks the graph, starting any node whose predecessors have finished and running several at a time (ten concurrent operations by default). The graph's shape sets the floor on wall-clock time: independent resources fan out, dependent ones queue. Every edge you add is a queue. A `depends_on` on a module block is the loud version of this — every resource inside now waits for the whole upstream object, even the ones that had nothing to do with it — but the effect exists at resource level too. Ten resources that each `depends_on` the previous one apply in strict sequence, and if each takes ninety seconds you have built a fifteen-minute apply out of work that could have finished in ninety seconds. ## Cost three: it is static, and it rots `depends_on` accepts a fixed list of addresses. You cannot compute it, cannot make it conditional on a variable, cannot generate it. That rigidity is deliberate — the graph is built before expressions are evaluated — but it means the entry survives every refactor unchanged. When the resource it names is renamed, you get an error and fix it; when the *reason* for the dependency disappears, nothing tells you, and the edge stays for years. ## Cost four: the code stops explaining itself A reference documents itself on the line where the value is used. An explicit edge is a bare address at the bottom of a block, and six months later nobody knows whether it encodes a real provider constraint or a superstition left over from a bad afternoon. Anyone considering removing it has to test in production to find out, so nobody does. The mitigation is cheap and almost always skipped: a one-line comment saying what the invisible dependency is. ```hcl resource "aws_instance" "app" { # The boot script writes to S3; without the role policy attached # first, the instance comes up and immediately 403s. depends_on = [aws_iam_role_policy.app] } ``` ## When the cost is worth paying None of this makes `depends_on` wrong. A genuine hidden dependency — permissions that must exist before software boots, an API activation, an ordering the provider schema simply does not model — is worth a coarse edge and a conservative plan, because the alternative is an intermittent first-run failure. The discipline is only this: ask whether an attribute reference could express the same thing, prefer it when it can, and when it cannot, write down why.

  • How would you audit an existing repository for depends_on entries that are no longer needed?
    List them all, then classify each: if the block already references an attribute of the named object, the entry is pure redundancy and can go immediately. For the rest, check whether the constraint is still real — the provider may now model it, or the resource may have been restructured. Remove one at a time and let the plan and a from-scratch environment build prove it.
  • Does a depends_on change what Terraform stores in state?
    State does record each resource's dependencies, which is how Terraform can still order a destroy when the configuration is gone. But the cost being discussed is a planning and concurrency cost, not a storage one: the entry makes the recorded relationship coarser, so the destroy ordering derived from it is coarser too.

saying these in an interview costs you the question

  • depends_on is identical to a reference, just more explicit
  • Extra depends_on entries are harmless if the order is already right
  • It only affects apply, not the plan output
  • You can generate depends_on from a variable when needed
  • More edges make the apply safer

context