cswork / libraryOpen practice workspace

Articles

Terraform Dependency Interview Questions: Unknown Values, for_each, and Apply Order

Practice Terraform dependency interview questions with known for_each keys, unknown apply-time values, explicit depends_on, and safe plan review.

“Terraform knows it must create the network before the instances. Why does for_each still fail with an unknown value?” This interview question tests two concepts that are easy to blend together: dependency ordering and instance identity at plan time. Terraform can build a graph edge to defer a resource's operations, yet it must know the keys of a for_each collection before it can plan which resource instances exist. An extra depends_on cannot supply missing keys.

The platform behavior below follows HashiCorp's for_each reference and dependency graph documentation. The plans and interview questions are original teaching scenarios, not candidate reports or evidence that a named employer uses them. For adjacent practice, Discuss Terraform, Infrastructure as Code, and AWS Experience asks how you review a change under partial failure.

Question 1: What must Terraform know before for_each can plan?

The keys of the map or members of the set must be known before Terraform performs remote operations. Those keys become resource instance addresses such as aws_instance.worker["east"]. If Terraform cannot enumerate them during planning, it cannot decide how many instances to create, destroy, or update. The values of a map can sometimes remain unknown until apply, provided the keys are already known and expressions using those values are allowed. That distinction is the center of this interview.

Here is a hypothetical shape, not a ready-to-run provider example:

locals {
  workers = {
    east = { subnet_id = aws_subnet.east.id }
    west = { subnet_id = aws_subnet.west.id }
  }
}

resource "aws_instance" "worker" {
  for_each  = local.workers
  subnet_id = each.value.subnet_id
  # Other provider-required arguments omitted for clarity.
}

The labels east and west are configuration-known. The subnet IDs can be unknown until the provider creates them. Terraform can still assign stable addresses to the worker instances and infer edges from the references. If you instead construct the map with newly created subnet IDs as the keys, the addresses themselves depend on apply-time results and planning can fail. Do not overgeneralize “unknown values break for_each”; tell the interviewer which part of the collection is unknown.

for_each keys also cannot be sensitive values, because instance keys appear in resource addresses and output. A scheme that uses a password, token, or private identifier as an instance key is both unsupported and conceptually unsafe. Choose nonsecret stable labels and keep sensitive data in the values or an appropriate secret path.

Question 2: Does depends_on fix an unknown key error?

No. depends_on adds a dependency edge so Terraform waits for the declared dependency's actions before acting on the dependent object. It can be useful for a hidden behavioral dependency that no expression reference captures. It cannot make an apply-time provider-generated ID known during planning. In fact, HashiCorp warns that broad explicit dependencies can produce more conservative plans, making more values unknown than necessary.

Consider for_each = toset(aws_subnet.private[*].id). If the subnets do not exist yet, their IDs are provider-assigned after creation. Adding depends_on = [aws_subnet.private] to the consuming resource may tell Terraform when to act, but Terraform still has to name the consumer instances in the plan. The plan-time identity problem remains. It is not a race condition solved by waiting longer; it is a modeling problem.

The direct repair is usually to choose stable configuration-known keys. For example, use a map keyed by availability-zone labels and store the subnet IDs as values. Another legitimate approach is to split ownership into separate plans and pass a deliberately stable interface between them, but that adds workflow complexity and should not be the default reflex. -target can sometimes bootstrap a graph, yet routine dependence on targeting weakens the normal whole-configuration plan review. Explain the tradeoff rather than presenting targeting as a universal cure.

A worked graph and key table

The table below is an editorial inference from the for_each and graph contracts. It intentionally separates “can name the instance” from “can run the operation.” It is not a transcript of Terraform's internal scheduler.

Expression shapeInstance identity at planDependency and likely result
Map keys east, west; values are new subnet IDsKnownWorker instances can be planned; references add subnet edges
Set of new subnet IDsUnknownTerraform cannot enumerate worker addresses; plan can fail
Map keys east, west plus redundant depends_onKnownPlan possible, but extra edge may defer reads or widen unknowns
Map keys drawn from sensitive tokensRejected as instance keysLabels would expose sensitive data in addresses

When an interviewer shows a failed plan, first identify the exact for_each expression and inspect the type and collection shape. Which expressions produce keys? Which produce values? Which values depend on a resource attribute marked “known after apply”? Then propose a keying scheme that survives replacements. A provider ID is often a poor identity for a managed instance because replacing the upstream resource can change the ID and therefore the downstream address, causing churn unrelated to the intended logical object.

Question 3: How does Terraform decide apply order?

Terraform builds a dependency graph. Expression references normally create implicit edges: if an instance's subnet_id uses aws_subnet.east.id, Terraform knows the subnet must be handled first. Explicit depends_on creates an edge when a genuine relationship is not visible in an argument. The graph is walked in dependency order, with independent nodes potentially handled concurrently. Textual block order in .tf files does not define the deployment order.

Be precise about “before.” A graph edge orders operations in Terraform's run, but it does not guarantee that an external service is ready merely because its API returned. An infrastructure resource may report creation complete before an application can accept traffic. Provider waiters, health checks, and explicit readiness contracts matter. If the hidden dependency is “the role policy must propagate before an instance can start,” an explicit edge may ensure the API calls occur in order, but eventual consistency may still require a provider-specific readiness mechanism.

Graph ordering also changes during destroy and replacement. Terraform needs to preserve dependency safety while tearing down old objects and building new ones, but lifecycle settings and provider behavior can complicate the sequence. Do not say a single depends_on line guarantees a zero-downtime replacement. Instead, read the plan's replace actions and understand whether dependents use the old or new object, whether address changes cause churn, and whether the application can tolerate overlap.

Question 4: When is explicit depends_on justified?

Use it when the dependent object truly relies on a side effect not represented by an argument reference. A policy attachment, a service initialization step, or another hidden prerequisite can motivate an edge. Name the exact dependency and its failure mode. If you already refer to an upstream attribute, an extra edge usually adds no useful information and can make plans less precise.

HashiCorp's guidance favors expression references because they communicate which value is needed. A broad module-level depends_on may tell Terraform to wait for many unrelated changes, leading data sources or outputs to be deferred to apply and showing more (known after apply) values in the plan. That reduces the reviewer's ability to assess the diff. The better interview answer asks whether the prerequisite can be expressed through a specific output or resource reference.

Suppose a deployment needs a newly created security policy to exist before creating a worker. If the worker has a direct policy ID argument, reference it there. If the provider relationship is genuinely indirect, add a narrowly scoped explicit dependency and document why. Afterward, run a plan and check whether unrelated objects become unknown. The dependency is a hypothesis about operations, not a substitute for observing readiness.

Question 5: How do you debug a surprising plan safely?

Start with a reproducible plan on the same configuration revision, workspace, variable set, provider lockfile, and state snapshot. Read the diagnostic error and the expression that produced unknown keys. Inspect upstream attributes rather than assuming a provider ID is stable. Use terraform console or a minimal configuration to inspect collection construction when appropriate; do not paste secrets or full state into a public ticket.

Then change one modeling decision. Replace provider-generated key material with stable logical keys, rerun the plan, and compare resource addresses and actions. If the plan now shows destroy-create churn, inspect whether the address migration itself requires a moved block or state-safe transition. If only values remain unknown, ask whether the unknown attributes are acceptable for review and whether their downstream API calls are ordered by references. Verify in a disposable environment before applying to production.

If a previously clean plan becomes noisy after adding depends_on, remove the broad edge in a branch and compare. Confirm whether an implicit reference already captures the real relationship. If an external readiness race remains, model the readiness contract explicitly or use a provider resource designed for it. The investigation should produce a falsifiable reason for the edge, not “Terraform is flaky.”

Question 6: What would a strong answer sound like?

“I would inspect whether the collection's keys are known during plan. Terraform can plan worker["east"] while its subnet ID value is unknown; it cannot plan worker[<new-subnet-id>] before the provider returns that ID. A reference already orders the subnet before the worker. depends_on changes graph ordering, not the timing when a provider-generated key becomes known. I would key the map by stable labels, keep the IDs in values, re-plan, and review address changes before applying.”

That explanation separates a static plan requirement from execution order. It also avoids promising that a successful graph walk proves service readiness. To practice, draw the graph for two subnets and two workers, then ask a partner to change one subnet ID, one logical key, and one explicit dependency. Predict the plan in each case before running it.

Practice questionConnection to this scenario
Discuss Terraform, Infrastructure as Code, and AWS ExperienceExplain a plan, state boundary, and partial apply.
Design a Dependency-Aware CI/CD PipelineDistinguish graph ordering from task identity.
Design a CI/CD Task SchedulerExplain ready nodes and parallel execution.
Design a CI/CD pipeline with schedulerReason about explicit versus inferred edges.
Level-Ordered Dependency Build OrderCalculate task levels from a dependency graph.

Continue with Terraform interview questions for platform engineers, then revisit the for_each example and label every key, value, graph edge, and possible apply-time unknown.

Sources and further reading