Frontend
easy
mid
Web storage APIs: localStorage, sessionStorage, cookies
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
HttpOnly→ never store secrets or unencrypted PII in localStorage; it's fully exposed to XSS. - Storage is per-origin —
https://app.comandhttps://api.app.comdon't share it. - Use the
storageevent 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
Frontend
Medium
6 min