← Back to blog

IndexedDB vs LocalStorage vs Cookies

Apr 15, 2026·11 min read
Engineering
Web

Recently, I was asked to compare IndexedDB, LocalStorage, and Cookies as browser storage during an interview. Now, I did fine - but not great - and that's because I had never really sat down and forced myself to memorize the boring stuff that actually matters in production: request headers, thread blocking, serialization rules, etc.

So this is the article I'd have wanted to read the night before. If you already know the APIs, think of it like a checklist of gotchas and a few library shortcuts that'll make your life a whole lot easier.

"My brother in christ, my interview's in 10 minutes"

Hey, relax. It's not like you're going to get hired anyway. Chill out and check out this summary.

MechanismAPI ShapeTypical SizeAllowed StorageAccess
Cookiesdocument.cookie / Set-Cookietinystrings onlyShipped on HTTP requests (subject to domain/path/SameSite rules)
LocalStoragesync key/value~5 MB / originstrings onlySame origin, any tab
SessionStoragesync key/value~5 MB / origin (tab-scoped)strings onlySame origin, same tab
IndexedDBasync database"a lot" (disk-based quota)Structured-cloneable valuesSame origin; workers + service workers

Note: The middle columns are where interviews love to hide traps.

Cookies: The Backend's Secret Agent

Cookies are ancient and oddly powerful because they sit outside your JavaScript heap. More importantly, matching cookies ride along on HTTP requests (navigation, fetch in many cases, images, fonts.. whatever the browser decides needs a Cookie header).

Now if you cram 2 KB of JSON into a cookie, then you're not storing data quietly on disk, you're inflating every single request with a copy of that payload. RFC 6265 literally tells servers to keep cookies small specifically to reduce bandwidth from the Cookie header.

Browsers also enforce hard caps (about 4096 bytes per cookie counting name + value + attributes in practice). There are also per-domain cookie counts, exceed them and the browser starts lining cookies up against a wall and executing them.

Highlights

  • Strings only at the document.cookie layer. Binary data means base64 → even fewer usable bytes.
  • Synchronous string parsing when you read/write document.cookie on the main thread.

OK, so when should I use cookies?

Small opaque identifiers (session IDs), CSRF tokens, preference flags the server must read.

LocalStorage & SessionStorage: The /comfy/ Bros

localStorage and sessionStorage expose the Web Storage API. It looks like a hash map, probably because it is. It's synchronous, and every getItem / setItem will block the main thread while the browser does I/O on whatever backing store it picked for your origin.

Google's Storage for the Web article actually advices avoiding localStorage for general data because of that blocking behavior, and because you are capped to roughly 5 MB (again, implementation-dependent).

Highlights

  • Strings only. If you want objects, you JSON.stringify on write and JSON.parse on read, which means you will lose anything JSON cannot parse cleanly (Date becomes string, undefined disappears, Map/Set breaks unless you write custom handlers).
  • SessionStorage is the same API and similar limits, but scoped to the tab lifetime (close the tab, it's gone) and invisible to workers and service workers.
  • Web Storage is same-origin, not "same site". This means that the port matters too(!) and subdomains don't magically share the same cookie jar unless you try to access document.domain (please don't) or do some crafty iframe hacks (please really don't).

IndexedDB: The Real Deal

IndexedDB is an asynchronous, transactional, key-value/object-store database. It holds structured data up to your per-origin quota which is often hundreds of MB to much (much) more on desktop. Chrome ties quota roughly to the available disk space, while Firefox groups by eTLD+1 with limits on the order of literal gigabytes. Safari, thanks to Tim Apple, is tighter and more restrictive in this regard.

Highlights

No serious per-record byte limit is specified the way cookies are, so in practice you eventually hit quota or memory before you hit any wall.

Now, that does not mean you should straight-up store one 400 MB JSON blob. If you're a serious vibecoder, please feel free to disregard this opinion.

Structured Clone vs "JSON-Serializable"

We're finally at the interesting part.

IndexedDB values must be structured-cloneable (same family of algorithm used by postMessage) and that is much richer than plain old JSON:

  • Date, deeply nested objects, ArrayBuffer, Blob, File, typed arrays, Map, Set, and more survive round trips.
  • Functions and DOM nodes though are not your friends here, while prototype chains and symbols are, well, complicated.

IndexedDB stores what the structured clone algorithm accepts, while Web Storage stores whatever fits in a string after you JSON serialize.

Workers, And How Eviction Happens

IndexedDB is available from window, workers, and service workers (unlike Web Storage), so it's the default answer for offline caches that sync from a SW.

Data, overall, is best-effort by default. Browsers may evict under storage pressure unless you pursue persistent storage (navigator.storage.persist()), while Safari has historically had aggressive policies (including the infamous "idle for seven days" eviction policy, consider reading WebKit's posts if iOS is in your user base).

Now while none of that is a reason to avoid IndexedDB, you should still handle QuotaExceededError and consider measuring the sizeof your playground with navigator.storage.estimate().

How Big Is "Big", Really?

Rough per-value ceilings:

StoreSize of "one value"
Cookie~4 KiB per cookie (name + value + attributes in real browsers)
LocalStorage/SessionStorageEntire budget ~5 MiB and individual keys share that pool
IndexedDBNothing mandated by the spec, single records are bounded by quota + RAM

For the total space, cookies and Web Storage stay in the teeny-tiny megabyte range, while IndexedDB is in the large origin quota league.

How Tiny Is "Tiny", Really?

Both bars use a 5 MiB scale so you can see just how tiny a single Cookie is next to the usual localStorage/sessionStorage limit.

Per cookie (≈4 KiB)4 KB
Typical Web Storage limit (per origin)~5 MB

How Much?

navigator.storage.estimate() gives us the quota shared by IndexedDB, Cache API, service worker storage, etc. but not Cookies or Web Storage.

Note: estimate() might look suspiciously huge on Chrome, but it's likely because reported quota !== physically free disk.

Useful Libraries

idb

Useful promise wrapper over raw IndexedDB. Super tiny (~1 kB brotli-ish). Keeps the real IndexedDB model (transactions, versioning, indexes) but removes the onupgradeneeded hassle. If you're teaching someone IndexedDB in 2026, they should probably start here.

idb-keyval

If literally all you need is async localStorage with bigger values, this one's it. It's a single-object-store keyval on top of IndexedDB.

localforage

localStorage-like async API, while prefering IndexedDB under the hood. It can fall back to WebSQL or localStorage.

Note: When it's forced onto the localStorage driver, complex objects go through JSON serialization and not structured clone (as we saw earlier), so you are back in Date-as-string land. When it uses IndexedDB, you get the richer types. Know thy driver.

Summary (Taylor's Version)

  • Cookies are for tiny server-readable tokens.
  • SessionStorage for ephemeral tab state, LocalStorage for small preference states.
  • IndexedDB for offline app data, large caches, binary assets, and basically anything a service worker touches. It's async, uses structured clone, gargantuan quota.

Summary

I'll be honest. I used to treat "Where do I store this?" as a one-line decision, but there's a lot of nuance and - if you're not being careful - several gotcha-s too. The browser thinks about threads, headers, eviction, and serialization for you, whether you like it to or not.

The interview question was useful because it forced me to critically examine what I actually knew about browser storage after years of building web applications. Turns out, there's always more to learn. :)

If you want to go deeper than I went here, I highly recommend going through Storage for the Web.