← all cheat sheets
FUNDAMENTALS · MECHANISM WALKTHROUGH

Two Engineers Run terraform apply
At the Same Time — What Really Happens

the state-locking mechanism that stops two concurrent applies from corrupting the same state file.
ENGINEER A: apply ACQUIRES LOCK ENGINEER B: apply LOCK CHECK FAILS B ERRORS OUT A COMPLETES LOCK RELEASED B RETRIES
01 The Full Sequence — Worked Example
1

Both Engineers Share One Remote State File

With a remote backend (e.g. S3), both Engineer A and Engineer B's Terraform CLI point at the exact same state file — there's no local-only state to isolate them from each other; they are, by design, operating on shared data.

Shared State
2

Engineer A Runs terraform apply First

A's Terraform reaches out to the backend's locking mechanism — for S3, that's a separate DynamoDB table configured specifically for this purpose — and writes a lock record containing a unique lock ID, who holds it, and when.

Lock Attempt
3

Checkpoint — Lock Acquired by A

The DynamoDB write succeeds because no lock record currently exists for this state file. A now holds the lock, and proceeds into its normal refresh → graph → plan sequence. Nothing about B's situation has changed yet.

Checkpoint
4

Engineer B Runs terraform apply Seconds Later

B's Terraform independently attempts the exact same lock-acquisition write against the same DynamoDB table — it has no idea A is running; the two CLI processes never talk to each other directly.

Lock Attempt
5

B's Lock Write Fails — Conditional Check

DynamoDB's conditional write (used specifically for this purpose) rejects B's attempt because a lock record already exists. This isn't a race that "usually" works — the whole mechanism exists because a plain read-then-write would have a race condition; the conditional write is atomic.

Rejected
6

B's Terraform Exits with an Error

B sees an error like Error acquiring the state lock, including who holds it (A's identity) and when they started. Crucially, nothing about B's apply has run — no refresh, no plan, no API calls — Terraform refuses to proceed at all without the lock.

Blocked
7

A's Apply Continues, Unaffected

A has no visibility into B's failed attempt at all — from A's side, this looks like a completely normal, uncontested apply proceeding through refresh, plan, and execution.

Unaffected
8

A Finishes — State Updated, Lock Released

Once A's apply completes (success or failure), Terraform deletes the lock record from DynamoDB as its final step — this is what actually frees the state file for the next apply attempt.

Unlock
9

B Retries and Succeeds Normally

B re-runs terraform apply. This time the lock is free, so B acquires it and proceeds — but now against the updated state A just wrote, meaning B's own plan reflects A's changes as the new baseline, not what B originally saw.

Retry
10

The Alternative: A Crashes Mid-Apply

If A's process is killed (laptop closes, network drops) before releasing the lock, the lock record is left stuck — this is the specific, narrow scenario terraform force-unlock exists for, and only after confirming no apply is genuinely still running.

Stale Lock
02 How to Explain This in an Interview
03 Follow-Up / Gotcha Questions
Q Does terraform plan (not apply) also acquire the state lock?
A Yes — plan also locks state, since it performs a refresh that reads and could technically race with a concurrent write. It releases the lock immediately after computing the plan, since it never writes changes.
Q What actually makes the DynamoDB lock write "atomic" and race-free?
A DynamoDB supports conditional writes — "write this item only if it doesn't already exist" — evaluated atomically by DynamoDB itself, so two simultaneous attempts can't both succeed even if they arrive within milliseconds of each other.
Q If B force-unlocks while A is still genuinely running, what happens?
A Both A and B can now proceed to write state concurrently — this is exactly the corruption scenario locking exists to prevent, which is why force-unlock is a manual, deliberate override rather than something automated.
Q Does every Terraform backend support locking?
A Most remote backends do (S3+DynamoDB, Terraform Cloud, Azure Storage, GCS), but a plain local state file with no backend configured has no locking at all — concurrent applies against local state genuinely can corrupt it.
Q Can two applies against completely different, unrelated resources still block each other?
A Yes, if they share the same state file — locking is per state file, not per resource. This is one practical argument for splitting large infrastructures into multiple smaller state files/workspaces.
Q Is state locking the same thing as a CI pipeline concurrency limit?
A No — they're complementary. A CI concurrency limit (e.g. "only one apply job at a time") is a scheduling policy that prevents the situation from arising; state locking is the underlying safety net that protects you even if that policy fails or someone applies manually from their laptop.
04 Quick-Fire Glossary
TermMeaning
Remote BackendShared, external location (S3, Terraform Cloud, etc.) where state is stored
State LockBackend-enforced exclusive hold preventing concurrent writes to one state file
DynamoDB TableCommon S3-backend companion used specifically for atomic lock records
Conditional WriteAn atomic "write only if not already present" operation preventing races
force-unlockManual override to clear a stuck lock — only safe if no apply is truly still running
Lock IDUnique identifier in the lock record, shown in the "who holds it" error message
Stale LockA lock left behind by a crashed/killed Terraform process that never released it
Local StateState stored only on one machine's disk — no locking, no sharing, not for teams
WorkspaceA separate state file under the same backend, one way to scope/limit locking
DriftDivergence between state and real infrastructure — unrelated to locking, but caught by refresh