Terraform’s surface area grows with every release, but most teams keep using the same handful of commands they learned years ago. The six features below have been stable for at least a year and a half, they come up on the Terraform Associate 003 exam, and they solve real problems that engineers typically handle with fragile workarounds. Let’s go through them one by one.

1. moved blocks — Rename resources without destroy/recreate

Before Terraform 1.1, renaming a resource in your configuration meant deleting the old address from state and re-importing under the new name — or accepting a destroy/recreate cycle that could cause downtime. moved blocks solve this declaratively.

Suppose you initially wrote:

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
}

You later want to rename it to app_server to align with your naming convention. Instead of touching state manually, add a moved block:

moved {
  from = aws_instance.web
  to   = aws_instance.app_server
}

resource "aws_instance" "app_server" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
}

Run terraform plan and Terraform records the rename in state with no destroy, no recreate. Once the rename is live and the team has pulled the change, you can delete the moved block in a follow-up commit. moved blocks also work when refactoring a resource into a module, or when moving a resource between modules.

2. import blocks (Terraform 1.5+) — Declarative infrastructure import

The old workflow was: create the resource in config, run terraform import <address> <id> on the CLI, then reconcile any drift by hand. Terraform 1.5 introduced import blocks that let you express the import in HCL itself, version-control it, and even auto-generate the resource configuration with terraform plan -generate-config-out=generated.tf.

# import.tf
import {
  to = aws_s3_bucket.legacy_assets
  id = "my-company-legacy-assets-bucket"
}

resource "aws_s3_bucket" "legacy_assets" {
  bucket = "my-company-legacy-assets-bucket"
}

Run terraform plan: Terraform shows an import operation instead of a create. Run terraform apply and the bucket is now tracked in state. The import block is then safe to remove. Because the import is in source control, your whole team can see why that resource exists in state and when it was onboarded.

3. check blocks (Terraform 1.5+) — Post-apply assertions

check blocks let you define assertions that run after apply completes. They don’t block the apply or mark the plan as failed — they emit warnings — but they are ideal for smoke-testing that your infrastructure is actually healthy, not just provisioned.

check "api_health" {
  data "http" "endpoint" {
    url = "https://${aws_lb.api.dns_name}/health"
  }

  assert {
    condition     = data.http.endpoint.status_code == 200
    error_message = "API health endpoint returned ${data.http.endpoint.status_code}, expected 200."
  }
}

This is particularly useful in CI/CD pipelines: you can detect a bad deploy (the load balancer provisioned but the app is still unhealthy) within the same terraform apply run, rather than waiting for a separate test stage. Combine it with a depends_on if you need the check to run after a specific resource.

4. terraform console — Interactive expression REPL

How often do you write a cidrsubnet call, run apply, and only then discover the subnet CIDR is wrong? terraform console gives you an interactive shell to evaluate any Terraform expression against your current state and variable values, before you commit anything.

$ terraform console
> cidrsubnet("10.0.0.0/16", 8, 1)
"10.0.1.0/24"

> cidrsubnet("10.0.0.0/16", 8, 5)
"10.0.5.0/24"

> toset(["a", "b", "a", "c"])
toset([
  "a",
  "b",
  "c",
])

> formatdate("YYYY-MM-DD", "2026-04-25T08:00:00Z")
"2026-04-25"

You can also reference actual state values: aws_instance.app_server.id will return the real resource ID if you have a state file loaded. This is the fastest way to debug complex for expressions, templatefile calls, or any expression you’re uncertain about. Press Ctrl+D to exit.

5. -replace flag — Targeted resource recreation without taint

terraform taint has been deprecated since Terraform 0.15.2. The replacement is the -replace flag on terraform plan and terraform apply. It is safer because it is not a persistent state mutation: you declare the intent at the moment you run the command, rather than marking the resource in state and forgetting to clean it up.

# Old way (deprecated)
terraform taint aws_instance.app_server
terraform apply

# New way
terraform apply -replace="aws_instance.app_server"

You can target multiple resources in one command:

terraform apply \
  -replace="aws_instance.app_server" \
  -replace="aws_eip.app_server_ip"

Terraform will show a plan with -/+ (destroy-then-create) for those specific resources and leave everything else untouched. This is the correct answer on the Terraform Associate exam whenever a question asks about recreating a specific resource without destroying the entire workspace.

6. precondition and postcondition blocks — In-resource validation

Variable validation blocks have been around for a while, but most engineers don’t know that resources and data sources also support lifecycle conditions. precondition checks fire before the resource is created or updated; postcondition checks fire after. Both produce clear error messages that explain the business rule being violated.

resource "aws_instance" "app_server" {
  ami           = var.ami_id
  instance_type = var.instance_type

  lifecycle {
    precondition {
      condition     = contains(["t3.micro", "t3.small", "t3.medium"], var.instance_type)
      error_message = "instance_type must be t3.micro, t3.small, or t3.medium for cost compliance."
    }

    postcondition {
      condition     = self.public_ip == null
      error_message = "App server must not have a public IP. Check your subnet configuration."
    }
  }
}

The postcondition block has access to self, meaning it can inspect the actual attributes of the resource after Terraform reads them back from the provider. This is far more powerful than a variable validator, which only sees the raw input value before any API call is made.

Why these matter for the Terraform Associate 003 exam

HashiCorp refreshed the Terraform Associate exam (003) to include more questions on features introduced in Terraform 1.x. Based on the official exam guide, you should expect questions on:

Knowing that import blocks and check blocks exist is table-stakes for the 003 version of the exam. Earlier study guides written for the 002 version do not cover them, so candidates who rely on old material often get caught out.

Key takeaways

None of these features require a plugin or a paid Terraform Cloud tier. They are all part of open-source Terraform and available from version 1.1 or 1.5 onwards. If your team is still on an older version, this list is a solid argument for upgrading.