When Terraform destroys a set of resources, what order does it use, and how does it work that order out?
answer
- same graph, edges inverted
- dependents die before their dependency
- subnets before the VPC
- state remembers what depended on what
- hardcoded id means no edge at all
basics
~20 sTerraform destroys in reverse dependency order, walking the same graph with its edges inverted: nothing is deleted until everything that depends on it is gone. Subnets go before the VPC, because the subnet referenced the VPC's id.
solid answer
~40 sDestroy uses the same dependency graph as create, walked backwards. If `aws_subnet.app` references `aws_vpc.main.id`, the create edge says subnet-after-VPC, so the destroy edge says VPC-after-subnet — Terraform deletes the subnet first and only then the VPC. Every edge is reversed the same way, including ones you added with `depends_on`. The important consequence is that destroy ordering is only as good as the edges you declared. If someone hardcoded `subnet_id = "subnet-0a1b..."` instead of referencing the resource, Terraform sees no relationship, may try to delete the subnet while the instance still lives in it, and AWS rejects the call with `DependencyViolation`. That is also why state records each resource's dependencies: after you delete a block from the configuration, the recorded edges are the only thing left to order its removal.
code
hcl · 21 linesresource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "app" {
vpc_id = aws_vpc.main.id # create: after vpc / destroy: before vpc
cidr_block = "10.0.1.0/24"
}
resource "aws_instance" "app" {
ami = var.ami_id
instance_type = "t3.micro"
# Hardcoded: Terraform sees no link to aws_subnet.app, so on destroy
# it may delete the subnet while this instance is still in it.
subnet_id = "subnet-0a1b2c3d4e5f67890"
}
variable "ami_id" {
type = string
}go deeper
Know the rule: Terraform tears things down in reverse order of creation, so the subnet goes before the VPC. Be able to say that the order comes from the same references, not from the file.
Explain that destroy walks the same graph with edges inverted, that depends_on reverses too, and that a resource is only removed once everything depending on it is gone. Give the VPC and subnet example.
Diagnose a half-completed teardown: identify a missing edge from a hardcoded id or an out-of-band attachment as the cause of DependencyViolation, and know that state carries the dependency records that order the removal of blocks already deleted from the configuration.
Treat destroyability as a property you verify, not assume. Environments that are only ever built accumulate undeclared edges; scheduling real teardowns of ephemeral environments is how the estate keeps proving its own dependency graph is honest.
## One graph, walked in two directions Terraform does not maintain a separate teardown plan. It builds the dependency graph from references exactly as it does for a create, and for destroy it inverts the edges. The rule that falls out is simple and worth being able to state in one line: **a resource is destroyed only after everything that depends on it has been destroyed.** So for: ```hcl resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" } resource "aws_subnet" "app" { vpc_id = aws_vpc.main.id cidr_block = "10.0.1.0/24" } ``` create order is VPC then subnet, and destroy order is subnet then VPC. This matches how the cloud provider behaves: AWS will not delete a VPC that still contains subnets, and refuses with an error rather than cascading. Terraform's reversed walk is what keeps you from ever making that call. Explicit edges reverse identically. `depends_on = [aws_iam_role_policy.app]` on an instance means the instance is created after the policy — and destroyed *before* it, so the instance is gone before its permissions are withdrawn. ## Why state stores dependencies The subtlety candidates miss is where the edges come from when the configuration no longer has them. If you delete a resource block entirely and run apply, there is no HCL left to read references from — yet Terraform still orders the removal correctly. It can do that because each resource instance in state carries a record of what it depended on when it was last applied. That recorded list is what orders a destroy of things the configuration has forgotten. This is one of the concrete reasons a stale or hand-edited state produces strange teardown behaviour: the ordering information lives there, not only in the code. ## The failure mode: an edge that was never declared Because ordering is derived entirely from declared relationships, a real-world dependency that was never expressed simply does not exist as far as Terraform is concerned. The usual way this happens is a hardcoded identifier: ```hcl resource "aws_instance" "app" { subnet_id = "subnet-0a1b2c3d4e5f67890" # no edge! } ``` On create this is often harmless — the subnet already exists, so nothing races. On destroy it bites: Terraform sees an instance and a subnet with no relationship, starts both deletions concurrently, and the provider returns `DependencyViolation` because the network interface is still attached. The apply half-completes, and you are left cleaning up by hand or re-running until it happens to succeed. The same shape appears with genuinely out-of-band relationships — something outside Terraform attached to a resource Terraform owns — which no reference can express. That is the legitimate case for `depends_on`, or for accepting that the teardown needs a manual step. ## Replacement is a create and a destroy in the same graph When a change forces a resource to be replaced, both operations appear in one walk, and by default the order is destroy-then-create for that resource. `create_before_destroy` inverts it, which also changes the ordering constraints on neighbouring nodes — Terraform requires the inversion to be consistent across the dependency chain, which is why turning it on in one place sometimes surfaces errors elsewhere. The point for this discussion is just that destroy ordering is not only a `terraform destroy` concern; it is live in every apply that replaces something. ## What to say in an interview Lead with the rule — reverse dependency order, same graph, inverted edges — give the VPC and subnet example because it is instantly recognisable, then show depth by naming the two things most people leave out: that state carries the dependency records so removed blocks still order correctly, and that a hardcoded id deletes an edge and turns a clean teardown into a `DependencyViolation`. That combination reads as someone who has actually torn down an environment rather than only built one.
- If you delete a resource block from the configuration entirely, how does Terraform know what order to destroy it in?From state. Each resource instance records the dependencies it had at the last apply, so even with no configuration left Terraform can order the removal correctly. It is one of the reasons hand-editing state is risky: you can strip the ordering information that a later destroy relies on.
- Why might a destroy fail with DependencyViolation even though the whole configuration is being torn down at once?Because Terraform only orders relationships it knows about. A hardcoded identifier, or an attachment made outside Terraform, leaves no edge, so it deletes concurrently and the provider rejects the call while the resource is still in use. The fix is to replace the hardcoded value with a real reference, or to declare the ordering explicitly.
- Does depends_on affect destroy ordering as well as create ordering?Yes. It is an ordinary edge in the same graph, so it is inverted for destroy exactly like an implicit reference: the resource that declares depends_on is created after its target and destroyed before it. That is usually what you want — an instance should be gone before the IAM policy that let it work is removed.
saying these in an interview costs you the question
- Terraform destroys in the order resources appear in state
- Destroy uses a completely separate teardown plan
- depends_on only matters when creating things
- Hardcoding an id is fine, the resource still exists
- A failed destroy means the provider is inconsistent