Day 15: localStorage, sessionStorage & JSON
✅ Today you will learn
- localStorage vs sessionStorage vs cookies vs IndexedDB
- setItem, getItem, removeItem – strings only
- Saving objects and arrays with JSON
- A safe storage helper with defaults, versioning and try/catch
- Real uses: cart, theme, form drafts – and what never to store
🏢 In the real world
A customer fills her cart with 4 sweets, gets a phone call, closes the tab – and comes back to an empty cart. She leaves. Studies of online shops consistently show that a remembered cart brings back a meaningful share of buyers. Remembering a cart, the chosen theme and a half-typed message is all done with Web Storage.
Where can a browser keep data?
| Storage | Lifetime | Size | Sent to server? | Use for |
|---|---|---|---|---|
| localStorage ⭐ | Until deleted | ~5 MB per site | No | Cart, theme, preferences, drafts |
| sessionStorage | Until the tab closes | ~5 MB | No | One-time wizard steps, a tab’s temporary state |
| Cookies | Set by expiry | ~4 KB each | Yes, with every request | Login sessions (HttpOnly, set by the server) |
| IndexedDB | Until deleted | Large (hundreds of MB) | No | Offline apps, big data, files |
The API: strings in, strings out
// Node.js has no localStorage, so this demo creates a tiny stand-in.
// In the browser, delete these 7 lines – localStorage already exists.
const localStorage = (() => {
const data = new Map();
return { getItem: k => (data.has(k) ? data.get(k) : null), setItem: (k, v) => data.set(k, String(v)),
removeItem: k => data.delete(k), clear: () => data.clear(), get length() { return data.size; },
key: i => [...data.keys()][i] ?? null };
})();
localStorage.setItem('theme', 'dark');
localStorage.setItem('visits', 3); // stored as the STRING "3"
console.log(localStorage.getItem('theme'));
console.log(typeof localStorage.getItem('visits'), Number(localStorage.getItem('visits')) + 1);
console.log(localStorage.getItem('missing')); // null when not found
localStorage.setItem('cart', { id: 1, qty: 2 }); // ❌ objects become "[object Object]"
console.log(localStorage.getItem('cart'));
localStorage.setItem('cart', JSON.stringify([{ id: 1, qty: 2 }])); // ✅
const cart = JSON.parse(localStorage.getItem('cart'));
console.log(cart[0].qty, Array.isArray(cart));
localStorage.removeItem('theme');
console.log(localStorage.length, 'keys left');Safari private mode, a full quota, disabled cookies or a sandboxed frame make localStorage throw an error. One uncaught error stops your whole script – so always wrap storage access in try…catch.
A safe, reusable storage helper
// Node.js has no localStorage, so this demo creates a tiny stand-in.
// In the browser, delete these 7 lines – localStorage already exists.
const localStorage = (() => {
const data = new Map();
return { getItem: k => (data.has(k) ? data.get(k) : null), setItem: (k, v) => data.set(k, String(v)),
removeItem: k => data.delete(k), clear: () => data.clear(), get length() { return data.size; },
key: i => [...data.keys()][i] ?? null };
})();
const store = {
get(key, fallback) {
try {
const raw = localStorage.getItem(key);
return raw === null ? fallback : JSON.parse(raw);
} catch (err) {
return fallback; // corrupted JSON or storage blocked
}
},
set(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (err) {
return false; // quota full or blocked
}
},
remove(key) {
try { localStorage.removeItem(key); } catch (err) { /* ignore */ }
},
};
store.set('ss-cart', { version: 2, items: [{ id: 1, qty: 2 }, { id: 4, qty: 1 }] });
console.log(store.get('ss-cart', { version: 2, items: [] }).items.length, 'items');
console.log(store.get('ss-theme', 'light')); // default when missing
localStorage.setItem('broken', '{not json');
console.log(store.get('broken', 'safe default'));Versioning saved data
// Node.js has no localStorage, so this demo creates a tiny stand-in.
// In the browser, delete these 7 lines – localStorage already exists.
const localStorage = (() => {
const data = new Map();
return { getItem: k => (data.has(k) ? data.get(k) : null), setItem: (k, v) => data.set(k, String(v)),
removeItem: k => data.delete(k), clear: () => data.clear(), get length() { return data.size; },
key: i => [...data.keys()][i] ?? null };
})();
// Old site (v1) saved: { "1": 2, "4": 1 } (id → qty)
localStorage.setItem('ss-cart', JSON.stringify({ 1: 2, 4: 1 }));
function loadCart() {
let saved;
try { saved = JSON.parse(localStorage.getItem('ss-cart')); } catch (e) { saved = null; }
if (!saved) return { version: 2, items: [] };
if (saved.version === 2) return saved;
// migrate v1 → v2 so returning customers keep their cart
const items = Object.entries(saved).map(([id, qty]) => ({ id: Number(id), qty }));
const migrated = { version: 2, items };
localStorage.setItem('ss-cart', JSON.stringify(migrated));
return migrated;
}
console.log(JSON.stringify(loadCart()));Use names like ss-cart, ss-theme. Every script on the same domain shares one localStorage – prefixes avoid clashes with plugins or other apps on subfolders.
Real task: theme + draft autosave
<button id="theme">🌓 Toggle theme</button>
<p><label for="msg">Message for Sharma Sweets</label><br>
<textarea id="msg" rows="3" cols="40" placeholder="Type – it is saved as you go"></textarea></p>
<p id="status" role="status"></p>
<script>
// safe helpers (same as above, shortened)
const store = {
get(k, d) { try { const r = localStorage.getItem(k); return r === null ? d : JSON.parse(r); } catch (e) { return d; } },
set(k, v) { try { localStorage.setItem(k, JSON.stringify(v)); return true; } catch (e) { return false; } },
};
const status = document.querySelector('#status');
// theme
let theme = store.get('ss-theme', 'light');
const apply = () => { document.body.style.background = theme === 'dark' ? '#111827' : '#fff'; document.body.style.color = theme === 'dark' ? '#e5e7eb' : '#111827'; };
apply();
document.querySelector('#theme').addEventListener('click', () => {
theme = theme === 'dark' ? 'light' : 'dark';
const saved = store.set('ss-theme', theme);
apply();
status.textContent = saved ? 'Theme saved ✅' : 'Theme changed (saving is blocked in this preview – works on a real page)';
});
// draft autosave
const msg = document.querySelector('#msg');
msg.value = store.get('ss-draft', '');
msg.addEventListener('input', () => {
const ok = store.set('ss-draft', msg.value);
status.textContent = ok ? 'Draft saved ✓' : 'Draft kept in memory only (storage blocked here)';
});
</script>What you must NEVER store
OK in localStorage
- Cart items and quantities
- Theme, language, last filter
- Form drafts (non-sensitive)
- Recently viewed products
Never in localStorage
- Passwords or OTPs
- Card numbers, UPI PINs, Aadhaar
- Long-lived login tokens (any script can read them – XSS)
- Prices used for billing – always recalculate on the server
Bonus: sync between tabs
// Fires in OTHER tabs of the same site when localStorage changes
window.addEventListener('storage', (e) => {
if (e.key === 'ss-cart') {
cart = JSON.parse(e.newValue || '{"version":2,"items":[]}');
renderCartBadge(); // the cart count updates in every open tab
}
});🏅 Job skills unlocked today
Practice tasks
🏋️ Practice – do it yourself
Show solution
// Node.js has no localStorage, so this demo creates a tiny stand-in.
// In the browser, delete these 7 lines – localStorage already exists.
const localStorage = (() => {
const data = new Map();
return { getItem: k => (data.has(k) ? data.get(k) : null), setItem: (k, v) => data.set(k, String(v)),
removeItem: k => data.delete(k), clear: () => data.clear(), get length() { return data.size; },
key: i => [...data.keys()][i] ?? null };
})();
function visit() {
const n = Number(localStorage.getItem('ss-visits') || 0) + 1;
localStorage.setItem('ss-visits', n);
return n >= 3 ? `Welcome back! Visit #${n} – here is 5% off 🎁` : `Visit #${n}`;
}
console.log(visit());
console.log(visit());
console.log(visit());Show solution
// Node.js has no localStorage, so this demo creates a tiny stand-in.
// In the browser, delete these 7 lines – localStorage already exists.
const localStorage = (() => {
const data = new Map();
return { getItem: k => (data.has(k) ? data.get(k) : null), setItem: (k, v) => data.set(k, String(v)),
removeItem: k => data.delete(k), clear: () => data.clear(), get length() { return data.size; },
key: i => [...data.keys()][i] ?? null };
})();
function remember(id) {
let list = JSON.parse(localStorage.getItem('ss-recent') || '[]');
list = [id, ...list.filter(x => x !== id)].slice(0, 5);
localStorage.setItem('ss-recent', JSON.stringify(list));
return list;
}
[3, 1, 4, 1, 5, 9, 2].forEach(id => remember(id));
console.log(JSON.parse(localStorage.getItem('ss-recent')));Quick quiz
🧠 Quick Quiz – test yourself
Interview corner
💼 Interview Corner
localStorage vs sessionStorage vs cookies?
localStorage persists per origin until cleared; sessionStorage lasts for the tab session; both ~5 MB, synchronous, never sent to the server. Cookies are small, sent with every request, can be HttpOnly/Secure/SameSite – right for server sessions.
Why is storing JWTs in localStorage risky?
Any JavaScript running on the page – including injected XSS code or a compromised third-party script – can read it and send it to an attacker. HttpOnly, Secure, SameSite cookies are not readable by JavaScript.
Summary
- localStorage persists, sessionStorage per tab; both store strings.
- JSON.stringify to save objects, JSON.parse to read.
- Wrap access in try…catch, use defaults, prefixes and versions.
- Never store secrets; recalculate prices on the server.
Day 16: scope, hoisting and closures – the concepts interviewers love and that power counters, caches and private data.
