Web Storage API gives browsers two key-value stores: localStorage (persistent across tabs and sessions) and sessionStorage (tab-scoped, cleared on close)
Both share identical API: setItem, getItem, removeItem, clear — only the lifecycle and scope differ
All stored values must be strings — use JSON.stringify()/JSON.parse() for objects and arrays
Storage limit is ~5-10MB per origin; exceeding throws a QuotaExceededError
Biggest mistake: assuming sessionStorage survives tab-to-tab navigation — it does, but only within the same tab
Plain-English First
Imagine your browser is a desk at work. LocalStorage is like a personal drawer in that desk — you lock it at the end of the day, come back the next morning, and everything is still there. SessionStorage is like a sticky note on your monitor — super useful while you're working, but when you shut down your computer and come back, the note is gone. Both let your browser remember things about you, but one remembers forever and one forgets when you leave.
Every time you visit a website, close the tab, and come back to find your shopping cart still full or your dark-mode setting still on — that is browser storage doing its job quietly in the background. Without it, every page refresh would wipe the slate clean and you'd have to log back in, re-select your preferences, and re-fill your forms from scratch. Browser storage is what turns a website from a goldfish-memory stranger into a place that actually remembers you.
Before LocalStorage and SessionStorage existed, developers had to shove everything into cookies — tiny text files that were sent back and forth to the server on every single request. That was wasteful, slow, and had a laughably small size limit. The Web Storage API (which gives us LocalStorage and SessionStorage) was introduced to solve that: give the browser a proper place to hold onto data locally, without pestering the server every five seconds.
By the end of this article you'll know exactly how to save data to both LocalStorage and SessionStorage, read it back, update it, and delete it. You'll understand which one to reach for and when, you'll avoid the mistakes that trip up most beginners, and you'll be able to answer the questions interviewers love to ask about browser storage.
What Is the Web Storage API and Why Does It Exist?
The Web Storage API is a set of tools built into every modern browser that lets your JavaScript code save information directly inside the user's browser. No server needed. No databases. Just your code and the browser.
It lives on the global window object, which means you can access it anywhere in your JavaScript without importing anything. window.localStorage and window.sessionStorage are always available — though in practice you just type localStorage and sessionStorage because the browser assumes window for you.
All data is stored as key-value pairs, exactly like a labelled filing cabinet. The key is the label on the folder, and the value is the document inside it. You decide both. The critical rule: both the key and the value MUST be strings. That means if you want to store a number or an object, you need to convert it to a string first — we'll cover exactly how to do that.
LocalStorage persists until the user or your code explicitly clears it. It survives the browser being closed, the computer being restarted, anything short of the user clearing their browser data. SessionStorage is scoped to a single browser tab and a single session — the moment that tab is closed, the data is gone. Same origin, different tab? Different SessionStorage. That distinction matters enormously in real apps.
webStorageBasics.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ─────────────────────────────────────────────// Web Storage API — Getting Your Bearings// Run this in your browser's DevTools console// ─────────────────────────────────────────────// Check that localStorage is available in this browserif (typeof localStorage !== 'undefined') {
console.log('localStorage is supported ✅');
} else {
console.log('localStorage is NOT supported ❌');
}
// localStorage and sessionStorage live on the window object// These two lines do exactly the same thing:
console.log(window.localStorage === localStorage); // true — same reference
console.log(window.sessionStorage === sessionStorage); // true — same reference// Both storages have the same API methods:// .setItem(key, value) → save data// .getItem(key) → read data// .removeItem(key) → delete one item// .clear() → delete everything// .length → how many items are stored
console.log('localStorage item count:', localStorage.length);
console.log('sessionStorage item count:', sessionStorage.length);
Where to practise this:
Open any webpage, press F12 (or Cmd+Option+I on Mac) to open DevTools, click the 'Console' tab, and type any of these examples directly. You can also inspect what's stored by clicking the 'Application' tab and looking under 'Storage' on the left sidebar.
Production Insight
Private browsing mode in Safari blocks localStorage entirely — setItem throws an error.
Always wrap storage calls in try-catch so your app degrades gracefully.
Rule: never assume storage is available; check with 'typeof' or catch errors.
Key Takeaway
Web Storage is a client-only key-value store with no expiry.
localStorage = persistent across sessions; sessionStorage = per-tab, cleared on close.
Both require string values — objects need JSON serialisation.
Saving, Reading, Updating and Deleting Data — The Full CRUD Lifecycle
There are four things you'll do with browser storage all the time: Create (save), Read (retrieve), Update (overwrite), and Delete (remove). Let's walk through all four with a realistic example — saving a user's display name and theme preference.
setItem(key, value) saves a piece of data. If an item with that key already exists, it silently overwrites it — that's your update operation covered for free.
getItem(key) reads the data back. If the key doesn't exist, it returns null — not undefined, not an error — null. Always handle that case, or your app will break the first time a new user visits.
removeItem(key) deletes one specific item. clear() wipes everything stored by your origin in that storage type. Be careful with clear() — it nukes all keys, not just the ones your app set.
The biggest beginner trip-up: storing objects and arrays. setItem converts whatever you pass to a string, so if you do localStorage.setItem('user', {name: 'Alex'}), you get the useless string '[object Object]' stored. The fix is JSON.stringify() before saving and JSON.parse() after reading — always.
storageCRUD.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// ─────────────────────────────────────────────// Full CRUD with LocalStorage// Realistic example: saving user preferences// ─────────────────────────────────────────────// ── CREATE (Save) ─────────────────────────────// Saving a simple string — no conversion needed
localStorage.setItem('username', 'Alex');
localStorage.setItem('theme', 'dark');
// Saving a number — must convert to string
localStorage.setItem('loginCount', String(5));
// Saving an object — MUST use JSON.stringify()const userPreferences = {
language: 'en',
notifications: true,
fontSize: 16
};
localStorage.setItem('preferences', JSON.stringify(userPreferences));
console.log('Items saved! Total stored:', localStorage.length);
// ── READ (Retrieve) ───────────────────────────const storedUsername = localStorage.getItem('username');
console.log('Username:', storedUsername); // 'Alex'// Reading a key that doesn't exist returns null — always guard against thisconst storedAvatar = localStorage.getItem('avatar');
console.log('Avatar (not set):', storedAvatar); // null// Reading an object — MUST use JSON.parse() to convert back from stringconst rawPreferences = localStorage.getItem('preferences');
const parsedPreferences = JSON.parse(rawPreferences);
console.log('Font size:', parsedPreferences.fontSize); // 16
console.log('Notifications on?', parsedPreferences.notifications); // true// Safe pattern: handle the case where the item might not exist yetconst safePreferences = JSON.parse(localStorage.getItem('preferences')) || {};
console.log('Safe read result:', safePreferences);
// ── UPDATE (Overwrite) ────────────────────────// setItem on an existing key simply overwrites it — no separate update method
localStorage.setItem('theme', 'light'); // was 'dark', now 'light'
console.log('Updated theme:', localStorage.getItem('theme')); // 'light'// Updating a nested property inside a stored object:const currentPrefs = JSON.parse(localStorage.getItem('preferences'));
currentPrefs.fontSize = 18; // change just this one property
localStorage.setItem('preferences', JSON.stringify(currentPrefs)); // save it back
console.log('Updated font size:', JSON.parse(localStorage.getItem('preferences')).fontSize); // 18// ── DELETE (Remove) ───────────────────────────// Remove one specific item
localStorage.removeItem('loginCount');
console.log('After removeItem, loginCount:', localStorage.getItem('loginCount')); // null// Remove EVERYTHING stored by your site (use with caution!)// localStorage.clear();// console.log('After clear(), item count:', localStorage.length); // 0
Watch Out: The JSON.stringify / JSON.parse Rule
Forgetting JSON.stringify when saving an object is the #1 beginner mistake. You won't get an error — you'll silently store the useless string '[object Object]'. Always stringify before saving objects or arrays, and always parse after reading them back.
Production Insight
In production, always use a safe read pattern: JSON.parse(getItem(key)) || {}. This prevents TypeError when the key doesn't exist.
Also consider wrapping CRUD operations in a try-catch: localStorage can throw in private browsing or when quota is exceeded.
Rule: treat storage operations as fallible — never assume they succeed.
Key Takeaway
setItem overwrites existing keys silently — update is the same as create.
getItem returns null for missing keys, not undefined.
Always JSON.stringify before storing objects, and JSON.parse after reading them.
SessionStorage — Same API, Completely Different Lifespan
SessionStorage uses the exact same methods as LocalStorage — setItem, getItem, removeItem, clear. You can copy-paste code between them just by swapping the variable name. The difference is entirely about lifespan and scope.
A 'session' in browser terms means one tab, open and alive. The moment that tab is closed — not refreshed, not navigated away from, but actually closed — the session ends and all SessionStorage data for that tab is wiped. If a user opens your site in two tabs, each tab has its own completely separate SessionStorage. They cannot see each other's data.
This makes SessionStorage perfect for things that should be temporary and tab-specific: a multi-step form where you don't want data leaking between two open copies of the same form, a temporary draft the user hasn't committed yet, or step-by-step wizard data that should reset if the user opens a fresh tab.
LocalStorage, by contrast, is shared across all tabs from the same origin. Open your site in five tabs — they all read and write to the same LocalStorage. That's powerful for syncing things like login state or theme preferences, but it means changes in one tab immediately affect all others.
sessionStorageExample.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// ─────────────────────────────────────────────// SessionStorage — Tab-Scoped Temporary Storage// Great example: saving a multi-step form draft// ─────────────────────────────────────────────// ── Saving form progress step by step ────────// User fills in Step 1 of a signup formconst stepOneData = {
firstName: 'Jordan',
lastName: 'Rivera',
email: 'jordan@example.com'
};
sessionStorage.setItem('signupStep1', JSON.stringify(stepOneData));
console.log('Step 1 saved to sessionStorage');
// User moves to Step 2const stepTwoData = {
plan: 'pro',
billingCycle: 'annual'
};
sessionStorage.setItem('signupStep2', JSON.stringify(stepTwoData));
console.log('Step 2 saved to sessionStorage');
// ── Reading it back on page load ──────────────// On any page reload within the same tab, this data is still hereconst savedStep1 = JSON.parse(sessionStorage.getItem('signupStep1'));
if (savedStep1) {
console.log('Resuming form for:', savedStep1.firstName, savedStep1.lastName);
} else {
console.log('No saved form data — starting fresh');
}
// ── Checking the difference in scope ─────────// Save something to BOTH storages so we can compare their lifespans
localStorage.setItem('persistentNote', 'I survive tab closes and restarts');
sessionStorage.setItem('temporaryNote', 'I disappear when this tab closes');
console.log('localStorage note:', localStorage.getItem('persistentNote'));
console.log('sessionStorage note:', sessionStorage.getItem('temporaryNote'));
// Now close this tab and open a new one to the same page...// localStorage.getItem('persistentNote') → still 'I survive tab closes and restarts'// sessionStorage.getItem('temporaryNote') → null (it's gone)// ── Cleanup after form submission ─────────────functionhandleFormSubmissionSuccess() {
// Once the user successfully submits, clean up the drafts
sessionStorage.removeItem('signupStep1');
sessionStorage.removeItem('signupStep2');
console.log('Form submitted — draft data cleared from sessionStorage');
}
handleFormSubmissionSuccess();
console.log('Step1 after cleanup:', sessionStorage.getItem('signupStep1')); // null
Pro Tip: SessionStorage for Sensitive Temporary Data
If you need to hold onto something sensitive (like a one-time token or a temporary auth code) only for the duration of a user's interaction with a single tab, SessionStorage is safer than LocalStorage because it auto-clears when the tab closes — reducing the window of exposure.
Production Insight
A common production bug: using sessionStorage for 'remember me' functionality — users lose their data when they accidentally close the tab.
Rule: sessionStorage is for ephemeral state that should not leak across tabs.
If you need tab-specific data that survives page refreshes but not the tab closing, sessionStorage is exactly right.
Key Takeaway
SessionStorage is isolated per tab — two tabs on the same site have completely separate data.
Data survives page refreshes but NOT tab closes.
Use for temporary, single-tab workflows like multi-step forms or wizards.
Listening for Storage Changes Across Tabs with the storage Event
Here's something most beginner tutorials skip entirely: LocalStorage fires a built-in browser event called storage whenever it changes — but only in OTHER tabs, not the one that made the change. This sounds weird at first, but it's actually brilliant. It lets you keep multiple open tabs in sync without polling a server.
Imagine a user has your app open in two tabs. They switch to dark mode in Tab A. Tab B fires the storage event and can immediately update its own UI to match — no page refresh, no server call, no complex setup.
This only works for LocalStorage, not SessionStorage (since SessionStorage is already isolated per-tab, there's nothing to sync). And remember: the event fires in the OTHER tabs, not the one that triggered the change. If you want to react in the same tab, just handle it directly in your code after calling setItem.
This pattern is genuinely useful for things like: keeping a login/logout state in sync across tabs, syncing a shopping cart count in the header, or broadcasting a notification that the user's session has expired.
storageSyncAcrossTabs.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// ─────────────────────────────────────────────// The 'storage' Event — Syncing Tabs in Real Time//// HOW TO TEST THIS:// 1. Open your site in Tab A and Tab B// 2. Run this code in BOTH tabs// 3. In Tab A, run: localStorage.setItem('cartCount', '3')// 4. Watch Tab B's console react automatically// ─────────────────────────────────────────────// Listen for any localStorage change that originates from ANOTHER tab
window.addEventListener('storage', function(storageEvent) {
// storageEvent contains useful info about what changed:
console.log('Storage changed in another tab!');
console.log('Key that changed:', storageEvent.key); // e.g. 'cartCount'
console.log('Old value:', storageEvent.oldValue); // e.g. null
console.log('New value:', storageEvent.newValue); // e.g. '3'
console.log('Which storage:', storageEvent.storageArea); // localStorage object
console.log('Origin:', storageEvent.url); // URL of the tab that changed it// React to a specific key changingif (storageEvent.key === 'cartCount') {
const newCount = parseInt(storageEvent.newValue, 10);
updateCartBadge(newCount); // call your UI update function
}
// React to the user logging out in another tabif (storageEvent.key === 'authToken' && storageEvent.newValue === null) {
console.log('User logged out in another tab — redirecting to login page...');
// window.location.href = '/login';
}
});
// Simulated UI update functionfunctionupdateCartBadge(count) {
console.log(`Cart badge updated to: ${count} item(s)`);
// In a real app you'd do something like:// document.getElementById('cart-badge').textContent = count;
}
// Simulating a change from another tab (for demonstration):// In the real scenario, Tab A does this:
localStorage.setItem('cartCount', '3');
// Tab B's 'storage' listener fires automatically — Tab A's does NOT.
console.log('This tab set cartCount. The storage event fires in OTHER tabs, not this one.');
Interview Gold: The storage Event
Most candidates have never heard of the storage event — knowing it exists and explaining the cross-tab sync use case will genuinely impress an interviewer. It shows you've thought beyond basic get/set and understand real-world multi-tab user behaviour.
Production Insight
The storage event does not fire in the same tab that made the change — only in other tabs.
If you need the same-tab UI to react immediately, call your update function right after setItem.
Rule: never rely on the storage event for same-tab reactivity; use it only for cross-tab synchronisation.
Key Takeaway
LocalStorage fires a 'storage' event in OTHER tabs when data changes — perfect for cross-tab sync.
SessionStorage never fires this event because it's never shared across tabs.
Use the event to sync UI state like theme or cart count without server polling.
Storage Limits, Quota Exceeded Errors, and Handling Private Browsing
Every browser enforces a maximum storage size per origin. Limits vary: most Chrome and Firefox versions allow ~10MB for localStorage and ~5MB for sessionStorage. Mobile browsers tend to be stricter — Safari on iOS allocates only 5MB total. When you exceed the limit, the browser throws a QuotaExceededError. Your code must handle this gracefully.
Private browsing causes another headache: Safari (and some other browsers in incognito mode) disable localStorage entirely. Any call to setItem throws an error. Firefox wraps it with up to 5MB available, but Chrome also allows it. The key rule: your app should never crash if storage fails — wrap all write operations in a try-catch and degrade to in-memory storage or notify the user.
To size your stored data, you can iterate over all keys and sum the character lengths. The limit is measured in UTF-16 characters, not bytes — but roughly, each character is 2 bytes. So 10MB ≈ 5 million characters.
storageLimitsAndHandling.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// ─────────────────────────────────────────────// Handling Quota Exceeded & Private Browsing// ─────────────────────────────────────────────// ── Safely write to localStorage ─────────────functionsafeSetItem(key, value) {
try {
localStorage.setItem(key, value);
returntrue;
} catch (e) {
if (e.name === 'QuotaExceededError') {
console.warn('Storage full — consider clearing old data or using in-memory fallback');
// Option: prompt user to clear storage
} elseif (e.name === 'SecurityError') {
console.warn('Storage unavailable (private browsing?) — using in-memory fallback');
// Fallback: save to a JavaScript object in memory
}
returnfalse;
}
}
// ── Calculate current storage usage ──────────functiongetStorageUsage(storage) {
let totalLength = 0;
for (let i = 0; i < storage.length; i++) {
const key = storage.key(i);
const value = storage.getItem(key);
totalLength += key.length + value.length;
}
return totalLength; // characters (UTF-16)
}
console.log('Current localStorage usage (chars):', getStorageUsage(localStorage));
// ── Clear old items by key pattern ───────────// Example: remove all keys starting with 'log_'functionclearMatching(pattern) {
const keysToRemove = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.startsWith(pattern)) {
keysToRemove.push(key);
}
}
keysToRemove.forEach(key => localStorage.removeItem(key));
console.log(`Removed ${keysToRemove.length} keys matching '${pattern}'`);
}
clearMatching('log_');
Safari Private Browsing = Storage Block
In Safari's private browsing mode, localStorage is completely disabled. Any call to setItem throws a SecurityError. Wrap your storage code in try-catch and always provide a fallback (like an in-memory object) so your app still works.
Production Insight
QuotaExceededError often hits silently after months of use as cached data accumulates.
Implement a storage size monitor: log usage to your analytics when it exceeds 80% of the limit.
Rule: never assume storage write succeeded — always catch the error and log it.
Key Takeaway
localStorage limit is ~5-10MB per origin; sessionStorage ~5MB.
Safari blocks localStorage entirely in private browsing — wrap with try-catch.
Use getStorageUsage() to monitor how close you are to the quota.
Security Considerations: What Not to Store in Web Storage
Web Storage is not encrypted. It's plain text stored on disk, accessible to any JavaScript running on the same origin. This means if your site gets even a tiny XSS vulnerability, an attacker can read everything you've stored. Never put passwords, credit card numbers, or long-lived authentication tokens in localStorage or sessionStorage.
For session tokens, use HttpOnly cookies set by the server. Those cookies are not accessible to JavaScript at all — they're sent automatically with HTTP requests and are protected from XSS. For non-sensitive data like theme preferences, localStorage is fine.
And remember: there's no built-in encryption for web storage. If you must store sensitive data (like an access token), consider encrypting it before saving, but even then, the encryption key lives in JavaScript and is vulnerable. The safest answer: don't store secrets in web storage.
storageSecurity.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// ─────────────────────────────────────────────// Security Note: Never store secrets in Web Storage// ─────────────────────────────────────────────// ❌ DANGEROUS: Never do this
localStorage.setItem('jwtToken', 'eyJhbGciOi...');
localStorage.setItem('creditCard', '4111111111111111');
// ✅ Better: Use sessionStorage for short-lived non-secret tokens,// but still avoid storing access tokens that grant prolonged access.// Better yet: use HttpOnly cookies for auth.// ── Encrypting sensitive data (if you absolutely must store in LS)// Note: the secret key is still in JS memory — at risk during XSS.functionencryptAndStore(key, data, secret) {
const encrypted = btoa(JSON.stringify(data)); // simplistic base64 encoding
localStorage.setItem(key, encrypted);
}
functiondecryptAndRead(key, secret) {
const encrypted = localStorage.getItem(key);
if (!encrypted) returnnull;
returnJSON.parse(atob(encrypted));
}
// ── Best practice: use server-side sessions with HttpOnly cookies// Server sets cookie with httpOnly flag, JS can't read it.// Data is never stored in web storage.
XSS + localStorage = Data Leak
If an attacker injects a script into your page, they can read all localStorage and sessionStorage data. That's why passwords, tokens, and PII must never be stored there. Use HttpOnly cookies for authentication secrets — they're invisible to JavaScript.
Production Insight
A common production mistake: storing refresh tokens in localStorage to avoid cookie overhead.
If an XSS payload runs, attacker exfiltrates the token and gains persistent access.
Rule: if you can't tolerate the data being publicly readable, don't put it in web storage.
Key Takeaway
Web Storage is not encrypted — any JavaScript on your page can read it.
Never store passwords, credit card numbers, or long-lived auth tokens.
For session credentials, use HttpOnly cookies set by the server.
Decision Tree: LocalStorage vs SessionStorage — Choose the Right One
This tree helps you pick the correct storage type based on your application's requirements. Ask yourself: does this data need to survive a browser restart? Does it need to be shared across tabs? Is it sensitive?
Mental Model: The Office Desk Analogy
LocalStorage = the locked drawer: everything stays even after you leave and come back the next day.
SessionStorage = a sticky note on your screen: it's there while you're at your desk, gone when you shut down.
Cookies = a postcard sent to the server every time you talk to it: everyone can read the front, but it's the only way for the server to know it's you.
In-memory variable = a thought you have right now: forget it the instant you navigate away.
Production Insight
Most teams over-complicate this: if you're asking 'will the user complain if this data is gone when they come back tomorrow?', use LocalStorage.
If you're asking 'do I want this to be tab-private like a form draft?', use SessionStorage.
Rule: start with SessionStorage and only move to LocalStorage if you have a proven need for persistence across sessions.
Key Takeaway
LocalStorage = long-term, cross-tab persistence.
SessionStorage = short-term, tab-scoped, cleared on close.
Sensitive data goes to HttpOnly cookies, not web storage.
LocalStorage vs SessionStorage Decision Tree
IfData must persist after tab close and browser restart
→
UseUse LocalStorage
IfData is temporary and should disappear when tab closes
→
UseUse SessionStorage
IfData must be shared across multiple tabs/windows
→
UseUse LocalStorage (and listen to 'storage' event for sync)
IfData is sensitive (auth tokens, passwords, PII)
→
UseNeither — use HttpOnly cookies on the server side
IfData is for a single page session that must survive page refresh
→
UseUse SessionStorage
Common Pitfalls and How to Avoid Them
Even experienced developers make mistakes with the Web Storage API. The three most common errors: forgetting JSON serialisation, assuming storage is always available, and misinterpreting scope. We cover them in the 'common_mistakes' section below, but here's a quick summary.
First, always use JSON.stringify on objects before saving, and JSON.parse on retrieval. Second, assume storage can fail — wrap writes in try-catch, provide in-memory fallback. Third, remember that sessionStorage is per-tab; don't use it for data you expect to carry across tabs.
Another subtle one: the 'storage' event only fires for localStorage changes from other tabs. If you need to react in the same tab, call your handler directly after setItem.
pitfallsExample.jsJAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// ─────────────────────────────────────────────// Common Pitfalls with Web Storage// ─────────────────────────────────────────────// PITFALL 1: Storing object without stringify
localStorage.setItem('user', { name: 'Alex' });
// Actually stores '[object Object]'
console.log(localStorage.getItem('user')); // '[object Object]'// FIX: Always JSON.stringify
localStorage.setItem('user', JSON.stringify({ name: 'Alex' }));
const user = JSON.parse(localStorage.getItem('user'));
console.log(user.name); // 'Alex'// PITFALL 2: Not handling missing keys (null)
const settings = JSON.parse(localStorage.getItem('settings')); // Might be null// TypeError if you do: settings.theme// FIX: Provide defaultconst safeSettings = JSON.parse(localStorage.getItem('settings')) || {};
// PITFALL 3: Assuming sessionStorage survives tab close
sessionStorage.setItem('cart', 'item1');
// Close tab, open new tab
console.log(sessionStorage.getItem('cart')); // null// FIX: Use localStorage for anything that should persist across tab closes// PITFALL 4: Forgetting that 'storage' event fires in OTHER tabs only
window.addEventListener('storage', (e) => {
console.log('Change from another tab:', e.key);
});
localStorage.setItem('test', 'value'); // This tab won't log the event
Embedded Reference
See the 'common_mistakes' section below for detailed descriptions of each pitfall with symptoms and fixes.
Production Insight
Code reviews often miss the JSON.parse(null) trap because it works in development when the data exists.
It only breaks in production for first-time visitors or after cache clears.
Rule: always use the || {} pattern for storage reads that expect objects.
Key Takeaway
Three non-negotiables: always stringify objects, always handle null reads, always expect storage to fail.
SessionStorage is per-tab — don't use it for cross-tab persistence.
'storage' event fires in other tabs only — code for same-tab reactivity separately.
● Production incidentPOST-MORTEMseverity: high
Lost Shopping Cart When User Clicked 'Open in New Tab'
Symptom
Users report that after clicking a product link (opened in a new tab) and returning to the original tab, their shopping cart is empty. Cart data is present in the new tab but vanishes on the original after the new tab closes.
Assumption
The team assumed sessionStorage was like a global session — they thought data would persist across tabs as long as the browser was open.
Root cause
SessionStorage is strictly tab-scoped. Each tab creates its own isolated sessionStorage object. When the user opened the product link in a new tab, that new tab had its own sessionStorage — separate from the original. Closing the new tab did not affect the original tab's data, but the confusion led users to refresh or navigate, which inadvertently cleared their original session.
Fix
Switch critical cart data to localStorage (since cart should persist across tabs) or use a backend session with a cookie to synchronise cart state across tabs. At minimum, show a clear warning: 'If you open links in new tabs, your cart may not follow you.'
Key lesson
SessionStorage is never shared across tabs — even same-origin tabs have independent stores.
Always validate your storage scope assumption against real user behaviour (opening links in new tabs, using back/forward, etc.).
For data that must survive tab switches, use localStorage or a server-side session.
Production debug guideWhen localStorage or sessionStorage behaves unexpectedly, follow these diagnosis steps.4 entries
Symptom · 01
Data saved with setItem returns null on getItem
→
Fix
Check that the key name matches exactly (case-sensitive). Open DevTools > Application > Storage > LocalStorage (or SessionStorage) to see all stored keys and their values.
Symptom · 02
setItem throws QuotaExceededError
→
Fix
Calculate the total size of stored data (typically 5-10MB limit). Use a loop with measureStorageSpace() (see quick_debug_cheat_sheet). Clear old or unused keys with removeItem.
Symptom · 03
Stored object reads as '[object Object]'
→
Fix
You saved an object without JSON.stringify(). Fix by wrapping with JSON.stringify() on write and JSON.parse() on read.
Symptom · 04
Data works in normal mode but fails in private/incognito
→
Fix
In Safari, private browsing throws an error when trying to setItem. Wrap all setItem calls in a try-catch block and degrade gracefully (e.g., fall back to in-memory object).
★ Quick Debug Cheat Sheet — Web StorageFive-minute diagnosis commands for common browser storage problems.
setItem fails without error−
Immediate action
Open DevTools Console and run localStorage.setItem('test', '1'). If it throws, you'll see the error.
Commands
localStorage.setItem('test', '1')
localStorage.getItem('test')
Fix now
Wrap all setItem calls in try-catch. If quota exceeded, clear old items with localStorage.removeItem('oldKey') or prompt user to clear site data.
getItem returns null unexpectedly+
Immediate action
Run localStorage.key(0) to see the first key. If that returns null, the storage is empty.
Commands
for (let i = 0; i < localStorage.length; i++) { console.log(localStorage.key(i), localStorage.getItem(localStorage.key(i))); }
console.table(localStorage)
Fix now
Check the key spelling and case. Check that you are on the same origin (protocol + domain + port). Clear and re-save under correct key.
Object stored but read as string+
Immediate action
Check the raw value: const raw = localStorage.getItem('myKey'); console.log(typeof raw);
tab closes, browser restarts, computer reboots — until your code or the user explicitly clears it. SessionStorage dies when the tab closes, full stop.
2
All stored values must be strings. Save objects and arrays with JSON.stringify() and read them back with JSON.parse()
skip this and you'll silently store '[object Object]' with no error to warn you.
3
LocalStorage is shared across all tabs from the same origin. SessionStorage is completely isolated to the individual tab that created it
even two tabs on the same page have separate SessionStorages.
4
The storage event lets LocalStorage changes in one tab automatically notify all other same-origin tabs
use this to sync UI state like cart counts or theme preferences without touching a server.
5
Never store passwords, tokens, or credit card numbers in web storage. Use HttpOnly cookies for authentication secrets.
6
Always wrap storage operations in try-catch to handle private browsing and quota exceeded errors gracefully.
Common mistakes to avoid
4 patterns
×
Storing objects without JSON.stringify
Symptom
Calling localStorage.setItem('user', {name: 'Alex'}) later reads back '[object Object]' instead of real data.
Fix
Always wrap objects and arrays in JSON.stringify() before saving, and always call JSON.parse() when reading them back. Example: localStorage.setItem('user', JSON.stringify({name: 'Alex'})) and const user = JSON.parse(localStorage.getItem('user')).
×
Not handling the null case when reading
Symptom
Calling JSON.parse(localStorage.getItem('settings')) on a first-time visitor results in a TypeError because null cannot be parsed.
Fix
Always use a fallback: const settings = JSON.parse(localStorage.getItem('settings')) || {}. The || {} guard means new users get a safe empty object instead of a crash.
×
Storing sensitive data like passwords or full auth tokens in localStorage
Symptom
An XSS vulnerability on your site allows an attacker to read all stored data and exfiltrate user credentials or session tokens.
Fix
Never store passwords, credit card numbers, or long-lived auth tokens in localStorage. Use HttpOnly cookies (set by the server) for sensitive session credentials — they are inaccessible to JavaScript entirely.
×
Assuming sessionStorage is shared across tabs
Symptom
A user opens two tabs of your multi-step form. Filling step 1 in Tab A does not show the data in Tab B, and closing Tab A loses unsaved progress.
Fix
Use localStorage if data must be shared across tabs, or use a server-side approach. If you need tab-scoped data, sessionStorage is correct; just don't expect it to transfer.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01JUNIOR
What is the difference between LocalStorage and SessionStorage, and can ...
Q02JUNIOR
What happens if you try to store a JavaScript object directly in LocalSt...
Q03SENIOR
What is the storage event, which storage type triggers it, and why does ...
Q04SENIOR
How do you handle the case where localStorage is unavailable (e.g., Safa...
Q05SENIOR
Explain why storing an authentication token in localStorage is considere...
Q01 of 05JUNIOR
What is the difference between LocalStorage and SessionStorage, and can you give a concrete example of when you would choose one over the other?
ANSWER
LocalStorage persists until explicitly cleared and is shared across all tabs from the same origin. SessionStorage is cleared when the tab is closed and is isolated per tab. Example: Use SessionStorage for a multi-step checkout form so that each tab handles its own draft and accidental closing wipes it. Use LocalStorage for user theme preferences that should persist across sessions and tabs.
Q02 of 05JUNIOR
What happens if you try to store a JavaScript object directly in LocalStorage without any conversion — what value actually gets stored and why?
ANSWER
The browser calls the object's toString() method, which for plain objects returns the string '[object Object]'. That useless string is stored instead of the actual data. To store objects correctly, you must call JSON.stringify() before setItem, and JSON.parse() after getItem to retrieve the structured data.
Q03 of 05SENIOR
What is the storage event, which storage type triggers it, and why does it only fire in tabs other than the one that made the change?
ANSWER
The 'storage' event fires on the window object whenever a localStorage key is changed, but only in other tabs/windows that are open to the same origin. SessionStorage never triggers this event because it is tab-isolated. The reason it doesn't fire in the same tab is to avoid redundant updates — the tab that made the change already knows what it did. This pattern enables cross-tab synchronisation without infinite loops.
Q04 of 05SENIOR
How do you handle the case where localStorage is unavailable (e.g., Safari private browsing) or the storage quota is exceeded?
ANSWER
Wrap all setItem calls in a try-catch block. Catch TypeError or QuotaExceededError. Provide a fallback like an in-memory object that mimics the storage API. Notify the user if their data cannot be saved. For quota issues, you can clear old keys or prompt the user to clear site storage.
Q05 of 05SENIOR
Explain why storing an authentication token in localStorage is considered insecure. What would you use instead?
ANSWER
localStorage is accessible to any JavaScript running on the page via the same origin. If an XSS vulnerability exists, an attacker can read the token and impersonate the user. Instead, use an HttpOnly cookie set by the server — it is automatically sent with requests but inaccessible to JavaScript, reducing XSS impact. For additional protection, use the Secure and SameSite flags.
01
What is the difference between LocalStorage and SessionStorage, and can you give a concrete example of when you would choose one over the other?
JUNIOR
02
What happens if you try to store a JavaScript object directly in LocalStorage without any conversion — what value actually gets stored and why?
JUNIOR
03
What is the storage event, which storage type triggers it, and why does it only fire in tabs other than the one that made the change?
SENIOR
04
How do you handle the case where localStorage is unavailable (e.g., Safari private browsing) or the storage quota is exceeded?
SENIOR
05
Explain why storing an authentication token in localStorage is considered insecure. What would you use instead?
SENIOR
FAQ · 5 QUESTIONS
Frequently Asked Questions
01
Is LocalStorage safe to store user passwords or tokens?
No — never store passwords in LocalStorage. It's readable by any JavaScript on your page, which makes it vulnerable to XSS attacks. For sensitive session tokens, use HttpOnly cookies set by your server instead — those cannot be accessed by JavaScript at all.
Was this helpful?
02
Does LocalStorage data expire automatically?
No, LocalStorage has no built-in expiry mechanism. It persists until your code calls removeItem or clear, or the user manually clears their browser data. If you need data to expire, you have to implement it yourself by storing a timestamp alongside the data and checking it on read.
Was this helpful?
03
Why does reading from LocalStorage give me null even though I saved data there?
The most common cause is a key mismatch — setItem and getItem are case-sensitive, so 'UserName' and 'username' are different keys. The second cause is being on a different origin (different domain or protocol), since LocalStorage is sandboxed per origin. Check the Application tab in DevTools to see exactly what keys are stored and their exact spelling.
Was this helpful?
04
Can I use LocalStorage in private/incognito mode?
In Chrome and Firefox, private browsing mode still allows LocalStorage (though data is cleared when the private window closes). In Safari, private browsing disables LocalStorage entirely — any setItem call throws an error. Always wrap storage writes in a try-catch block.
Was this helpful?
05
What is the size limit for LocalStorage and SessionStorage?
Most browsers set a limit between 5 and 10 MB per origin for LocalStorage, and around 5 MB for SessionStorage. Mobile browsers tend to have smaller limits. Exceeding the limit throws a QuotaExceededError.