Synchronous vs asynchronous storage
The main thread is JavaScript’s single execution lane
Section titled “The main thread is JavaScript’s single execution lane”Browsers run JavaScript, layout, and painting on a single main thread. When any JavaScript call takes a long time to return, it holds up everything else — animations stutter, clicks go unresponsive, and the browser may show a “page not responding” warning. This is called jank.
Synchronous storage: Web Storage and cookies
Section titled “Synchronous storage: Web Storage and cookies”localStorage, sessionStorage, and document.cookie are all synchronous. When you call localStorage.getItem('key'), the browser reads from disk, returns the value, and only then lets your code continue. Your script cannot do anything else while it waits.
For a single small read this is imperceptible. But problems surface when you:
- Loop over thousands of keys
- Store large serialised objects (many kilobytes each)
- Write on every keystroke or scroll event
- Parse the entire cookie string on a hot code path
Because all of this happens on the main thread, heavy Web Storage use can introduce measurable frame-time delays.
// Every one of these calls is synchronous — the thread blocks until each completesconst start = performance.now();for (let i = 0; i < 1000; i++) { localStorage.setItem('key-' + i, 'value-' + i);}const elapsed = performance.now() - start;console.log('1 000 writes took ' + elapsed.toFixed(2) + ' ms');Asynchronous storage: IndexedDB and Cache API
Section titled “Asynchronous storage: IndexedDB and Cache API”IndexedDB and the Cache API are asynchronous. Every operation returns a Promise (or fires an event) and your code continues immediately. The actual I/O happens off the main thread. When results are ready, the browser calls your callback or resolves your Promise.
// This returns immediately — the write happens in the backgroundconst db = await idb.openDB('my-db', 1, { upgrade(db) { db.createObjectStore('items'); }});await db.put('items', { name: 'Alice' }, 'user-1');console.log('Write complete — main thread was never blocked');Two-lane model
Section titled “Two-lane model”flowchart LR
subgraph Sync["Synchronous (Web Storage / Cookies)"]
direction TB
T1[Main thread] -->|blocked| R1[localStorage.getItem]
R1 -->|returns| T1
end
subgraph Async["Asynchronous (IndexedDB / Cache API)"]
direction TB
T2[Main thread] -->|fires request| IDB[IndexedDB / Cache]
T2 -->|continues immediately| T2
IDB -->|Promise resolves| T2
end Runnable timing comparison
Section titled “Runnable timing comparison”The snippet below times 10 000 localStorage.setItem calls synchronously, logs the elapsed time, and then cleans up every demo: key it created.