System Design Space
Knowledge graphSettings

Updated: May 27, 2026 at 8:00 AM

Client State and Cache Architecture in Frontend Applications

medium

How to separate local UI, server, URL, and form state; design client caching, invalidation, optimistic updates, and behavior under network failures.

Almost every complex frontend application breaks not because of one big state store, but because local state, server state, URL state, form state, and cache invalidation get blurred together. The earlier those boundaries become explicit, the less chaos appears in UI updates and bug investigation.

This chapter connects state management to real scenarios: optimistic updates, background refresh, offline queues, realtime updates, and users returning to a previous screen. It helps frame state as several classes of data with different lifetimes rather than one universal bucket.

For architecture reviews and interviews, the material gives you a practical way to discuss the hardest frontend question: where the source of truth lives, who owns the cache, and why invalidation is usually harder than the first fetch.

Practical value of this chapter

Design in practice

Separate state by lifetime and source of truth: local, server, URL, and form state should not follow one universal rule.

Decision quality

Evaluate the solution through data freshness, invalidation strategy, optimistic rollback, and reconnect behavior.

Interview articulation

Structure answers as state class, owner, storage location, update rule, cache behavior, and failure handling.

Trade-off framing

Make the cost explicit: local simplicity, cache reuse, stale-data risk, and synchronization complexity.

Context

Caching strategies

In frontend, invalidation is as hard as it is in backend, except the user sees every mistake directly on the screen.

Читать обзор

In almost every complex frontend application, debates about state management are really debates about which kinds of state have been mixed into one bucket. Local UI state, server state, URL state, and form state have different lifecycles and fail in different ways.

That is why the key question is: where is the source of truth and who owns invalidation after a user action, background refresh, or realtime event.

This chapter separates client state into local state, server state, URL state, and form state, then connects those boundaries to source of truth, client cache, data freshness, cache invalidation, optimistic updates, background refresh, and reconnect behavior.

State and cache map

One screen can hold local UI state, form state, URL parameters, and a client-side copy of server data at the same time. Switch scenarios to see where state lives and who owns each update.

FlowRoute/screenLocal contoursQuery cacheServerRealtime

Where state lives

The map separates state by owner: what the screen keeps nearby, what is encoded in the URL, what lives in query cache, and what the server confirms.

1

Entry

Route and screen

URL, route params, and the visible screen define the user scenario context.

2
↓ keeps nearby

Near UI

Local state

Modals, hover state, expanded panels, and temporary drafts should stay close to the component.

3
↓ reads and updates

Serialization

Form and URL

Form fields, dirty flags, filters, and pagination follow separate lifecycle rules.

4
↓ verifies

Data copy

Query cache

The cache stores a client-side copy of server data, its freshness, and invalidation rules.

5
↓ listens

Source of truth

Server

The server confirms facts, applies mutations, and returns a new consistent state.

6

Events

Realtime channel

WebSocket or push events bring external changes that must be merged into the local screen.

Architecture meaning

The main boundary

  • Not all state belongs in a global store.
  • Query cache is not the source of truth; it is a managed copy.
  • URL and form state need explicit serialization, recovery, and cleanup rules.
Good state architecture starts with a simple question: who owns this value and how long should it live?

State classes

Local UI state

Modal visibility, hover state, unsaved draft fragments, and temporary interaction state. It usually lives closest to the component and does not need global synchronization.

Server state

Data whose source of truth lives on the backend: profiles, feed items, dashboard widgets, orders. Its lifecycle is defined by cache policy, freshness, and invalidation.

URL state

Filters, pagination cursor, active tabs, and search params. If users expect deep links or recoverability after refresh, the state probably needs to be serializable into the URL.

Form state

Validation, dirty flags, autosave, field-level errors, and optimistic submission. Forms almost always need a lifecycle model different from standard query caching.

Related

Frontend System Design Case: Google Docs-Style Collaborative Editor

Realtime collaboration quickly exposes the cost of implicit merge and rollback rules.

Open chapter

Update and invalidation patterns

Optimistic update with explicit rollback

Useful when user intent is clear and immediate: like, rename, toggle. The important part is defining how the interface explains conflicts and rolls the change back.

Background freshness check

Works well for screens that can tolerate slightly stale data: show cached content first, then quietly re-check the server and update only the areas that changed.

Targeted invalidation by domain keys

Instead of clearing everything, invalidate only the query groups that depend on the changed resource. That reduces unnecessary network traffic and visual jumps.

Realtime merge rules

WebSocket and push updates need clear merge rules for local changes. Otherwise the UI starts jumping between local and remote versions.

Common anti-patterns

  • One global state store for the whole app without a clear distinction between server state and UI state.
  • Hidden side effects in derived state that are hard to explain and test.
  • Resetting the query cache on every route change, so the product keeps losing user context.
  • No explicit strategy for stale data, optimistic rollback, and reconnect after a network failure.

Practical rule

The closer state is to a user gesture and the shorter its lifetime, the closer it should live to the module or screen. The closer state is to server-side truth and reuse across screens, the more it needs a query cache with an explicit invalidation policy.

Related chapters

Enable tracking in Settings