What you'll learn
Quick Answer
localStorage persists until cleared and holds about 5 MB. sessionStorage clears when the tab closes. Cookies are small and get sent to the server on every request. Store preferences in localStorage; do not store authentication tokens there.
The API is four methods
localStorage.setItem("theme", "dark");
console.log(localStorage.getItem("theme")); // "dark"
localStorage.removeItem("theme");
localStorage.clear();
sessionStorage has exactly the same methods. The only difference is lifetime: localStorage survives closing the browser, sessionStorage is wiped when the tab closes and is not shared between tabs.
Reading a key that does not exist returns null, not undefined — worth knowing when writing the check.
Everything is a string, which catches everyone
localStorage.setItem("count", 5);
const n = localStorage.getItem("count");
console.log(typeof n); // "string"
console.log(n + 1); // "51" <-- concatenation, not addition
Storage holds strings only. The number 5 became "5", and adding 1 concatenated. Convert on the way out with Number(n) or parseInt(n, 10).
Objects and arrays need JSON on both sides:
const user = { name: "Asha", marks: [91, 88] };
localStorage.setItem("user", JSON.stringify(user));
const back = JSON.parse(localStorage.getItem("user"));
console.log(back.marks[0]); // 91
Forgetting stringify stores the literal text "[object Object]", which is a distinctive and frequently-seen bug. And wrap JSON.parse in a try/catch — stored data can be corrupted or left over from an older version of your app, and an exception at startup breaks the whole page.
How cookies differ
Cookies predate both and behave differently in ways that matter:
- They are sent to the server automatically with every request to that domain. localStorage is never sent anywhere unless your code sends it.
- They are small — roughly 4 KB each, against about 5 MB for localStorage.
- They can expire at a set time.
- They can be made invisible to JavaScript using the HttpOnly flag.
That automatic transmission is the whole point: it is how a server recognises you across requests, which is why sessions use cookies. It is also a cost — a large cookie is re-sent with every image and API call on the page.
Why tokens do not belong in localStorage
This is the part with real consequences.
Any JavaScript running on your page can read localStorage. That includes injected script — a cross-site scripting flaw, or a compromised third-party dependency. If an authentication token is there, it can be read and sent elsewhere, and the attacker is now logged in as your user.
A cookie marked HttpOnly cannot be read by JavaScript at all. Script can still cause requests to be made, but it cannot steal the token itself, which meaningfully limits the damage.
So the practical guidance: authentication tokens belong in HttpOnly, Secure, SameSite cookies set by the server. localStorage is for non-sensitive client state — theme, language, draft text, UI preferences.
Plenty of tutorials store JWTs in localStorage because it is simpler. Know that it is a trade-off with a known failure mode, and be able to say so in an interview — see API authentication explained.
Practical notes
Storage is per origin. Scheme, domain and port together — so localhost:3000 and localhost:5000 have entirely separate storage, which explains a surprising amount of local development confusion.
It can fail. In private browsing modes or when the quota is full, setItem throws. If your app cannot start without it, wrap it:
try {
localStorage.setItem("theme", "dark");
} catch (e) {
// quota exceeded or storage unavailable
}
It is synchronous. Reading or writing blocks the main thread, so avoid large values or writes inside a scroll handler.
Namespace your keys — myapp:theme rather than theme — since everything on the origin shares one space and collisions are silent.
