Course outline

GraphQL / Intermediate: policy, sources and clients / Lesson 10

Client identity, caching and reconciliation

Connect normalized client records to stable object identity and distinguish entity updates from list membership.

About 4 min readContent reviewed
On this page

Lesson goals

  • Explain why type and identity belong in normalized cache keys.
  • Reconcile writes, pagination and user changes under explicit freshness rules.

Name the cache before discussing invalidation

A GraphQL application can have several unrelated caches: parsed documents, request-scoped loaders, server response caches, HTTP caches and normalized client stores. Each has a different key and lifetime. A client entity store commonly combines an object’s concrete type and stable identifier, while query results also depend on arguments and field policy. Updating one entity does not necessarily add it to every filtered list or remove it from a list whose predicate changed. State which layer is stale before clearing everything or claiming that returning an ID solves invalidation.

Use a collision-resistant local identity convention

The local model uses __typename and id to distinguish a Course c1 from an Author c1. These fields are often useful selections for normalized clients, but exact cache configuration belongs to the chosen client library. Objects without stable identifiers may be embedded rather than globally normalized. Do not derive identity from a title that users can edit. If identifiers are only unique within a tenant, the cache must also reflect that scope or be completely replaced when the authenticated tenant changes.

javascript Offline entity identity model
function entityKey(value) {
  return `${value.__typename}:${value.id}`;
}
const course = {__typename:"Course",id:"c1",title:"Graph foundations"};
const author = {__typename:"Author",id:"c1",displayName:"Lina"};
console.assert(entityKey(course) === "Course:c1");
console.assert(entityKey(course) !== entityKey(author));

The two synthetic objects have different keys despite sharing id. This is a convention model, not a configured client cache.

Separate cached presence from usable freshness

A record being present locally does not prove that it reflects current server policy or source state. Choose whether each view may show cached data immediately, requires a fresh read, or may render stale data while refreshing. Authorization-sensitive state often needs stricter handling than a descriptive title. A logout or principal change should retire the old user’s cached data before another user can observe it. Keep private results out of shared HTTP caches unless the cache policy and keying correctly preserve their intended audience.

Return enough identity for a canonical update

A rename mutation can return the canonical Course identity and title. A configured normalized client may then update existing references to that entity. The operation below does not automatically repair a search result whose ordering depends on title or a list filtered by title prefix. Those lists may need a reviewed cache update or bounded refetch. Optimistic updates are a temporary prediction; give them stable temporary identity and a rollback/reconciliation policy, and never use optimistic success as proof that a sensitive server action was authorized or committed.

graphql Canonical entity fields after a write
mutation Rename($input: RenameCourseInput!) {
  renameCourse(input: $input) {
    course { __typename id title }
    problems { code field }
  }
}

This fragment assumes matching schema types. Identity can update references, while filtered list membership remains a separate cache concern.

Merge pages only within one argument scope

Paginated field caches must distinguish filters, sorting and principal scope. Concatenating every courses page under one unqualified key can mix an archived list into a published list or duplicate boundary records. Define a merge policy that respects the service’s cursor semantics and deduplicates by stable identity when appropriate. Test rename-driven reordering, deletion, logout, failed optimistic writes and changing filters during an in-flight request. A late response from an old principal must not repopulate the new principal’s view merely because its network request eventually completed.

Common errors and fixes

Course and Author records overwrite one another

Why it happens
The cache keyed only by a locally reused ID.
How to fix it
Include concrete type and required tenant scope.
Prevent it next time
Test identical IDs across types and tenants.

A renamed course stays in the wrong filtered list

Why it happens
Entity normalization was assumed to recompute list membership.
How to fix it
Apply a reviewed list update or refetch its bounded query.
Prevent it next time
Test fields that affect filters and sort order.

Old user data appears after logout

Why it happens
Cache lifetime and in-flight responses outlived identity.
How to fix it
Retire old cache state and reject stale-identity responses.
Prevent it next time
Test identity switches while requests are pending.

Check your understanding

1. Does returning id and title after rename repair every title-sorted list?

Show worked solution

No. A normalized store may update the entity’s title wherever referenced, but its existing list positions and predicate membership can remain unchanged. Review the client’s field policies and either update affected bounded lists deliberately or refetch them. Test a rename that moves a record across the visible page boundary rather than only a rename that preserves its order.

2. Why is a late response after an account switch a correctness issue?

Show worked solution

The request was authorized and scoped for the previous principal, while the visible client state now belongs to another principal. Applying it can expose private data or overwrite the new user’s view. Track an identity generation or equivalent client lifecycle and discard incompatible results, in addition to clearing existing cached records at the switch.

Key takeaways

  • Entity updates, list membership and freshness are separate cache decisions.
  • Cache identity and lifetime must follow the authenticated audience.

Official references