How Apollo Client Cache Normalization Keeps Your UI in Sync
Learn how Apollo Client’s automatic cache normalization creates a single source of truth for GraphQL objects, preventing duplicate data and stale UI after mutations.
26 Jul 2025, 03:53 UTC

The problem: duplicate data and stale UI
When Apollo Client stores each query result as a separate tree, the same object can appear many times in the cache. If a mutation updates that object, only the tree that matches the mutation’s result is changed; other copies stay stale, causing the UI to show outdated values. This duplication also wastes memory, especially in apps with large lists or nested relationships.
How Apollo Client normalizes the cache
The InMemoryCache uses the __typename field together with a primary key (usually id or _id) to build a flat lookup table. Each entity is stored once under a key like Post:1. Fields that reference other entities store only a reference to that key, not a copy of the whole object. When a mutation returns an object with the same typename and ID, the cache updates the single entry, and every query that holds a reference to that key sees the new values automatically.
Cache key generation
By default the cache derives the key from the typename and the value of the id field. If your schema uses a different field (e.g., _id), you can provide a custom typePolicies function to tell the cache how to build the key.
Worked example: posts and authors
Imagine a query that fetches a list of posts with their title and author:
query Posts {
posts {
id
title
author {
id
name
}
}
}
And a mutation that updates a post’s title:
mutation UpdatePostTitle($id: ID!, $title: String!) {
updatePost(id: $id, title: $title) {
id
title
}
}
Both operations return a Post object identified by its id. Because the cache normalizes by typename and id, the title field lives in a single entry such as Post:123. When the mutation finishes, the cached entry for Post:123 receives the new title, and any query that references Post:123 (like the list above) re‑renders with the updated value—no refetch needed.
Trade‑offs and limits
- Every entity must have a globally unique identifier. If two different types share the same ID value, or if an object lacks an ID, the cache falls back to storing the full object tree, losing deduplication.
- The normalized cache adds a level of indirection: field reads follow a reference to the lookup table. For extremely large schemas with deep relationships, this lookup cost can become measurable, though in most applications it remains negligible.
Checking that normalization works
- Install the Apollo Client DevTools extension for Chrome or Firefox.
- Run the query above in your app.
- Open the DevTools → Cache tab. You should see entries like Post:123 and Author:456 rather than the whole query result duplicated under each query name.
- Execute the mutation (e.g., rename a post).
- Observe that the Post:123 entry’s title field changes instantly, and any other cache entries that reference Post:123 reflect the new value.
Actionable takeaway
Enable Apollo Client’s automatic cache normalization by ensuring every type has a sensible ID field (or configuring a custom key function). This gives you a single source of truth for each GraphQL entity, reduces memory duplication, and keeps your UI in sync after mutations without extra refetching. Verify the behavior with the DevTools cache inspector or a unit test that asserts cached field updates after a mutation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.