Terraform exams care less about memorizing commands and more about whether you understand state management and the safety of changes.
Many questions map directly to real-world pitfalls: concurrent runs, missing backend configuration, breaking changes in modules, and misuse of -target to cut dependencies.
Associate-level questions test whether you can explain the roles of plan/apply/destroy and what a plan is based on (the .tf declarations, the current tfstate, and the diff against real infrastructure). The Pro exam adds team-operation scenarios such as reviews, concurrent runs, and CI integration.
Classic gotchas include believing plan produces side effects, or not knowing that -refresh-only can surface drift without changing configuration.
How plan/apply interact with remote state and locking
Developer CLI Remote Backend (S3/Azure/GCS) Provider API
| | |
| terraform plan | |
|-------------------------->| read: tfstate |
| |-------------------------->|
| |<--------------------------|
|<--------------------------| fetch the state the plan needs |
| | |
| terraform apply | |
|-------------------------->| acquire the lock (DynamoDB etc.) |
| |=== LOCK === |
| |-------------------------->|
| |<--------------------------|
|<--------------------------| write the state after applying |
| |--- UNLOCK --------------- |Minimal configuration example (observe plan diffs via outputs)
terraform {
required_version = ">= 1.0"
}
variable "project_name" {
type = string
default = "demo"
}
locals {
tag = "app-${var.project_name}"
}
output "tag_example" {
value = local.tag
sensitive = false
}
# Change var.project_name, compare the plan, and understand the diff before applyingState is not just a cache — it is the source of truth for the objects Terraform creates and manages. Manual edits are discouraged; the state subcommand is the safe way to manipulate it.
Associate questions cover the basics of state list/show/mv/rm and import. Pro questions lean into drift reconciliation, selective migration, and standardized procedures for team operations.
CLI command examples
# List the managed resources
terraform state list
# Details of a single resource (ID and attributes)
terraform state show aws_s3_bucket.logs
# Moving addresses as part of modularization
terraform state mv aws_s3_bucket.logs module.storage.aws_s3_bucket.logs
# Import an existing VPC (the ID is the real resource identifier)
terraform import aws_vpc.main vpc-0abc1234def567890
# Drift check only
terraform plan -refresh-onlyA remote backend provides a single source of truth and locking, and is a prerequisite for team operations. When using S3, it is easy to forget DynamoDB-based locking — and without it, concurrent applies cause destructive conflicts.
Exam questions routinely ask which backends support locking, how encryption and permission models differ, and which configuration traps are easy to fall into.
| Backend | Locking | Encryption at Rest | Auth / Permissions Model |
|---|---|---|---|
| S3 (+ DynamoDB) | Lockable via DynamoDB | SSE-S3/KMS | IAM roles / policies |
| azurerm (Blob) | Native locking | Storage default encryption | Azure AD + RBAC |
| gcs | Generations + locking | Server-side encryption | Service accounts + IAM |
| local | None | Optional | Local permissions |
S3 backend (with DynamoDB locking)
terraform {
backend "s3" {
bucket = "tfstate-prod"
key = "network/terraform.tfstate"
region = "ap-northeast-1"
dynamodb_table = "tfstate-lock"
encrypt = true
}
}
# Prepare an S3 bucket (versioning on) and a DynamoDB table (partition key: LockID) firstAssociate questions focus on module invocation and passing variables and outputs. Pro questions cover version pinning, for_each vs. count, input validation, and resisting the urge to over-generalize.
Key stability is critical for for_each. To avoid destroys, use immutable keys tied to the entity (such as a name).
Module invocation and input validation example
module "vpc" {
source = "git::https://example.com/terraform-modules.git//vpc?ref=v1.4.2"
name = var.project
cidr = var.cidr
azs = var.azs
}
variable "project" {
type = string
validation {
condition = length(var.project) <= 12
error_message = "project must be 12 characters or fewer."
}
}
variable "cidr" {
type = string
}
output "vpc_id" {
value = module.vpc.id
sensitive = false
}Lifecycle controls help avoid destructive changes, but overusing them adds complexity. Prefer implicit dependencies (via references) and reach for depends_on only when necessary.
Provisioners are a last resort. Prefer cloud-native configuration mechanisms (User Data, launch templates, etc.) or external tools instead.
Lifecycle and dependency example
resource "aws_launch_template" "app" {
name_prefix = "app-"
image_id = var.ami
instance_type = var.instance_type
lifecycle {
create_before_destroy = true
prevent_destroy = false
}
}
resource "aws_autoscaling_group" "asg" {
name = "app-asg"
desired_capacity = 3
max_size = 5
min_size = 3
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
depends_on = [aws_launch_template.app]
}
# Initialize with user data at boot instead of a provisionerWorkspaces separate state for the same configuration across environments, but they are not a full security boundary. The recommended approach is to separate buckets and permissions per environment.
Policy tools (such as Terraform Cloud Sentinel or OPA) put guardrails in front of dangerous changes and block them before review.
import associates an existing resource with state. It does not generate configuration files, so you must define the matching resource block in advance.
Workspace and import examples
# A workspace per environment
terraform workspace new staging
terraform workspace select staging
# Import an existing resource
# Define the matching resource block first
terraform import aws_iam_role.app_role app-role
# Sentinel/OPA are usually hooked into the plan during CI or remote runsAssociate / Pro
Question 1
Your team uses an S3 backend, but occasional concurrent terraform apply runs have nearly corrupted the state file. Which is the most appropriate remediation?
Correct answer: A
The S3 backend on its own does not provide locking, so enabling locking with a companion DynamoDB table is correct. -lock-timeout only adjusts the wait time; it is not a substitute for a locking mechanism. Workspaces separate state but do not solve concurrent updates to the same environment. Managing tfstate in Git is discouraged.
What is the main difference between terraform plan and apply?
plan only proposes changes with no side effects. apply executes the plan and actually modifies real infrastructure. Reviews and policy checks run against the plan output.
When is it appropriate to use -target?
Reserve it for emergency, narrowly scoped changes such as incident response. Because it ignores parts of the dependency graph, avoid it in routine operations or normal change workflows.
How should sensitive values and credentials be handled?
Use environment variables, the Terraform Cloud variable store, or each cloud's secret manager. Never leave plaintext in tfvars or state files, and minimize exposure with sensitive outputs.
Practice with certification-focused question sets
Try free questionsNicheeLab Editorial Team
NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.
HCL Syntax: Terraform's Configuration Language (2026)
HCL2 fundamentals for Terraform — blocks, attributes, expres...
Terraform Authoring & Operations Pro: Complete Guide (2026)
Tactics for the Terraform Pro exam — module authoring, works...
Terraform Providers: Plugin Management Fundamentals (2026)
Provider mechanics — required_providers, versions, mirrors, ...
Terraform Resource Blocks: Declarative Infra Units (2026)
Resource block fundamentals — addresses, references, common ...
Terraform Data Sources: Read-Only External Data (2026)
Data source basics — declaration, refresh behavior, dependen...