By Rahul — Google Frontend Engineer
Quick Comparison
Feature | Cookie | localStorage | sessionStorage
-----------------+----------------+----------------+----------------
Size limit | ~4KB | ~5-10MB | ~5-10MB
Sent to server | Yes (auto) | No | No
Expiry | Set by header | Never | Tab close
Scope | Domain + path | Domain | Tab + domain
Accessible by JS | Yes (if no | Yes | Yes
| HttpOnly) | |Cookies
The oldest storage mechanism. Sent with EVERY HTTP request to the matching domain. This makes them ideal for authentication tokens but terrible for large data (you would send 4KB with every request).
// Setting a cookie
document.cookie = "token=abc123; path=/; max-age=86400; Secure; SameSite=Strict";
// Reading cookies (annoying API)
const cookies = Object.fromEntries(
document.cookie.split('; ').map(c => c.split('='))
);
// Deleting — set max-age to 0
document.cookie = "token=; max-age=0";Critical Cookie Attributes
- HttpOnly: JavaScript cannot access it. Prevents XSS from stealing tokens
- Secure: Only sent over HTTPS
- SameSite: Controls cross-site sending (Lax, Strict, None)
- Path: Cookie only sent for requests matching this path
- Domain: Which domains receive the cookie
localStorage
Persistent key-value storage. Data survives browser restarts. Shared across all tabs on the same domain.
// Simple API
localStorage.setItem('theme', 'dark');
const theme = localStorage.getItem('theme');
localStorage.removeItem('theme');
localStorage.clear();
// Store objects (must serialize)
localStorage.setItem('user', JSON.stringify({ name: 'Rahul' }));
const user = JSON.parse(localStorage.getItem('user'));sessionStorage
Same API as localStorage but scoped to the browser tab. Data is cleared when the tab closes. Each tab has its own isolated storage.
// Per-tab state
sessionStorage.setItem('formDraft', JSON.stringify(formData));
// Opens new tab → new tab has empty sessionStorage
// Closes tab → data is goneWhen to Use Which
- Auth tokens: HttpOnly cookies (NOT localStorage — XSS can steal localStorage)
- User preferences: localStorage (theme, language, notification settings)
- Form drafts: sessionStorage (auto-save current tab's form state)
- Shopping cart: localStorage (persist across sessions)
- Multi-step wizard state: sessionStorage (scoped to current flow)
Production Issues
- localStorage is synchronous: Reading 5MB of data blocks the main thread. Keep stored data small or use IndexedDB for large datasets
- Safari private mode: localStorage has a 0 byte quota in some older versions. Always wrap in try/catch
- Storage events:
window.addEventListener('storage', handler)fires in OTHER tabs when localStorage changes. Useful for cross-tab communication but easy to forget - Third-party cookie blocking: Safari ITP and Firefox ETP aggressively block third-party cookies. If your auth relies on cross-domain cookies, it may break
Summary
Cookies for auth tokens (HttpOnly, Secure). localStorage for persistent user preferences. sessionStorage for per-tab temporary state. Never store sensitive data in localStorage — it is accessible to any XSS attack.