LEARNING OBJECTIVES โต
- Calculate the exact byte memory footprint of Web Storage data based on UTF-16 code units (2 bytes per character).
- Understand quota allocations across major browser engines (Chromium, Gecko, WebKit).
- Intercept and handle
DOMException: QuotaExceededErrorwithout breaking application runtime state. - Design and implement an automated Least Recently Used (LRU) cache eviction algorithm for client-side storage.
๐ The Mental Model & Story (Intuitive Foundation)
Imagine a carry-on suitcase for an airplane flight that has a strict 5 kg weight limit. You can pack lightweight t-shirts, socks, and headphones with plenty of room to spare. But if you try to pack a 10 kg solid iron barbell, the airport luggage scale triggers a red alarm (QuotaExceededError) and the flight attendant refuses to let you board.
+---------------------------------------------------------------------------------------------------+
| THE STORAGE QUOTA BOUNDARY |
| |
| [ Current Storage Bucket: 4.8 MB consumed ] |
| localStorage.setItem('huge_image_base64', '800KB payload') |
| | |
| v (Attempting write: Total would be 5.6 MB > 5.0 MB Limit!) |
| ๐ฅ DOMException: Failed to execute 'setItem' on 'Storage': Setting the value... exceeded quota! |
| |
| * The write is REJECTED atomically. No partial data is written. |
| * Solution: Implement LRU Eviction to discard oldest items before writing! |
+---------------------------------------------------------------------------------------------------+
Without proactive quota management, your application will crash the moment an active user exceeds their local storage limit. By building an LRU (Least Recently Used) eviction engine, your application becomes self-cleaningโautomatically shedding old, unused cached data to make room for fresh items.
Technical Deep Dive & Specifications
Browser Quota Comparison & Storage Allocations
The WHATWG specification recommends a default quota of 5 MB per origin, but does not enforce a rigid ceiling. Different browser vendors implement slightly different limits:
| Browser Engine | Typical localStorage Quota |
Typical sessionStorage Quota |
Quota Scope |
|---|---|---|---|
| Google Chrome / Chromium | ~5 MB โ 10 MB | ~5 MB โ 10 MB | Per Origin (<scheme, host, port>) |
| Mozilla Firefox (Gecko) | ~5 MB โ 10 MB | ~5 MB โ 10 MB | Per Origin |
| Apple Safari (WebKit) | ~5 MB | ~5 MB | Per Origin (Subject to 7-day ITP caps) |
| Mobile Safari (iOS) | ~5 MB | ~5 MB | Per Origin (Restricted on low memory) |
The UTF-16 Memory Model (2 Bytes Per Character)
In JavaScript and the DOM WebIDL specification, DOMString values are encoded in UTF-16 code units. Each character in a storage key or value consumes 2 bytes (16 bits) of allocated memory.
$$\text{Total Memory (Bytes)} = \sum (\text{length of Key} + \text{length of Value}) \times 2$$
Therefore:
- A 5 MB quota allows roughly 2,621,440 characters (approx. 2.5 million characters of text).
- Storing high-density binary data as Base64 strings inflates raw payload size by 33%, which is then doubled by UTF-16 encoding, rapidly consuming available storage.
QuotaExceededError Across Browser Engines
When storage.setItem() attempts to write a payload that exceeds the remaining origin quota, the browser throws a DOMException.
try {
localStorage.setItem('my_key', largePayload);
} catch (error) {
if (
error instanceof DOMException &&
(
// Standard W3C/WHATWG name
error.name === 'QuotaExceededError' ||
// Legacy code check (W3C standard code 22)
error.code === 22 ||
// Firefox legacy name
error.name === 'NS_ERROR_DOM_QUOTA_REACHED' ||
error.code === 1014
)
) {
console.error('Storage quota exceeded! Executing cache eviction...');
}
}
The LRU (Least Recently Used) Eviction Architecture
An LRU cache tracks when items are accessed or written. When storage space runs out, the eviction engine deletes the item with the oldest access timestamp until sufficient space is freed.
Write New Item -> [ Storage Full? ]
|
+------------+------------+
| |
NO (Space OK) YES (Full)
| |
Write to Disk v
Find Oldest 'lastAccessed'
|
Evict Oldest Key
|
Retry Write Operation
+------------------------------------------------------------------------------------+
| LRU CACHE METADATA STRUCTURE |
| Key: "cache:user:101" |
| Value: { |
| "lastAccessed": 1724200000000, // Unix timestamp (ms) |
| "data": { "name": "Alice", "role": "engineer" } |
| } |
+------------------------------------------------------------------------------------+
๐ป Interactive Code Playground
Starter Code
Line-by-Line Code Breakdown
- Lines 73โ81 (
calculateUsageBytes): Iterates through all stored keys and values, computing character count multiplied by 2 to measure total memory utilization in UTF-16 code units. - Lines 83โ91 (
updateMeter): Calculates percentage consumed against an estimated 5MB boundary and drives the CSS gradient meter width dynamically. - Lines 94โ96 (
create500KBString): Creates a 250,000-character string ('X'.repeat(250000)), which occupies exactly 500,000 bytes (approx. 500 KB) in memory. - Lines 100โ108: Traps
err.name === 'QuotaExceededError'cleanly whenlocalStorage.setItem()exceeds capacity, preventing runtime crashes. - Lines 111โ127 (
btn-stress-test): Synchronously loops untilsetItem()throws, identifying the exact maximum storage boundary allocated by the host browser.
Expected Browser Render Output
+-------------------------------------------------------------+
| Storage Quota Stress Tester |
| Current Usage: 4,500.0 KB (4.39 MB) / ~5,120 KB (Est. 5MB) |
| [======================================------] 88% |
| |
| [Inject 500 KB Chunk] [Stress Test to Max] [Clear Test Data]|
| |
| Diagnostic Log: |
| [02:26:15] Successfully wrote 500KB chunk: quota_test_chunk |
| [02:26:18] ๐จ QUOTA EXCEEDED! Browser stopped writes at 51. |
| [02:26:18] Total estimated stored capacity: 5.12 MB |
+-------------------------------------------------------------+๐๏ธ Hands-On Exercise
๐ฏ The Challenge: Build an Automated Self-Evicting LRU Storage Manager
Build a production-ready cache wrapper class LRUStorageCache that stores items with a timestamp. When a QuotaExceededError occurs during a write, it automatically finds the oldest accessed key, deletes it, and retries the write operation until it succeeds.
Your Goal:
- Prefix cached keys with a configurable namespace (e.g.
lru_cache:). - Store every item wrapped in an envelope:
{ lastAccessed: Date.now(), data: payload }. - When
get(key)is called, update the item'slastAccessedtimestamp. - When
set(key, data)throwsQuotaExceededError, find the key in the namespace with the smallestlastAccessedtimestamp, remove it, and retry writing.
๐ Starter Code Sandbox
โ ๏ธ Common Pitfalls
- Base64 Bloat in Web Storage: Storing base64 images or PDFs in
localStoragequickly triggersQuotaExceededError. Base64 is ~33% larger than raw binary, and JavaScript doubles this in memory via UTF-16. UseIndexedDBwithBloborArrayBufferinstead. - Assuming 10MB Everywhere: Never assume 10MB is guaranteed. On low-memory mobile devices or privacy browsers (Brave/Tor), storage limits can be restricted to 5MB or less.
- Unbounded Writes in Event Handlers: Storing continuous user coordinates (
mousemoveor scroll positions) without throttling will rapidly exhaust disk space.
๐ก Pro Tips
- The Storage API (
navigator.storage.estimate): Use the modernnavigator.storage.estimate()API to query overall origin storage quota and usage asynchronously before attempting large operations. - Soft Quota Warning Thresholds: Set an in-app soft ceiling (e.g. 80% of capacity). When usage exceeds 4MB, trigger proactive background cleanup before a hard
QuotaExceededErrorinterrupts user workflows.
๐ Key Takeaways
- Web Storage limits typically range from 5 MB to 10 MB per origin.
- JavaScript strings in Web Storage use UTF-16 code units (2 bytes per character).
- Writing beyond the available quota throws a
DOMExceptionnamedQuotaExceededError. - Writes that fail due to quota exhaustion are rejected atomically; no partial data is written.
- Implementing an LRU (Least Recently Used) cache eviction algorithm ensures automatic cleanup and prevents application crashes.
- --