skip to content

Client Storage and Persistence

You will learn every place a browser lets you keep bytes on the user's machine — cookies, Web Storage, IndexedDB, the Cache API — and the quota and cloning rules that govern them. Interviewers ask because 'where do we put the token?' and 'how does it work offline?' both land here.

on this pageshow

explore

questions

30

What does the global structuredClone() function do, and how does the copy it produces differ from JSON.parse(JSON.stringify(value))?

level: juniorimportance: must knowfreq 55%

basics

~20 s

structuredClone() returns a deep copy made by the browser's structured clone algorithm. Unlike a JSON round-trip it preserves Map, Set, Date, RegExp, typed arrays, Blobs and cyclic references, but it throws a DataCloneError on functions and DOM nodes.

open as a page

In the browser, how do localStorage and sessionStorage differ in scope and lifetime, and what do the two have in common?

level: juniorimportance: must knowfreq 85%

basics

~20 s

Both hold string key/value pairs scoped to one origin and share the same API. localStorage persists until something clears it and is visible to every tab on that origin; sessionStorage is confined to a single tab and is discarded when that tab closes.

open as a page

You stored a response with the browser's Cache API and are sure the URL is right, yet `cache.match(request)` resolves with `undefined`. What are the usual causes, and which `match` options address them?

level: middleimportance: must knowfreq 58%

basics

~20 s

Cache API lookups key on the full URL including the query string, honour the stored response's Vary header, and only match GET requests. The ignoreSearch, ignoreVary and ignoreMethod options relax each rule; a wrong cache name is the fourth cause.

open as a page

The browser gives you the Cache API (the global `caches` / `CacheStorage`) alongside the HTTP cache it manages itself. How do the two differ, and what does that difference mean for code that stores responses?

level: middleimportance: must knowfreq 68%

basics

~20 s

The Cache API is a script-owned store of Request/Response pairs that you fill, look up and delete explicitly, with no expiry of its own. The HTTP cache is browser-managed, driven by response headers, and unreachable from JavaScript.

open as a page

A single-page app cannot find its session cookie in `document.cookie` after login, yet its requests are authenticated. Why is that cookie invisible to script, and how should the app decide whether the user is signed in?

level: middleimportance: must knowfreq 66%

basics

~20 s

The server marked the cookie HttpOnly, so the browser withholds it from document.cookie while still attaching it to matching requests. The app should learn its signed-in state from an API call such as a session endpoint, never by looking for the cookie.

open as a page

A page already has three cookies. What happens when script runs `document.cookie = "theme=dark; path=/; max-age=3600"`, and which parts of that string can ever be read back?

level: middleimportance: must knowfreq 63%

basics

~20 s

Assigning to document.cookie adds or replaces exactly one cookie and leaves the other three untouched — it is not a normal property assignment. Only the name=value part is ever readable afterwards; path and max-age are write-only.

open as a page

What does `navigator.storage.persist()` do, and what does a `true` result actually guarantee about the data your origin has stored?

level: middleimportance: must knowfreq 48%

basics

~20 s

navigator.storage.persist() asks the browser to move the origin from best-effort to persistent storage. A true result means the browser will not evict that data automatically under disk pressure. The user can still clear it, and the origin's quota does not change.

open as a page

The call localStorage.setItem('user', { id: 1 }) runs without throwing. What is actually stored, and what does localStorage.getItem('missing') return for a key that was never written?

level: middleimportance: must knowfreq 68%

basics

~20 s

Web Storage keeps strings only, so setItem converts the object with the ordinary string conversion and stores the literal text "[object Object]". Reading a key that was never written returns null, not undefined, so structured data must be serialised and parsed explicitly.

open as a page

An IndexedDB readwrite transaction writes its first record fine, then throws TransactionInactiveError on a second write issued after an awaited fetch(). Why did the transaction die, and how should the code be restructured?

level: seniorimportance: must knowfreq 50%

basics

~20 s

IndexedDB transactions auto-commit: one stays active only while the task that created it, or one of its own request callbacks, is running. Awaiting a network response returns control to the event loop with no pending request, so the transaction commits and closes before the second write is issued.

open as a page

A `Cache` object from the browser's Cache API offers `add(request)`, `addAll(requests)` and `put(request, response)`. What does each one do, and when do you have to use `put`?

level: juniorimportance: should knowfreq 44%

basics

~20 s

add(request) fetches a URL and stores the result; addAll(list) does that for several URLs and stores none if any fails. put(request, response) stores a Response you already hold — the only option when you already fetched it.

open as a page

In IndexedDB, what is an object store, what does a store's keyPath do, and what changes when the store is created with autoIncrement: true?

level: juniorimportance: should knowfreq 45%

basics

~20 s

An object store is IndexedDB's container for records: a keyed collection of values, closer to a key-value map than a SQL table. keyPath names the property inside each record used as its primary key; autoIncrement: true makes the store generate increasing integer keys instead.

open as a page

What does `navigator.storage.estimate()` report in a browser, and what does the `quota` value in its result actually represent?

level: juniorimportance: should knowfreq 42%

basics

~20 s

navigator.storage.estimate() resolves with usage and quota in bytes: roughly what the origin already stores, and the approximate ceiling it may reach. Both numbers are deliberately imprecise, and they cover all of the origin's quota-managed storage as one shared pool.

open as a page

An IndexedDB store holds 200,000 orders with an index on their timestamp, and you need the 50 most recent. How do cursors and IDBKeyRange let you read that without pulling the whole store into memory?

level: middleimportance: should knowfreq 40%

basics

~20 s

Open a cursor on the timestamp index with direction 'prev' and stop calling continue() after 50 records. A cursor walks index order one record at a time, and an IDBKeyRange bounds which keys it visits, so memory stays proportional to what you keep, not to the store.

open as a page

In IndexedDB, what does IDBObjectStore.createIndex() give you that the object store's primary key does not, and what happens to a record whose value has no property at the index's key path?

level: middleimportance: should knowfreq 45%

basics

~20 s

An index is a second sorted view of a store, keyed by a chosen property, so records can be looked up and range-scanned by that property rather than only by primary key. Records missing the index key path simply get no index entry and never appear in index queries.

open as a page

A web app that writes large blobs to client storage sees the writes fail for some users. Which error does a browser raise when an origin's storage quota is exhausted, and what determines how much room a given user actually has?

level: middleimportance: should knowfreq 44%

basics

~20 s

The browser raises a QuotaExceededError DOMException: thrown synchronously by localStorage.setItem, aborting the transaction in IndexedDB, rejecting the promise in the Cache API. The ceiling is derived from the device's free disk space, so it varies per machine and shrinks as the disk fills.

open as a page

Given `const shared = { n: 1 }; const o = { a: shared, b: shared }; o.self = o;`, what does `structuredClone(o)` return, and what does that reveal about how the algorithm walks an object graph?

level: middleimportance: should knowfreq 35%

basics

~20 s

The clone reproduces the graph exactly: c.a === c.b is true and c.self === c is true, while c.a === shared is false. The algorithm keeps a map of already-serialized objects, so each source object becomes exactly one clone, which makes cycles and shared references safe.

open as a page

Passing a value to structuredClone() or postMessage() can fail with a DOMException named DataCloneError. Which kinds of values cause that, and which values are copied but come back changed?

level: middleimportance: should knowfreq 45%

basics

~20 s

Functions, symbols, DOM nodes, Promises, WeakMap/WeakSet and proxies are not serializable and throw a DataCloneError. Values that do clone can still change: prototypes are dropped, getters are invoked and flattened to data, and non-enumerable and symbol-keyed properties are skipped.

open as a page

A page writes to localStorage, but its own window.addEventListener('storage', ...) handler never runs — while another tab's handler does. Why, and which documents actually receive the storage event?

level: middleimportance: should knowfreq 52%

basics

~20 s

The storage event is a notification to OTHER documents: it fires on every same-origin window except the one that performed the write. The writing page already knows what it did, so it must update its own state directly rather than waiting for an event.

open as a page

Your app stores cross-origin assets fetched with `mode: 'no-cors'` in the browser's Cache API and starts hitting `QuotaExceededError` even though the files are small. What is an opaque response, and why does storing them behave this way?

level: seniorimportance: should knowfreq 42%

basics

~20 s

A no-cors cross-origin fetch yields an opaque response: status 0, no readable headers or body. Browsers pad its recorded storage size — in Chromium, to roughly 7 MB apiece — so a handful of small opaque entries can exhaust an origin's quota.

open as a page

Every deploy of your app creates a new named cache with the browser's Cache API, and users' storage grows without bound. How should named caches be versioned and cleaned up, and what order must the operations happen in?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Nothing deletes a named cache automatically. Enumerate with caches.keys() and remove the ones you no longer recognise with caches.delete(name) — after the new generation is fully populated, never before, so a failed deploy still has something to serve.

open as a page

A logout handler runs `document.cookie = "session=; max-age=0"`, but the cookie is still sent on the next request. Give every reason a JavaScript cookie deletion can silently fail, and the correct way to delete one.

level: seniorimportance: should knowfreq 49%

basics

~20 s

There is no delete API — you expire a cookie by rewriting it with the same name, path and domain plus max-age=0. A mismatched path or domain writes a different cookie, and an HttpOnly or Secure cookie cannot be touched from script at all.

open as a page

A shipped web app needs a new IndexedDB index and a reshaped record format. How does an IndexedDB version upgrade actually run, and what do the blocked and versionchange events have to do with tabs the user already has open?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Calling indexedDB.open(name, higherVersion) fires upgradeneeded and gives you an exclusive versionchange transaction — the only place stores and indexes can be created and existing records rewritten. Older tabs still holding the database block that upgrade until they close.

open as a page

When a device runs low on disk space, how does a browser evict a best-effort origin's stored data, and what does that granularity mean for an offline-capable web app?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Browsers evict a whole best-effort origin at once, least-recently-used first: its databases, caches and web storage all go together, never just the oldest records inside one store. Offline apps must therefore treat client storage as a cache that can be empty on the next visit.

open as a page

A widget served from widget.example is loaded in an iframe on two different top-level sites. Modern browsers no longer let it read the same client-side storage in both. What changed, and what is the unit that quota and eviction now apply to?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Browsers now key client storage on the top-level site plus the frame's origin, not the origin alone. An embedded widget therefore gets a separate store, a separate quota and a separate eviction fate on every site that embeds it, and cannot carry state between embedders.

open as a page

A class instance with methods and getters is passed through structuredClone() or postMessage(). What arrives on the other side, and how should the value be designed so the receiver can work with it?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Only own enumerable data arrives, as a plain object: the prototype is gone, so methods and instanceof fail, and getters have been flattened into fixed values. Design the boundary around explicit plain-data payloads plus a rehydrate function on the receiving side.

open as a page

Under what conditions can localStorage.setItem() throw — or even the plain expression window.localStorage throw before you call any method — and how should production code guard against it?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Writes throw a QuotaExceededError once the origin passes its limit, commonly around 5 MB. Access itself can throw a SecurityError when site data is blocked, for example in an embedded frame under restrictive settings. Feature-detect with a try/catch write probe and degrade instead of crashing.

open as a page

Why is localStorage described as a main-thread hazard, and what symptoms appear in a page that reads or writes large values on every interaction?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Web Storage is a blocking API: every read and write runs to completion on the main thread, so the page cannot render, respond to input or run anything else while it happens. Large values add serialisation cost on top, producing long tasks and stalled interactions.

open as a page

The `cookieStore` API is offered as a modern replacement for `document.cookie`. What does it change about reading, writing and observing cookies, and what stays exactly the same?

level: seniorimportance: nice to knowfreq 27%

basics

~20 s

cookieStore is a promise-based API returning cookie objects with their attributes, usable in service workers, with a change event instead of polling. The security model is unchanged: HttpOnly cookies stay invisible, and it is secure-context only.

open as a page

structuredClone() runs synchronously on the calling thread. What does that cost on a large object graph, and how does it show up when profiling a page?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

The clone walks and rebuilds the whole graph before returning, blocking the thread for the duration — so a large structure produces a long task that delays rendering and input handling. Cost scales with the number of distinct nodes and bytes, not with the value's nesting depth.

open as a page