TL;DR: With a locking backend the second apply fails lock acquisition by default;
-lock-timeout=5mallows bounded retries, which is correct. Without locking you get interleaved writes and a state file that matches neither reality nor either intention, and recovery requires reconciling a trusted state snapshot and actual resources, including imports where needed. The stale-lock case is the one you will actually meet, and force-unlock is safe only after you have confirmed nothing is still running.
How to approach it
Answer the happy path in one line, then spend the time on the two cases that matter operationally: no locking, and a lock nobody holds.
A strong answer
A locking backend takes an exclusive lock before reading state and holds it through apply. The second engineer sees the lock, with the holder, operation and timestamp, and either fails immediately or retries for the configured lock timeout. This is the mechanism and it is why remote state is not optional for a team.
On S3 this was historically a DynamoDB table with a conditional write; newer versions support native S3 conditional writes and no longer need the extra table. Terraform Cloud, GCS and azurerm all lock natively.
Without locking, applies can start from the same snapshot and persist conflicting updates, including intermediate state writes. One writer can overwrite resource records from another. Resources the first engineer created are now absent from state while existing in the cloud: orphaned, unmanaged, invisible to future plans, and still costing money. Worse, the next plan proposes creating them again, and if names collide the apply fails in a way that looks unrelated. This is why recovery compares available versions with current resource IDs and operation logs, then restores or imports the correct bindings under exclusive control, and why bucket versioning is the non-negotiable part of a backend.
The stale lock is the case you meet in practice. A pipeline was cancelled, a laptop lost network, a runner was terminated mid-apply. The lock persists because nothing released it. The dangerous move is to force-unlock reflexively:
terraform force-unlock <LOCK_ID>
Before running it, establish that the original operation is genuinely not running, because force-unlocking a live apply gives you exactly the interleaved-write scenario above. The lock metadata names the host, the user and the time, which is usually enough to go and ask. If the apply died mid-flight, also expect the state to be behind reality: resources may have been created without being recorded, so run a plan and read it carefully rather than applying immediately.
The structural preventions worth naming: applies run only from CI, not from laptops, so there is one identity and one place to look; state is split per environment and per component so the blast radius of any lock is small and two teams rarely contend; and a plan is reviewed as an artifact before an apply is authorised.
What interviewers probe next
"Does terraform plan take a lock?" It takes one by default because it refreshes state. -lock=false is available for read-only inspection and should not be used before an apply.
"How would you recover orphaned resources?" terraform import or import blocks, one at a time, until a plan shows no changes. Import blocks are preferable because they are reviewable and repeatable.
"Why split state?" Blast radius, plan time, and contention. A single state for an entire estate means every change locks everything and every plan takes minutes.
Common mistakes
Stopping at "state locking prevents it" without describing the failure it prevents.
Force-unlocking without checking, which converts a five-minute wait into a corrupted state.
Forgetting that a killed apply leaves state behind reality, so the next step is a careful plan rather than a retry.
Sources: Apply locking and failure behavior.