System Design Space
Knowledge graphSettings

Updated: April 21, 2026 at 4:55 PM

State and cache architecture in frontend applications

medium

How to separate local/UI/server/URL/form state, design invalidation and optimistic updates, and avoid turning the client into an unmanageable dependency graph.

Almost every complex frontend application breaks not on one giant state store, but on confusion between local state, server state, URL state, form state, and cache invalidation. The earlier those contours become explicit, the less chaos appears in UI updates and bug investigation.

The chapter is useful because it connects state management to real scenarios: optimistic updates, background refetch, offline queues, realtime updates, and users returning to a previous screen. It helps frame state as multiple classes of data with different lifecycles rather than one universal bucket.

For reviews and interviews, the material is strong because it gives you a practical way to talk about the hardest frontend problem: where the source of truth lives, who owns the cache, and why invalidation is usually harder than the initial fetch.

Practical value of this chapter

Design in practice

Turn guidance on client state boundaries, cache invalidation, and predictable UI updates into concrete decisions for composition, ownership, and client-runtime behavior.

Decision quality

Evaluate architecture through measurable outcomes: delivery speed, UI stability, observability, change cost, and operating risk.

Interview articulation

Structure answers as problem -> constraints -> architecture -> trade-offs -> migration path with explicit frontend reasoning.

Trade-off framing

Make trade-offs explicit around client state boundaries, cache invalidation, and predictable UI updates: team scale, technical debt, performance budget, and long-term maintainability.

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, the argument about state management is really an argument about which state classes 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 update.

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

Design Google Docs collaborative editor

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

Open chapter

Update and invalidation patterns

Optimistic update with an explicit rollback path

Useful when user intent is expensive and obvious: like, rename, toggle. The key is to define in advance how UI explains a conflict or a rollback.

Background revalidation

Works well for screens with stale-tolerant data: show cache first, then quietly revalidate and update only the areas that have changed.

Targeted invalidation by domain keys

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

Realtime merge policy

WebSocket or push updates need merge rules against local optimistic state. Otherwise UI starts flickering between local and remote versions.

Common anti-patterns

  • One giant global store for the whole app without a distinction between server state and UI state.
  • Hidden side effects in selectors and derived state that are hard to explain and test.
  • Resetting query cache on every route change so the product constantly loses user context.
  • No explicit strategy for stale data, optimistic rollback, and reconnect after 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 backend truth and reuse across screens, the more it needs a typed cache layer with explicit invalidation policy.

Related chapters

Enable tracking in Settings