Terraform's state stores the full attribute values of each resource, not just its ID. What does it need those cached values for, and how can they mislead a plan when they go stale?
answer
- the prior side of every diff
- references resolve without an API call
- some values the API never gives back
- a snapshot, not the truth
- no-changes only means cache agrees
basics
~20 sCached attributes are the prior side of every diff, the source for cross-resource references and outputs at plan time, and the only home for values the API never returns. When they are stale, the plan is computed against a fiction and can show no changes while reality differs.
solid answer
~50 sThe `attributes` object recorded for each instance does three jobs. It is the *prior state* half of the diff — Terraform compares configuration against it to decide create, update, replace or no-op. It resolves references at plan time, so `aws_instance.web.private_ip` in another resource or an output can be evaluated without an API call for every expression. And it holds values the provider can never read back, such as a generated password, which would otherwise be lost after the first apply. The `schema_version` beside them records which provider schema shaped the values, so a newer provider can upgrade them rather than misread them. The risk is that they are a *snapshot*, not the truth: if the plan runs without refreshing them, out-of-band changes are invisible and Terraform reports no changes on a resource somebody edited by hand.
go deeper
Know that state stores the resource's actual attribute values from the last apply, not just its ID, and that Terraform compares those values to your configuration to decide what to change.
Explain the three jobs — the prior side of the diff, plan-time reference and output resolution, and holding values the API never returns — and say that refresh is what keeps them honest.
Demonstrate the operational consequence: a plan is only as true as the state it diffed against, so a no-changes result without a refresh proves nothing. Connect refresh cost on large states to the temptation that creates the blind spot.
Own the tradeoff at estate scale: refresh time grows with objects per state, so the sizing of state boundaries is what decides whether teams can afford honest plans, and what the standard is for the plan a human approves.
## Three jobs for one blob of JSON Every resource instance in state carries an `attributes` object — the entire post-apply representation of the object as the provider described it, not just the identifier: ```json { "schema_version": 1, "attributes": { "id": "i-0abc123def456", "instance_type": "t3.micro", "private_ip": "10.0.1.24", "tags": { "Name": "web" } } } ``` **Job one: the prior side of the diff.** Terraform's decision for each resource is a three-way comparison, and the recorded attributes are one of the three inputs. Without them, Terraform could see that the configuration asks for `t3.large` but not that the object is currently `t3.micro` — so it could not tell an update from a no-op, nor decide whether the change is updatable in place or forces replacement. **Job two: resolving references cheaply.** When another resource or an output refers to `aws_instance.web.private_ip`, the value comes from the recorded attributes. This is why a plan can evaluate a whole graph of interdependent expressions without a per-expression API round trip, and why outputs have values even when you only run `terraform output`. **Job three: holding what the API will not return.** Many providers expose write-only or generate-once values — an initial database password, a private key, a generated secret. The remote API returns nothing useful for these on a read. If Terraform did not persist the value at create time, every subsequent plan would see an unknown and either churn or lose the value. That persistence is exactly why the file is sensitive, and it is a well-known property rather than a bug. ## schema_version Alongside the attributes sits `schema_version`, an integer owned by the provider. Providers reshape their resource schemas between releases — renaming an attribute, changing a nested block into a set. The recorded number tells a newer provider which shape the stored values are in, so it can run its own upgrade routine on read. Without it, a provider would have to guess whether `subnet_ids` was a list or a set in the version that wrote the object. ## Where staleness bites The cached attributes describe the object *as of the last time Terraform looked*. Between then and now, anything can have happened: a console edit, another tool, an autoscaling action, a provider-side default change. The diff Terraform shows is only as true as that snapshot. By default a plan refreshes the recorded objects in memory before diffing, so the comparison uses fresh values, and the plan output calls out anything that changed outside Terraform. Skip that refresh — a plan run with refresh disabled, typically for speed on a very large state — and you get a diff computed purely against the recorded values. The failure mode is quiet and specific: **the plan says no changes for a resource somebody edited by hand**, because config and cache agree and nobody asked the cloud. The reverse also happens: a resource that is actually gone still shows as present, so an apply that depends on it fails at the provider rather than at plan time. A subtler variant: a plan can be perfectly refreshed and still be wrong by the time it applies, because the world keeps moving between the two. That gap is inherent to any preview-then-approve flow, not a defect of caching. ## Practical consequences worth stating - **A no-changes plan is not proof the estate matches the code** unless the plan actually refreshed. Say this in an interview; it is the sentence that separates people who have operated Terraform from people who have read about it. - **Big states make refresh expensive**, since it is one read per tracked object. That cost is what tempts teams to skip it, and skipping it is what makes the cache lie. The structural fix is fewer objects per state, not a faster lie. - **Cached attributes are why state is sensitive.** They contain real values, not references to values. - **A provider upgrade can change the diff without you changing anything**, because the upgrade routine rewrites how stored values are represented; a first plan after a major provider bump deserves a careful read. ## The shape of a strong answer Name all three jobs rather than just "it caches for speed" — speed is a consequence of job two, not the reason the values are stored. Then make the staleness point concrete with the no-changes-on-a-hand-edited-resource case, and land on the operational rule: the trustworthiness of a plan is exactly the freshness of the state it was diffed against.
- Why does state hold values the provider can never read back, like a generated password?Because create-time is the only moment that value exists in the response. If Terraform discarded it, every later plan would see an unknown, and any output or resource referencing it would break. Persisting it is the only way the diff stays computable — and it is precisely why the state file has to be treated as a secret-bearing artifact.
- What is schema_version on a state instance actually used for?It records which version of the provider's resource schema shaped the stored attributes. When a newer provider reads an object written under an older schema, it runs its own upgrade routine to convert the values rather than misinterpreting them. It is provider-owned, and unrelated to the state format's own version field.
- A plan on a huge state is slow, so someone wants to disable refresh in CI. What do you tell them?That it trades correctness for latency: the plan is then a diff against yesterday's snapshot, so any out-of-band change is invisible and a hand-edited resource reports no changes. It is defensible for a quick local iteration, not for the plan a human approves before production. The real fix is splitting the state so there are fewer objects to read.
saying these in an interview costs you the question
- Attributes are cached purely to make plans faster
- A no-changes plan proves reality matches the code
- Terraform re-reads every attribute from the API on every expression
- Stale attributes are harmless because apply re-checks everything
- Only the resource ID is needed to compute a diff