Browser storage: localStorage vs sessionStorage vs cookies vs IndexedDB
Pick by need: cookies (sent to server, small, can be HttpOnly/Secure for auth); localStorage (persistent key-value, ~5MB, JS-only, sync API); sessionStorage (same but per-tab session); IndexedDB (large structured async storage, indexable, transactions). Tokens → HttpOnly cookies. UI prefs → localStorage. Big or structured data → IndexedDB.
Four storage mechanisms, each with a different shape — knowing what trade-off you're paying for each is the interview point.
Cookies
- Size: ~4KB per cookie, ~50–100 per domain.
- Lifetime: until
Expires/Max-Age; session if neither. - Sent automatically with every request to the domain → the only storage usable for server-side auth.
- JS access:
document.cookie(clunky) — butHttpOnlyblocks JS access entirely (anti-XSS). - Security flags:
Secure,HttpOnly,SameSite(Lax/Strict/None). - Use for: session tokens (HttpOnly + Secure + SameSite), CSRF tokens, server-readable preferences.
localStorage
- Size: ~5MB per origin.
- Lifetime: persistent until cleared.
- Sync API — blocks the main thread; don't write large values.
- String only —
JSON.stringifyeverything. - Scope: origin-wide, all tabs.
- Not sent to server.
- Use for: small UI prefs (theme, last-used filter), non-sensitive client-only data.
- Don't use for: auth tokens (XSS-readable), large data, structured data with queries.
sessionStorage
- Same API as localStorage but per-tab session — cleared when the tab closes.
- Use for: wizard state mid-session, one-tab caches, scratch state.
IndexedDB
- Size: large (often quota-based; can be hundreds of MB).
- Async API — doesn't block the main thread.
- Structured storage with indexes and transactions.
- Real types — store objects, Blobs, Files.
- Use for: offline-first apps (PWAs), large or structured data, queryable caches.
- Cost: API is verbose; reach for Dexie or idb wrappers.
Cache Storage (bonus)
- Tied to service workers; stores Request/Response pairs.
- Use for: SW caching strategies (cache-first, etc.).
The decision matrix
| Need | Pick |
|---|---|
| Server-side auth | Cookie (HttpOnly + Secure + SameSite) |
| Small UI prefs | localStorage |
| Per-tab session state | sessionStorage |
| Large/structured/offline | IndexedDB |
| SW asset/response cache | Cache Storage |
Security notes
- localStorage and sessionStorage are XSS-readable — never put auth tokens there. The "JWT in localStorage" pattern is widely criticized for this reason. HttpOnly cookies are the safer default.
- Cookies need SameSite + Secure to mitigate CSRF and MITM.
- IndexedDB is also XSS-readable — don't store secrets.
Performance notes
- localStorage is synchronous — large reads/writes block. IndexedDB is async.
- Quota-exceeded errors must be handled (
try/catch). storageevent fires across tabs on localStorage changes — useful for tab sync (see [[how-would-you-keep-multiple-tabs-in-sync]]).
Interview framing
"Cookies are the only storage sent to the server, so auth tokens go there as HttpOnly + Secure + SameSite. localStorage is sync, ~5MB, persistent, origin-wide — fine for small UI prefs. sessionStorage is the same API but per-tab. IndexedDB is async, large, structured with indexes and transactions — the right answer for offline-first apps and big caches; use Dexie. All of localStorage, sessionStorage, and IndexedDB are XSS-readable, so secrets don't belong there."
Follow-up questions
- •Why are HttpOnly cookies safer for tokens than localStorage?
- •When would you reach for IndexedDB over localStorage?
- •What does SameSite=Lax/Strict/None do?
- •How do you sync state across tabs?
Common mistakes
- •JWT in localStorage exposed to XSS.
- •Writing big JSON blobs to localStorage and hitting quota.
- •Using localStorage where IndexedDB fits.
- •Forgetting SameSite/Secure on auth cookies.
Performance considerations
- •localStorage is sync — avoid large values. IndexedDB is async — use transactions, don't open per-op. Cookies bloat every request — keep them small.
Edge cases
- •Private/incognito mode quota limits.
- •Storage cleared mid-session.
- •Cross-tab updates via storage event.
- •Storage event doesn't fire in the same tab that wrote.
Real-world examples
- •Auth: HttpOnly session cookie.
- •Theme/UI: localStorage.
- •Email client offline cache: IndexedDB.
- •SW asset cache: Cache Storage.