DNA🌐 Web BrowserBrowser Storage & Caching
ðŸĨšEggBrowserStoragePerformance

Browser Storage & Caching

localStorage, sessionStorage, IndexedDB, cookies, Cache API — each has a purpose. Senior engineers choose the right storage mechanism for the job.

Browser Storage & Caching

"Just use localStorage" is the hallmark of someone who hasn't dealt with storage at scale. Each browser storage mechanism has different capacity, persistence, access patterns, and security implications.

Storage Comparison

StorageCapacityPersistenceAccessSent to server
Cookies~4KBConfigurable expirySyncYes, every request
localStorage~5-10MBUntil clearedSyncNo
sessionStorage~5-10MBUntil tab closesSyncNo
IndexedDBHundreds of MB+Until clearedAsyncNo
Cache APIHundreds of MB+Until clearedAsyncNo

Cookies

Cookies are the only storage mechanism automatically sent with HTTP requests. This makes them the right choice for authentication tokens — and the wrong choice for almost everything else.

// Setting a secure cookie
document.cookie = 'session=abc123; Secure; HttpOnly; SameSite=Strict; Max-Age=86400; Path=/';

Key attributes:

  • HttpOnly — inaccessible to JavaScript (XSS protection)
  • Secure — only sent over HTTPS
  • SameSite=Strict|Lax|None — CSRF protection
  • Max-Age / Expires — lifetime control

Auth tokens belong in HttpOnly cookies, not localStorage. A localStorage token is one XSS vulnerability away from being stolen.

localStorage & sessionStorage

Simple key-value string storage. Synchronous and blocking:

localStorage.setItem('theme', 'dark');
localStorage.getItem('theme'); // 'dark'
 
// Store objects by serializing
localStorage.setItem('prefs', JSON.stringify({ theme: 'dark', lang: 'en' }));
const prefs = JSON.parse(localStorage.getItem('prefs') ?? '{}');

localStorage persists until explicitly cleared. sessionStorage is scoped to the browser tab and dies when it closes.

Pitfalls

  • Synchronous — blocks the main thread. Storing/parsing large JSON payloads causes jank.
  • String-only — everything is serialized. Type safety is your responsibility.
  • Storage events — window.addEventListener('storage', cb) fires in other tabs, not the current one. Useful for cross-tab sync.
  • No expiry — unlike cookies, there's no built-in TTL. You must implement expiration logic manually.

IndexedDB

A full asynchronous database in the browser. Use it for structured data that's too large for localStorage:

const request = indexedDB.open('myApp', 1);
 
request.onupgradeneeded = (event) => {
  const db = event.target.result;
  const store = db.createObjectStore('articles', { keyPath: 'id' });
  store.createIndex('date', 'publishedAt');
};
 
request.onsuccess = (event) => {
  const db = event.target.result;
  const tx = db.transaction('articles', 'readwrite');
  tx.objectStore('articles').put({ id: '1', title: 'Hello', publishedAt: Date.now() });
};

The raw API is verbose. In practice, use a wrapper like idb (by Jake Archibald) for a Promise-based interface:

import { openDB } from 'idb';
 
const db = await openDB('myApp', 1, {
  upgrade(db) {
    db.createObjectStore('articles', { keyPath: 'id' });
  },
});
 
await db.put('articles', { id: '1', title: 'Hello' });
const article = await db.get('articles', '1');

Use IndexedDB for: offline data, large datasets, structured queries, blob storage.

Cache API

Designed for caching HTTP request/response pairs, primarily used with Service Workers for offline support:

const cache = await caches.open('v1');
 
// Cache a response
await cache.put('/api/data', new Response(JSON.stringify(data)));
 
// Cache from network
await cache.add('/api/data');
 
// Match a request
const response = await caches.match('/api/data');

Caching Strategies

StrategyDescriptionUse case
Cache FirstCheck cache, fallback to networkStatic assets, fonts
Network FirstTry network, fallback to cacheAPI data that should be fresh
Stale While RevalidateServe cache, update in backgroundContent that can be briefly stale
// Stale-while-revalidate in a Service Worker
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.open('v1').then(async (cache) => {
      const cached = await cache.match(event.request);
      const fetched = fetch(event.request).then((response) => {
        cache.put(event.request, response.clone());
        return response;
      });
      return cached || fetched;
    })
  );
});

Decision Framework

Need to send data with every HTTP request? → Cookies
Need simple key-value persistence? → localStorage
Need tab-scoped temporary data? → sessionStorage
Need structured data or large storage? → IndexedDB
Need to cache network responses for offline? → Cache API
Need auth token storage? → HttpOnly Secure cookie (never localStorage)

Interview Signal

Storage questions test security awareness and architectural judgment. Senior signals:

  1. Security-first thinking — knowing that auth tokens belong in HttpOnly cookies, understanding XSS implications of localStorage
  2. Performance awareness — knowing localStorage is synchronous and can block rendering
  3. Right tool for the job — not using localStorage for everything, understanding IndexedDB's strengths
  4. Offline strategy — articulating how Cache API + Service Workers enable offline experiences