Web Storage APIs
Reviewed & published by Brayan K
Master localStorage, sessionStorage, and IndexedDB to build fast, offline-friendly applications with persistent client-side data.
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
🎯 What You'll Learn
- localStorage vs sessionStorage vs IndexedDB
- Building a Storage Service Layer
- TTL-based caching
- Storing files & blobs in IndexedDB
- Cross-tab communication
- Offline-first sync architecture
💡 Running Code Locally: While this online editor runs real JavaScript, some advanced examples may have limitations. For the best experience:
- Download Node.js to run JavaScript on your computer
- Use your browser's Developer Console (Press F12) to test code snippets
- Create a .html file with <script> tags and open it in your browser
Understanding the Storage Landscape
JavaScript provides three powerful storage mechanisms, each designed for different needs:
localStorage
- • Persists forever
- • Shared across tabs
- • ~5-10 MB limit
- • Synchronous
sessionStorage
- • Deleted when tab closes
- • Isolated per tab
IndexedDB
- • Stores objects & blobs
- • Hundreds of MB+
- • Asynchronous
localStorage: Long-Term Persistent Storage
localStorage keeps data forever (or until the user clears it manually). It's ideal for preferences, themes, cached data, and recently viewed items.
// localStorage keeps data forever (until manually cleared)
// Perfect for: preferences, theme, cached data, recently viewed
localStorage.setItem("theme", "dark");
const theme = localStorage.getItem("theme");
console.log("Theme:", theme); // "dark"
localStorage.removeItem("theme");
localStorage.clear(); // removes all items
// Values are ALWAYS strings - must convert objects
localStorage.setItem("user", JSON.stringify({ id: 10, role: "admin" }));
const user = JSON.parse(localStorage.getItem("user"));
console.log("User:", user);Common Mistakes to Avoid
// ❌ COMMON MISTAKE - string concatenation
localStorage.setItem("count", 1);
console.log(localStorage.getItem("count") + 1); // "11" (string!)
// ✔ CORRECT - convert to number first
localStorage.setItem("count", 1);
console.log(Number(localStorage.getItem("count")) + 1); // 2
// ❌ Another mistake - forgetting to stringify
localStorage.setItem("data", { name: "test" }); // stores "[object Object]"
// ✔ CORRECT
localStorage.setItem("data", JSON.stringify({ name: "test" }));Worked Example: A Settings Store, Start to Finish
Those two snippets showed the pieces. This one is a complete program that stores a theme, a number and a whole settings object, then reads them all back — including the two ways a read can go wrong (nothing stored yet, and stored rubbish). Notice the first line: it deletes its own keys before starting, because storage that persists is exactly what makes a demo print something different the second time you run it.
// WORKED EXAMPLE - a settings store, and the one rule that trips everyone up:
// Web Storage only ever holds STRINGS.
// Start clean so the example prints the same thing every time you run it.
// (Storage survives reloads - that is the whole point of it - so a demo that
// does not reset itself gives different answers on the second run.)
["demo:theme", "demo:fontSize", "demo:settings"].forEach(k => localStorage.removeItem(k));
// ---------- 1. Strings go in and strings come out ----------
localStorage.setItem("demo:theme", "dark");
const theme = localStorage.getItem("demo:theme");
console.log("theme:", theme, "| typeof:", typeof theme);
// A number is silently converted to text on the way in.
localStorage.setItem("demo:fontSize", 16);
const rawSize = localStorage.getItem("demo:fontSize");
console.log("fontSize:", rawSize, "| typeof:", typeof rawSize);
console.log("raw + 1 :", rawSize + 1); // string concatenation, not maths
console.log("Number() + 1:", Number(rawSize) + 1); // what you actually wanted
// ---------- 2. Objects need JSON on the way in AND out ----------
const settings = { theme: "dark", fontSize: 16, notifications: false };
localStorage.setItem("demo:settings", JSON.stringify(settings)); // object -> text
const saved = JSON.parse(localStorage.getItem("demo:settings")); // text -> object
console.log("saved.fontSize:", saved.fontSize, "| typeof:", typeof saved.fontSize);
// Skip JSON.stringify and the browser calls String() on your object for you,
// which turns every object into the same useless five words:
localStorage.setItem("demo:settings", settings);
console.log("without JSON.stringify:", localStorage.getItem("demo:settings"));
// ---------- 3. A read that cannot crash your page ----------
// getItem returns null for a key that was never set, and JSON.parse throws on
// anything that is not valid JSON - including that "[object Object]" above.
function read(key, fallback) {
const raw = localStorage.getItem(key);
if (raw === null) return fallback; // nothing stored yet: first visit
try {
return JSON.parse(raw);
} catch {
return fallback; // stored value is corrupt: do not crash
}
}
console.log("missing key ->", read("demo:nothing-here", "default"));
console.log("corrupt value ->", read("demo:settings", "default"));
// ---------- 4. Clean up after yourself ----------
localStorage.removeItem("demo:theme");
console.log("after removeItem:", localStorage.getItem("demo:theme"));
// ✅ Expected output:
// theme: dark | typeof: string
// fontSize: 16 | typeof: string
// raw + 1 : 161
// Number() + 1: 17
// saved.fontSize: 16 | typeof: number
// without JSON.stringify: [object Object]
// missing key -> default
// corrupt value -> default
// after removeItem: null
//
// Line 3 is the bug that reaches production most often: "161" looks almost
// right, so nobody notices until a font size of 161 pixels turns up.🎯 Your Turn: Remember the Visit Count
Now you write it. A page counts how many times it has been opened, and remembers a small profile object. Four blanks are marked ___ — two are about numbers versus text, two are about objects versus text. That is the entire Web Storage API in four lines.
// 🎯 YOUR TURN - fill in the four blanks marked ___
// A page that remembers how many times you have visited it.
localStorage.removeItem("demo:visits"); // reset, so the demo is repeatable
localStorage.removeItem("demo:profile");
// 1) Read the counter. On a first visit nothing is stored, so getItem returns
// null - start from 0 in that case, and turn stored TEXT back into a number.
const stored = localStorage.getItem("demo:visits");
const visits = stored === null ? 0 : ___(stored); // 👉 replace ___ with Number
// 2) Count this visit and save it back under the same key.
const next = visits + 1;
localStorage.___("demo:visits", next); // 👉 replace ___ with setItem
console.log("visit number " + localStorage.getItem("demo:visits"));
// 3) Save a whole object. Storage cannot hold objects, only text.
const profile = { name: "Sam", pro: true };
localStorage.setItem("demo:profile", ___(profile)); // 👉 replace ___ with JSON.stringify
// 4) Read it back and turn the text into a real object again.
const back = ___(localStorage.getItem("demo:profile")); // 👉 replace ___ with JSON.parse
console.log(back.name + " pro? " + back.pro);
console.log("typeof back:", typeof back);
// ✅ Expected output once the blanks are filled:
// visit number 1
// Sam pro? true
// typeof back: object
//
// If the last line says "string", blank 4 is still missing - you read the text
// but never parsed it. If it says "[object Object]" anywhere, blank 3 is missing.sessionStorage: Tab-Specific, Temporary Storage
sessionStorage is deleted when the tab closes. It's perfect for multi-step forms, temporary shopping cart state, and tab-isolated workflows.
// sessionStorage is deleted when the tab closes
// Perfect for: multi-step forms, temp state, tab-specific data
sessionStorage.setItem("step", "payment");
console.log(sessionStorage.getItem("step")); // "payment"
// Each tab has SEPARATE storage!
// Open two tabs → each has its own sessionStorage
// Great for checkout flows
sessionStorage.setItem("cartDraft", JSON.stringify({
items: [{ id: 1, qty: 2 }],
step: 2
}));
// Prevents data bleed between tabs
sessionStorage.setItem("tabId", crypto.randomUUID());localStorage vs sessionStorage
| Feature | localStorage | sessionStorage |
|---|---|---|
| Persistence | Until cleared | Until tab closes |
| Scope | Shared across tabs | Per-tab isolation |
| Use case | Preferences & saved state | In-progress workflows |
Building a Storage Service Layer
Production apps don't store values directly into localStorage. They create a Storage Service Layer that centralizes logic, handles parsing errors, and adds features like namespacing.
// Production apps use a Storage Service Layer
class StorageService {
constructor(prefix = "app") {
this.prefix = prefix;
}
key(name) {
return `${this.prefix}:${name}`;
}
get(name, fallback = null) {
try {
const value = localStorage.getItem(this.key(name));
return value ? JSON.parse(value) : fallback;
} catch {
return fallback;
}
}
set(name, value) {
localStorage.setItem(this.key(name), JSON.stringify(value));
}
remove(name) {
localStorage.removeItem(this.key(name));
}
clearAll() {
Object.keys(localStorage)
.filter(k => k.startsWith(this.prefix))
.forEach(k => localStorage.removeItem(k));
}
}
const storage = new StorageService("myapp");
storage.set("theme", "dark");
storage.set("user", { id: 1, name: "John" });
console.log(storage.get("theme")); // "dark"
console.log(storage.get("user")); // { id: 1, name: "John" }TTL-Based Storage (Time-To-Live)
For caching API responses, you want data to expire automatically. This pattern prevents stale data while improving performance.
// TTL-Based Storage (Time-To-Live)
// Perfect for caching API responses
function setWithTTL(key, value, ttlMs) {
const data = {
value,
expiry: Date.now() + ttlMs
};
localStorage.setItem(key, JSON.stringify(data));
}
function getWithTTL(key) {
const item = localStorage.getItem(key);
if (!item) return null;
try {
const data = JSON.parse(item);
if (Date.now() > data.expiry) {
localStorage.removeItem(key);
return null; // expired
}
return data.value;
} catch {
return null;
}
}
// Cache API response for 15 minutes
setWithTTL("weatherData", { temp: 72, city: "NYC" }, 15 * 60 * 1000);
// Later...
const cached = getWithTTL("weatherData");
if (cached) {
console.log("Using cached data:", cached);
} else {
console.log("Cache expired, fetch fresh data");
}IndexedDB: The Browser's Real Database
IndexedDB is the production-grade solution for storing large datasets and offline application data. It supports images, audio files, documents, and complex object structures.
Opening a Database
// IndexedDB is async and event-based
// Perfect for: large datasets, offline apps, images, files
const request = indexedDB.open("MyAppDB", 1);
request.onupgradeneeded = (e) => {
const db = e.target.result;
// Create object stores (like tables)
if (!db.objectStoreNames.contains("notes")) {
db.createObjectStore("notes", { keyPath: "id" });
}
console.log("Database upgraded to version 1");
};
request.onsuccess = (e) => {
const db = e.target.result;
console.log("Database opened successfully:", db.name);
};
request.onerror = (e) => {
console.error("Database error:", e.target.error);
};CRUD Operations
// IndexedDB CRUD operations
// All operations must happen inside transactions
function openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open("NotesDB", 1);
request.onupgradeneeded = (e) => {
const db = e.target.result;
db.createObjectStore("notes", { keyPath: "id" });
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async function addNote(note) {
const db = await openDB();
const tx = db.transaction("notes", "readwrite");
const store = tx.objectStore("notes");
store.add({
id: Date.now(),
content: note,
createdAt: new Date().toISOString()
});
return new Promise((resolve, reject) => {
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
}
async function getAllNotes() {
const db = await openDB();
const tx = db.transaction("notes", "readonly");
const store = tx.objectStore("notes");
const request = store.getAll();
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
// Usage
await addNote("Learn IndexedDB");
await addNote("Build offline app");
const notes = await getAllNotes();
console.log("All notes:", notes);Storing Files and Blobs
IndexedDB can store images, audio, PDFs, and binary data — essential for PWAs and offline apps.
// Storing files and blobs in IndexedDB
// Perfect for: offline image editors, file managers, PWAs
async function saveImage(id, file) {
const db = await openDB();
const tx = db.transaction("images", "readwrite");
const store = tx.objectStore("images");
store.put({
id: id,
blob: file,
name: file.name,
type: file.type,
savedAt: new Date().toISOString()
});
return new Promise((resolve, reject) => {
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
}
async function loadImage(id) {
const db = await openDB();
const tx = db.transaction("images", "readonly");
const store = tx.objectStore("images");
const request = store.get(id);
return new Promise((resolve, reject) => {
request.onsuccess = () => {
const data = request.result;
if (data && data.blob) {
// Create URL for display
const url = URL.createObjectURL(data.blob);
resolve(url);
} else {
resolve(null);
}
};
request.onerror = () => reject(request.error);
});
}
// Usage with file input
// const file = fileInput.files[0];
// await saveImage("avatar", file);
// const imageUrl = await loadImage("avatar");
// document.querySelector("img").src = imageUrl;Checking Storage Availability
Important: Safari Private Mode often breaks IndexedDB entirely. Always build with fallbacks!
// Always check storage availability
// Safari Private Mode often breaks IndexedDB!
function isStorageAvailable(type) {
try {
const storage = window[type];
const test = "__storage_test__";
storage.setItem(test, "1");
storage.removeItem(test);
return true;
} catch (e) {
return false;
}
}
// Check before using
if (isStorageAvailable("localStorage")) {
localStorage.setItem("key", "value");
} else {
console.warn("localStorage not available");
// Use in-memory fallback
}
// Quota error handling
try {
localStorage.setItem("bigData", hugeString);
} catch (e) {
if (e.name === "QuotaExceededError") {
console.warn("Storage full! Clearing old data...");
localStorage.clear();
}
}Cross-Tab Communication
localStorage can act as a lightweight messaging system between tabs. Changes trigger events in other tabs automatically.
// Cross-Tab Communication with Storage Events
// localStorage changes trigger events in OTHER tabs
// In Tab 1: Broadcast a logout
function broadcastLogout() {
localStorage.setItem("globalLogout", Date.now().toString());
}
// In Tab 2: Listen for logout
window.addEventListener("storage", (e) => {
if (e.key === "globalLogout") {
console.log("Logout detected in another tab!");
window.location.href = "/login";
}
if (e.key === "userData") {
console.log("User data changed:", e.newValue);
// Refresh UI with new data
}
});
// Real-time sync between tabs
function syncData(key, value) {
localStorage.setItem(key, JSON.stringify(value));
}
// Usage: User updates profile in Tab 1
// → Tab 2 automatically reflects the changeOffline-First Architecture
This is the architecture behind modern productivity apps like Notion, Slack, and Linear. Save locally first, sync when online.
// Offline-First Sync Architecture
// Used by Notion, Slack, Linear, etc.
class OfflineSync {
constructor() {
this.queue = [];
this.isOnline = navigator.onLine;
window.addEventListener("online", () => this.processQueue());
window.addEventListener("offline", () => this.isOnline = false);
}
async save(action) {
// 1. Save to IndexedDB immediately (instant UI)
await this.saveToIndexedDB(action.data);
// 2. Queue sync job
this.queue.push({
id: crypto.randomUUID(),
type: action.type,
data: action.data,
timestamp: Date.now(),
retries: 0
});
// 3. Try to sync if online
if (this.isOnline) {
await this.processQueue();
}
}
async processQueue() {
while (this.queue.length > 0) {
const job = this.queue[0];
try {
await this.sendToServer(job);
this.queue.shift(); // Success - remove from queue
} catch (error) {
job.retries++;
if (job.retries > 3) {
this.queue.shift(); // Give up after 3 retries
console.error("Sync failed:", job);
}
break; // Wait before retrying
}
}
}
async saveToIndexedDB(data) { /* ... */ }
async sendToServer(job) { /* ... */ }
}
const sync = new OfflineSync();
sync.save({ type: "CREATE_NOTE", data: { title: "New note" } });Hybrid Storage Architecture
Professional apps combine all three storage types based on data characteristics:
// Production Storage Architecture Example
// Dashboard app with offline support
const storageMap = {
// localStorage: small, fast settings
theme: "localStorage",
language: "localStorage",
sidebarCollapsed: "localStorage",
recentSearches: "localStorage",
// sessionStorage: tab-specific state
currentStep: "sessionStorage",
tabId: "sessionStorage",
unsavedDraft: "sessionStorage",
// IndexedDB: large data & offline
tasks: "IndexedDB",
messages: "IndexedDB",
cachedReports: "IndexedDB",
attachments: "IndexedDB",
offlineQueue: "IndexedDB"
};
class HybridStorage {
async get(key) {
const type = storageMap[key];
switch(type) {
case "localStorage":
return JSON.parse(localStorage.getItem(key));
case "sessionStorage":
return JSON.parse(sessionStorage.getItem(key));
case "IndexedDB":
return await this.getFromIndexedDB(key);
default:
throw new Error("Unknown storage type for " + key);
}
}
async set(key, value) {
const type = storageMap[key];
switch(type) {
case "localStorage":
localStorage.setItem(key, JSON.stringify(value));
break;
case "sessionStorage":
sessionStorage.setItem(key, JSON.stringify(value));
break;
case "IndexedDB":
await this.saveToIndexedDB(key, value);
break;
}
}
async getFromIndexedDB(key) { /* ... */ }
async saveToIndexedDB(key, value) { /* ... */ }
}
// Clean, unified API for entire app
const storage = new HybridStorage();
await storage.set("theme", "dark");
await storage.set("tasks", [{ id: 1, title: "Learn storage" }]);Performance Best Practices
// Performance Best Practices
// ❌ BAD: Blocking the main thread on load
const config = JSON.parse(localStorage.getItem("config"));
heavyFunction(config); // UI freezes
// ✔ GOOD: Defer to idle time
requestIdleCallback(() => {
const config = JSON.parse(localStorage.getItem("config"));
heavyFunction(config);
});
// ❌ BAD: Writing on every keystroke
input.addEventListener("input", (e) => {
localStorage.setItem("draft", e.target.value);
});
// ✔ GOOD: Debounced writes
let timeout;
input.addEventListener("input", (e) => {
clearTimeout(timeout);
timeout = setTimeout(() => {
localStorage.setItem("draft", e.target.value);
}, 500);
});
// ❌ BAD: Large arrays in localStorage
localStorage.setItem("logs", JSON.stringify(hugeArray)); // Blocks UI
// ✔ GOOD: Use IndexedDB for large data
await db.transaction("logs", "readwrite")
.objectStore("logs")
.add(hugeArray);Safe JSON Parsing
Always wrap JSON.parse in try-catch to prevent app crashes from corrupted data.
// Safe JSON Parse (prevents crashes)
function safeParse(key, fallback = null) {
try {
const value = localStorage.getItem(key);
return value ? JSON.parse(value) : fallback;
} catch {
console.warn("Invalid JSON for key:", key);
localStorage.removeItem(key); // Clean up corrupted data
return fallback;
}
}
// Usage
const user = safeParse("user", { name: "Guest" });
const settings = safeParse("settings", {});
const items = safeParse("cartItems", []);
// Safe storage with validation
function safeSet(key, value, validator) {
if (validator && !validator(value)) {
throw new Error("Invalid value for " + key);
}
localStorage.setItem(key, JSON.stringify(value));
}
// Only accept valid themes
safeSet("theme", "dark", (v) => ["light", "dark", "system"].includes(v));🎯 Mini-Challenge: Recently Viewed
This is a real feature you will be asked to build, and it uses every rule in this lesson at once: JSON on the way in and out, a fallback for the very first visit, and a safe parse in case what is stored is rubbish. No starter logic this time — just the brief and the calls that test it.
Read the expected output before you start writing. It is the specification, and it is not quite what a first guess produces.
// 🎯 MINI-CHALLENGE: a "recently viewed" list
//
// Nearly every shop has one. The rules are:
// - newest first
// - no duplicates: viewing something again moves it to the front
// - never longer than 5 entries
// - it survives a reload, so it lives in localStorage as JSON
//
// Write these two functions:
//
// 1. readList() -> returns the stored array, or [] when nothing is stored
// or the stored text is not valid JSON (use try/catch).
//
// 2. addRecent(id) -> read the list, remove any existing copy of id
// (Array.filter), put id at the front (Array.unshift),
// cut it back to 5 (Array.slice), then save it with
// JSON.stringify.
//
// Then run the calls at the bottom, which are already written.
localStorage.removeItem("demo:recent"); // reset, so the demo is repeatable
// your code here
["p1", "p2", "p3", "p1", "p4", "p5", "p6"].forEach(addRecent);
console.log(readList().join(", "));
console.log("length: " + readList().length);
// ✅ Expected output:
// p6, p5, p4, p1, p3
// length: 5
//
// Read that first line carefully before you start - it is the whole spec.
// p1 appears third from the end because it was viewed twice and jumped the
// queue the second time, and p2 is gone because the cap of 5 pushed it off.Common Mistakes Developers Make
❌ Security Mistakes
- • Storing passwords in localStorage
- • Storing JWT tokens (XSS can steal them)
- • Storing PII or banking data
❌ Performance Mistakes
- • Storing huge arrays in localStorage
- • Writing on every keystroke
- • Loading large storage synchronously
❌ Logic Mistakes
- • Forgetting to JSON.stringify objects
- • Assuming storage always exists
- • Not handling quota errors
✔ Best Practices
- • Use storage service layers
- • Check storage availability first
- • Use IndexedDB for large data
Practice Exercises
Build these projects to master Web Storage:
- A theme switcher using localStorage
- A multi-step checkout using sessionStorage
- A fully offline notes app using IndexedDB
- A custom wrapper library that unifies all three storage types
- A cache system that stores API responses with TTL
- An IndexedDB migration (version 1 → 2 → 3)
- A "Recently Viewed Items" tracker
- Cross-tab real-time sync for a shopping cart
What You Learned
- ✔ localStorage for persistent settings
- ✔ sessionStorage for tab-specific state
- ✔ IndexedDB for large datasets
- ✔ Building storage service layers
- ✔ TTL-based caching patterns
- ✔ Cross-tab communication
- ✔ Offline-first sync architecture
- ✔ Hybrid storage design
- ✔ Storing files and blobs
- ✔ Safe JSON parsing
🎉 Lesson Complete!
You now understand how to store data client-side using localStorage, sessionStorage, and IndexedDB.
Practice quiz
How long does data in localStorage persist?
- Until the tab closes
- For one hour
- Forever, until manually cleared
- Until the page reloads
Answer: Forever, until manually cleared. localStorage persists indefinitely until the user or code clears it; sessionStorage clears on tab close.
When is sessionStorage data deleted?
- When the tab closes
- After 24 hours
- Never automatically
- On every reload
Answer: When the tab closes. sessionStorage is wiped when the tab closes, making it ideal for tab-specific, temporary state.
How is sessionStorage scoped across browser tabs?
- Shared across all tabs
- Shared only within the same domain
- Shared with incognito tabs
- Isolated per tab
Answer: Isolated per tab. Each tab has its own separate sessionStorage; localStorage is shared across tabs of the same origin.
What type are values returned by localStorage.getItem?
- Always the original object
- Always strings
- Numbers when numeric
- null for objects
Answer: Always strings. Web Storage values are always strings; you must JSON.parse objects and Number() numeric values.
What is logged? localStorage.setItem('count', 1); console.log(localStorage.getItem('count') + 1);
- '11'
- 2
- 11
- It throws
Answer: '11'. getItem returns the string '1', so '1' + 1 is string concatenation giving '11'. Convert with Number() first.
How should you store an object in localStorage?
- Pass it directly to setItem
- Use localStorage.setObject
- JSON.stringify it before setItem
- Spread it into setItem
Answer: JSON.stringify it before setItem. Storing an object directly yields '[object Object]'; use JSON.stringify on save and JSON.parse on read.
Which storage type is async, event-based, and handles large data and blobs?
- localStorage
- IndexedDB
- sessionStorage
- Cookies
Answer: IndexedDB. IndexedDB is asynchronous and built for large datasets, objects, and files; Web Storage is synchronous and small.
Roughly how much can localStorage typically hold?
- About 4 KB
- Several GB
- Unlimited
- About 5-10 MB
Answer: About 5-10 MB. localStorage and sessionStorage are limited to roughly 5-10 MB; cookies are ~4 KB; IndexedDB can hold GBs.
Which event lets one tab detect a localStorage change made in another tab?
- The 'change' event
- The 'storage' event
- The 'message' event
- The 'sync' event
Answer: The 'storage' event. Writing to localStorage fires a 'storage' event in other tabs, enabling lightweight cross-tab communication.
Why should JSON.parse on stored data be wrapped in try/catch?
- To speed it up
- Because parse is async
- To prevent crashes from corrupted or invalid JSON
- To stringify the result
Answer: To prevent crashes from corrupted or invalid JSON. Corrupted stored data can make JSON.parse throw; a safe parser catches it and returns a fallback.
Continue this course
- Previous: Advanced Fetch API Patterns (Retry, Timeout, AbortController)
- Next: Building Custom APIs with Fetch & Async Logic — Design reusable API modules with auth, caching, and error handling built in
- Quick reference: JavaScript cheat sheet
- From the blog: How to Learn to Code Fast (Without Burning Out)