How does Terraform handle dependencies — implicit vs explicit?
Quick Answer
Terraform builds a dependency graph automatically from resource attribute references (implicit dependencies) and supports depends_on for cases where dependencies exist but are not expressed through attribute references (explicit dependencies). The graph determines the order of create, update, and destroy operations.
Detailed Answer
Terraform's dependency management is one of its most powerful features, and understanding it deeply is key to writing correct infrastructure code. Think of dependencies like a construction project: you cannot install electrical wiring (resource B) until the walls are framed (resource A). Terraform figures out this ordering automatically by analyzing which resources reference attributes of other resources.
Implicit dependencies are the primary and preferred mechanism. When you write aws_instance.payments_api.subnet_id = aws_subnet.private_payments.id, Terraform analyzes this reference and understands that the subnet must exist before the instance can be created. It adds an edge in the dependency graph from aws_subnet.private_payments to aws_instance.payments_api. During apply, Terraform walks this directed acyclic graph (DAG) in topological order, creating resources with no dependencies first and working its way up. For destroy operations, Terraform reverses the graph — it destroys the instance before the subnet, because the instance depends on the subnet.
Internally, Terraform's graph builder collects all resource and data source blocks, resolves all interpolation references, and constructs a DAG where nodes are resources and edges are dependencies. It then validates the graph is acyclic — if you accidentally create a circular dependency (A references B, B references A), Terraform detects this and fails with an error showing the cycle. The graph also determines parallelism: resources at the same depth in the graph with no dependencies between them can be created simultaneously.
Explicit dependencies using depends_on are necessary when a dependency exists but is not visible through attribute references. The classic example is an IAM role and an EC2 instance that assumes that role. If the instance references the role ARN directly, the implicit dependency works. But if the instance uses an instance profile that was created elsewhere, and the role's permissions are needed at boot time for a user_data script, Terraform might try to launch the instance before the IAM role's policy attachments are complete. AWS IAM has eventual consistency — a policy attachment might take seconds to propagate. Adding depends_on = [aws_iam_role_policy_attachment.payments_api_s3_access] forces Terraform to wait.
However, depends_on should be used sparingly because it has a hidden cost. When you add depends_on to a resource, Terraform treats it as a more opaque dependency — during plan, it may not be able to determine the full set of changes, which can lead to unnecessary replacements or overly conservative planning. It also makes your code harder to understand because the dependency reason is not self-documenting like an attribute reference.
Another important dependency concept is resource ordering during updates. If you change a security group rule that an RDS instance references, Terraform understands that the security group must be updated before or after the RDS instance depending on the nature of the change. Terraform's graph handles this automatically for most cases, but understanding the graph helps you predict apply behavior and debug issues when operations fail mid-apply.