Back to Browser Internals
Browser Internals
easy
mid

What are the web storage APIs, and how do localStorage, sessionStorage, and cookies differ?

Three client-side storage mechanisms with different lifetimes, scopes, capacities, and access models. localStorage persists indefinitely (~5MB, same-origin), sessionStorage clears on tab close, cookies (~4KB) are sent on every HTTP request and are the only one usable by the server.

6 min read·~8 min to think through

Browsers give you three main client-side storage options. Picking the right one is about lifetime, scope, size, and who needs to read it.

localStorage

  • Lifetime: persists until explicitly cleared (survives restarts).
  • Scope: per-origin, shared across all tabs/windows of that origin.
  • Size: ~5–10MB.
  • API: synchronous, string-only — localStorage.setItem(k, JSON.stringify(v)).
  • Use for: user preferences, theme, cached non-sensitive data, feature-flag overrides.

sessionStorage

  • Lifetime: cleared when the tab closes (a page reload keeps it; a duplicated tab gets a fresh copy).
  • Scope: per-origin per-tab — not shared between tabs.
  • Use for: multi-step form state, scroll position, one-off flows you don't want leaking across tabs.

Cookies

  • Lifetime: Expires/Max-Age, or session cookie if omitted.
  • Size: ~4KB total per domain — tiny.
  • Key difference: automatically attached to every HTTP request to the domain, so the server can read them. localStorage/sessionStorage are client-only.
  • Security flags: HttpOnly (JS can't read it — defeats XSS token theft), Secure (HTTPS only), SameSite (CSRF protection).
  • Use for: auth/session tokens, anything the server needs per-request.

Decision rule

  • Server needs it per request → cookie (with HttpOnly + Secure + SameSite).
  • Client-only, must survive restarts → localStorage.
  • Client-only, scoped to one tab session → sessionStorage.
  • Large, structured, or needs indexing/transactions → IndexedDB (async, much bigger).

Important caveats

  • localStorage/sessionStorage are synchronous — large reads/writes block the main thread.
  • All three are readable by JS unless the cookie is HttpOnlynever store secrets or unencrypted PII in localStorage; it's fully exposed to XSS.
  • Storage is per-originhttps://app.com and https://api.app.com don't share it.
  • Use the storage event to sync localStorage changes across tabs.

Follow-up questions

  • Why is storing a JWT in localStorage considered risky, and what's the alternative?
  • How do you sync state across tabs using the storage event?
  • When would you reach for IndexedDB over localStorage?
  • What does SameSite=Lax vs Strict vs None actually change?

Common mistakes

  • Storing auth tokens in localStorage where any XSS payload can exfiltrate them.
  • Forgetting localStorage only stores strings — pushing objects in without JSON.stringify.
  • Assuming sessionStorage is shared across tabs (it isn't).
  • Putting large blobs in localStorage and blocking the main thread on every read.

Performance considerations

  • localStorage/sessionStorage are synchronous and block the main thread — batch writes, never write in a scroll/resize handler. Cookies add weight to every single request, so a bloated cookie slows all network calls. For large or frequently-accessed structured data, IndexedDB is async and won't jank the UI.

Edge cases

  • Safari Private Mode historically threw on setItem — wrap writes in try/catch.
  • Quota exceeded errors when ~5MB limit is hit.
  • Cookies silently dropped when the 4KB limit is exceeded.
  • localStorage disabled entirely by privacy settings or enterprise policy.

Real-world examples

  • Theme preference (dark/light) in localStorage, read on first paint to avoid a flash.
  • A checkout wizard keeping step state in sessionStorage so a refresh doesn't lose progress.
  • Session auth token in an HttpOnly Secure cookie so XSS can't read it.

Senior engineer discussion

Senior answers center on the security model: localStorage is a plaintext bucket fully exposed to XSS, so the real auth conversation is HttpOnly cookies vs token-in-memory + silent refresh. Also worth raising: the synchronous nature of Web Storage as a perf footgun, cookie bloat taxing every request, and IndexedDB (or libraries like idb-keyval) as the grown-up option for anything structured or large.

Related questions