localStorage vs sessionStorage vs cookies vs IndexedDB — when do you use each?
Take a moment.
Say your answer out loud or write it down first — retrieval is what makes it stick. Reveal when you're ready.
Short answer
Cookies (~4KB) auto-attach to requests — use for session ids with HttpOnly/Secure/SameSite. localStorage (~5MB, string key-value, persists) suits UI prefs and caches. sessionStorage is the same but dies with the tab — good for per-tab flow state. IndexedDB is the async NoSQL store for large structured offline data.
Detailed explanation
All four live in the browser but serve different jobs. Cookies are tiny and unique in one way: they ride along on HTTP requests automatically, which makes them the session mechanism — and a CSRF vector, hence the flags. Never store large or XSS-sensitive secrets in the other three, because any injected script reads them freely (HttpOnly cookies being the exception). localStorage persists across restarts on the same origin, synchronous API, roughly 5MB — fine for theme, drafts, and cached API blobs; its sync reads block the main thread, so keep values small. sessionStorage shares the API but is scoped to the tab lifetime — ideal for multi-step form state that shouldn't leak between tabs. IndexedDB is the heavyweight: asynchronous, transactional, stores megabytes-to-gigabytes of structured records with indexes — the database behind offline-first apps (email clients, editors), usually wrapped by libraries like Dexie or idb. Eviction reality: browsers may clear site storage under disk pressure (Safari aggressively), so treat client storage as cache, not source of truth, and always handle missing data gracefully.
Example
// UI prefs: small, non-sensitive → localStorage
localStorage.setItem('theme', 'dark');
// Per-tab wizard state → sessionStorage (dies with the tab)
sessionStorage.setItem('checkout-step', '2');
// ❌ Never: session tokens readable by any XSS payload
// localStorage.setItem('token', jwt); // stealable via document access
// ✅ Instead: HttpOnly + Secure + SameSite cookie set by the server
// Large offline dataset → IndexedDB (async, indexed)
// const db = await openDB('mail', 1, { upgrade(d) { d.createObjectStore('threads'); } });Interview tip
Organize by the decision: needs server round-trip → cookies; small UI state → web storage; big structured offline data → IndexedDB. The JWT-in-localStorage trap is the expected follow-up — answer with HttpOnly cookies.
Common mistakes
- Keeping auth tokens in localStorage where XSS can steal them.
- Treating client storage as durable — browsers can evict it.
- Blocking the main thread with huge localStorage reads.