35. Removing an item from a count list
You remove "b" from the list below. terraform plan wants to destroy and recreate buckets you did not touch. Why, and what is the fix?
variable "names" { default = ["a", "b", "c"] }
resource "aws_s3_bucket" "this" {
count = length(var.names)
bucket = "acme-${var.names[count.index]}"
}- A.The AWS provider is outdated; upgrading it will stop the replacement.
- B.S3 bucket names cannot be changed, so Terraform always replaces every bucket.
- C.count tracks resources by index, so "c" moves from index 2 to 1 and is seen as a change; use for_each over a set or map so each bucket is keyed by its name, and use moved blocks to migrate.
- D.The state file is corrupted and must be deleted.
Show answer
Answer: C. count tracks resources by index, so "c" moves from index 2 to 1 and is seen as a change; use for_each over a set or map so each bucket is keyed by its name, and use moved blocks to migrate.
With count, resources are addressed as this[0], this[1], this[2]. Removing a middle item shifts later items to new indexes, so Terraform thinks this[1] must change from b to c and this[2] must be destroyed. for_each keys resources by a stable value such as the name, and moved blocks let you change addresses without recreating real infrastructure.
36. One huge Terraform state
One Terraform root module manages networking, databases, Kubernetes and 40 app stacks. Plans take 25 minutes and a typo in one app once touched the VPC. What is the best direction?
- A.Keep the single state but run every plan and apply with -parallelism=1 so changes go one at a time.
- B.Split into smaller root modules by layer and lifecycle, each with its own state, linked by outputs.
- C.Wrap everything in one large shared module, so the code is reused and planned in one place.
- D.Use -target on every apply so only the changed resources are planned and applied.
Show answer
Answer: B. Split into smaller root modules by layer and lifecycle, each with its own state, linked by outputs.
Separate states shrink the blast radius, speed up plans and let teams own and lock their own stacks. Rarely changed foundations such as the network live apart from fast-moving app stacks. The trade-off is managing dependencies between stacks, often via remote state outputs, data sources or tools such as Terragrunt or Terraform stacks. -target is an escape hatch, not a workflow.
37. Forcing one resource to be rebuilt
One VM is broken inside, though its config is fine. You want Terraform to destroy and recreate just it, with the change visible in a reviewed plan. What do you use?
- A.terraform apply -target=aws_instance.web -refresh=false
- B.terraform plan -replace=aws_instance.web
- C.terraform state rm aws_instance.web, then apply
- D.terraform taint aws_instance.web, then apply later
Show answer
Answer: B. terraform plan -replace=aws_instance.web
-replace marks the resource for replacement inside a single plan, so reviewers see it and nothing changes until apply. taint is deprecated because it changes state immediately, which can surprise teammates. state rm would make Terraform create a second VM and leave the old one orphaned.
38. Rebuilding when a related thing changes
An instance must be replaced whenever the launch script in aws_s3_object.bootstrap changes, even though the instance does not reference its content. Which feature fits?
- A.lifecycle { replace_triggered_by = [aws_s3_object.bootstrap] }
- B.depends_on = [aws_s3_object.bootstrap] on the instance
- C.lifecycle { ignore_changes = [aws_s3_object.bootstrap] }
- D.lifecycle { create_before_destroy = true } on the instance
Show answer
Answer: A. lifecycle { replace_triggered_by = [aws_s3_object.bootstrap] }
replace_triggered_by replaces the resource when any listed resource or attribute changes. depends_on only orders creation and does not force replacement. To trigger on a plain value, wrap it in a terraform_data resource and reference that.
39. for_each over a list with repeats
var.buckets is ["logs", "media", "logs"]. A resource uses for_each = toset(var.buckets). How many buckets will Terraform plan?
- A.1
- B.None, because for_each fails on duplicates
- C.2
- D.3
Show answer
Answer: C. 2
toset removes duplicates, so the set is {"logs", "media"} and two instances are planned with keys logs and media. for_each needs a set or map, which is why lists are wrapped in toset. A silent de-duplication can hide a typo, so validate inputs if repeats are a mistake.
40. depends_on on a whole module
After adding depends_on = [module.network] to module.app, most data sources inside module.app show "known after apply" and many resources now plan an update every time. Why?
- A.Terraform re-imports every resource inside the module
- B.Modules with depends_on are always replaced on each apply
- C.depends_on on modules disables state locking
- D.Its data sources now wait until module.network is applied
Show answer
Answer: D. Its data sources now wait until module.network is applied
With module-level depends_on, any pending change in module.network makes Terraform defer reading data sources in module.app to apply time. Their results become unknown, which ripples into planned updates. Prefer passing specific outputs as inputs so the graph has precise edges.
41. Sharing VPC IDs between stacks
The app stack reads the network stack with data "terraform_remote_state". Security says the app team must not read the network state. What is a good alternative?
- A.Publish the IDs to SSM Parameter Store and read them there
- B.Copy the network state file into the app repo each release
- C.Give the app team read access to the whole state bucket
- D.Merge both stacks into one state so no sharing is needed
Show answer
Answer: A. Publish the IDs to SSM Parameter Store and read them there
terraform_remote_state needs read access to the full state, which may contain secrets. Publishing just the needed values to a store like SSM, Consul or a tagged lookup, and reading them with ordinary data sources, limits access and decouples the stacks. Merging stacks undoes the reason they were split.
42. Unit testing a module cheaply
You want fast tests for a naming module in CI without creating real cloud resources. Which built-in feature fits best?
- A.terraform validate with a -test flag
- B.terraform console scripted with expect
- C.terraform test with command = plan and mock providers
- D.terraform apply in a sandbox account on every commit
Show answer
Answer: C. terraform test with command = plan and mock providers
terraform test runs .tftest.hcl files with run blocks and assertions. command = plan avoids creating resources, and mock_provider (1.7+) returns fake values so no credentials are needed. Real apply tests are still useful for integration, but are slower and cost money.
43. Splat on a for_each resource
aws_instance.web uses for_each over a map. An output uses aws_instance.web[*].id and fails. What should it use?
- A.aws_instance.web[0].id
- B.aws_instance.web.id
- C.values(aws_instance.web)[*].id
- D.aws_instance.web.*.id, the older splat form
Show answer
Answer: C. values(aws_instance.web)[*].id
With for_each the resource is a map of objects, and the [*] splat works on lists (or single values), not maps. Use a for expression or values() to get a list first. With count the resource is a list, so web[*].id works there.
44. Designing state boundaries
You are designing Terraform for network, databases and 12 app services across dev and prod. Which layout best limits blast radius?
- A.Separate states per env and layer, with separate credentials
- B.One state for everything so dependencies are always correct
- C.One state per developer so nobody blocks each other
- D.One state per resource type, shared by dev and prod
Show answer
Answer: A. Separate states per env and layer, with separate credentials
Splitting by environment and by layer (network, data, apps) means a bad change to an app cannot touch the prod VPC, and plans stay small and fast. Separate credentials per environment stop a dev pipeline from changing prod. Layers share values through outputs or a parameter store.
45. Plan after a provider major upgrade
After moving to a new major provider version, plan shows several resources to be replaced though no code changed. What is the right response?
- A.Apply quickly, because replacements after upgrades are always harmless
- B.Delete the lock file so the old version comes back
- C.Stop, read the upgrade guide, and adjust code or use moved blocks before applying
- D.Run terraform state rm on the affected resources
Show answer
Answer: C. Stop, read the upgrade guide, and adjust code or use moved blocks before applying
Major versions can rename resources, change defaults or split resources, which Terraform reads as replacements. The upgrade guide explains the needed code changes, and moved blocks or state migrations often avoid recreation. Never accept replacements of stateful resources without understanding them.
46. Bake an image or configure at boot
Auto scaling instances take 9 minutes to become healthy because Ansible installs everything at boot. Scale-out is too slow for traffic spikes. What is the best long-term fix?
- A.Keep more instances running so scale-out never happens
- B.Run Ansible with more forks on each new instance
- C.Raise the health check grace period to 15 minutes
- D.Bake a golden image with Packer and Ansible ahead of time
Show answer
Answer: D. Bake a golden image with Packer and Ansible ahead of time
Building a golden image ahead of time moves the slow install out of the scale-out path, so new instances only fetch config and secrets at boot. It also makes every instance identical. A longer grace period hides the problem, and extra idle capacity costs money every hour.
47. Looking up a resource you just created
A config creates aws_vpc.main and, in the same config, uses data "aws_vpc" filtered by its Name tag to get the ID. The first apply fails with "no matching VPC found". What is the fix?
- A.Move the data block above the resource in the file
- B.Use aws_vpc.main.id directly instead of a data lookup
- C.Run the first apply twice and ignore the error
- D.Add a sleep in a local-exec provisioner before the lookup
Show answer
Answer: B. Use aws_vpc.main.id directly instead of a data lookup
Data sources are read during plan when they can be, and at that point the VPC does not exist yet. Inside one configuration, refer to the resource attributes directly, which also gives Terraform the right dependency. Data sources are for objects managed elsewhere.
48. Continuous reconcile vs plan and apply
With Crossplane, an engineer edits a managed database in the console during an incident, and minutes later the change is undone. How does this compare with Terraform?
- A.Crossplane reconciles nonstop; Terraform waits for an apply
- B.Both tools ignore console changes by default
- C.Crossplane never touches resources after creating them
- D.Terraform reverts console changes within minutes too
Show answer
Answer: A. Crossplane reconciles nonstop; Terraform waits for an apply
Controller-based tools keep comparing desired and actual state, so drift is fixed automatically, which is great for consistency but surprising during incidents. Terraform changes things only when someone applies. Teams using reconcilers need a documented way to pause reconciliation for emergencies.
49. Two tools managing one resource
A load balancer listener is defined in Terraform, and a Kubernetes controller also edits its rules. Every apply and every controller sync undo each other. What is the right principle?
- A.Disable state locking so both tools can write
- B.Copy the controller rules into Terraform daily
- C.Give each field one owner and ignore it elsewhere
- D.Run Terraform more often so that it always wins
Show answer
Answer: C. Give each field one owner and ignore it elsewhere
Two tools writing the same fields cause endless churn and outages. Decide which system owns each resource or field; if the controller owns rules, let Terraform create only the base listener and use ignore_changes for the rest, or hand the whole listener to the controller. Write the ownership down so others do not reintroduce the fight.
50. for_each keys unknown until apply
Terraform fails with the error below. What is the right fix?
Error: Invalid for_each argument
on main.tf line 12, in resource "aws_route53_record" "this":
for_each = toset(aws_instance.app[*].id)
The "for_each" set includes values derived from resource attributes
that cannot be determined until apply.
- A.Upgrade the AWS provider to the latest major version and plan again
- B.Run terraform init -reconfigure so the backend reloads its state
- C.Key for_each by values known at plan time, like static names
- D.Replace for_each with a depends_on that points at the instances
Show answer
Answer: C. Key for_each by values known at plan time, like static names
for_each keys become resource addresses, so Terraform must know them during plan. Instance IDs only exist after apply, so they cannot be keys. Build the map from static input such as names and use the IDs as values instead; -target applies are only a last-resort workaround.
51. A console fix that gets undone
During an incident, someone opened a port in a security group via the console. The next terraform apply silently closes it again. What is the right practice?
- A.Stop using Terraform for security groups
- B.Never run terraform apply again
- C.Make the change in code, or import it, then apply
- D.Make every console change twice
Show answer
Answer: C. Make the change in code, or import it, then apply
Terraform makes real resources match the code, so manual changes are drift and get reverted. After an emergency change, put it in code (or import it) and review it. Run regular plans to detect drift early.