localStorage and sessionStorage
Web Storage APIs — persistence, quota, JSON serialization, privacy modes, and when cookies or IndexedDB fit better.
- javascript
- localstorage
- sessionstorage
- web-storage
localStorage and sessionStorage are synchronous key/value string maps scoped to an origin. They’re perfect for UI prefs and light caches. They’re wrong for secrets, large binary data, and anything that needs querying.
Differences
localStorage |
sessionStorage |
|
|---|---|---|
| Lifetime | until cleared | until tab/window closes |
| Shared tabs | same origin shares | per tab (mostly) |
| Survives restart | yes | no |
localStorage.setItem('theme', 'dark');
localStorage.getItem('theme'); // "dark"
localStorage.removeItem('theme');
localStorage.clear();
sessionStorage.setItem('wizardStep', '2');
Only strings. Objects need JSON:
function setJson(key, value) {
localStorage.setItem(key, JSON.stringify(value));
}
function getJson(key, fallback = null) {
const raw = localStorage.getItem(key);
if (raw == null) return fallback;
try {
return JSON.parse(raw);
} catch {
return fallback;
}
}
setJson('prefs', { density: 'compact' });
Storage events (cross-tab)
window.addEventListener('storage', (e) => {
// fires in *other* documents on the origin, not the writer
if (e.key === 'theme') applyTheme(e.newValue);
});
Useful for syncing logout or theme across tabs.
Quota and errors
try {
localStorage.setItem('cache', bigString);
} catch (err) {
// QuotaExceededError in many browsers
console.warn('storage full', err);
}
Rough per-origin limits are a few MB — not for video. Private browsing may throw or ephemeral-ize storage.
SSR / availability
function storageAvailable(type) {
try {
const s = window[type];
const k = '__test__';
s.setItem(k, '1');
s.removeItem(k);
return true;
} catch {
return false;
}
}
Guard in SSR frameworks — window is missing on the server.
Security
- Readable by any JS on the page → XSS steals tokens. Don’t store session tokens here; prefer HttpOnly cookies.
- Not encrypted at rest.
- Same-origin only — not a cross-site sharing tool.
When to choose something else
| Need | Use |
|---|---|
| Auth session | HttpOnly cookie |
| Large structured offline data | IndexedDB |
| Cache HTTP responses | Cache API |
| Tiny sync prefs | localStorage |
| Tab-scoped draft | sessionStorage |
Performance
Web Storage is synchronous and can block the main thread on large reads/writes. Keep payloads small; debounce writes on rapid updates.
// bad: write on every keystroke without debounce
// good:
const save = debounce((draft) => {
sessionStorage.setItem('draft', draft);
}, 300);
Interview answer
“localStorage persists string key/values per origin; sessionStorage lasts for the tab session. I JSON-encode objects, try/catch quota errors, and listen to storage events for cross-tab sync. I never put auth tokens in web storage because XSS can read them. For large or queryable data I use IndexedDB.”
Related
Versioning stored data
const KEY = 'prefs';
const VERSION = 2;
function loadPrefs() {
const raw = localStorage.getItem(KEY);
if (!raw) return { version: VERSION, theme: 'system' };
try {
const data = JSON.parse(raw);
if (data.version !== VERSION) return migrate(data, VERSION);
return data;
} catch {
localStorage.removeItem(KEY);
return { version: VERSION, theme: 'system' };
}
}
function savePrefs(prefs) {
localStorage.setItem(KEY, JSON.stringify({ ...prefs, version: VERSION }));
}
Schemas evolve — store a version and migrate or drop. Corrupted JSON from partial writes is rare but real; never let a parse throw kill app boot.
Further reading
Related guides
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- Async Iteration and for await...ofAsync iterables, for await...of, and streaming data — how async iterators differ from Promise.all and when to use each.