localStorage vs sessionStorage
Lifetime
Section titled “Lifetime”The most important difference between the two stores is how long data lives:
localStorage
- Data persists until it is explicitly removed with
removeItemorclear, or the user clears browser data. - Closing all tabs, closing the browser, or restarting the machine does not remove the data.
- This makes it suitable for preferences, themes, or cached values that should survive sessions (e.g., dark mode toggle, saved language choice).
sessionStorage
- Data exists only for the lifetime of the browser tab (or window) in which it was created.
- As soon as that tab is closed, the data is gone — no recovery.
- Opening a new tab to the same URL starts a fresh, empty
sessionStorage. - This makes it suitable for tab-local state that should not leak across tabs (e.g., a multi-step wizard’s current step, a one-time token, a temporary form draft).
Tab and window scope
Section titled “Tab and window scope”Scope is where the two APIs differ most subtly:
// Tab A:localStorage.setItem('demo:user', 'Ada');// Tab B on the same origin sees 'Ada' immediately.
sessionStorage.setItem('demo:step', '2');// Tab B has its OWN sessionStorage — it cannot see Tab A's 'step'.There is one nuance: when a user opens a link with window.open() or Ctrl+click (which duplicates the tab), the new tab receives a copy of the parent tab’s sessionStorage at the moment it is opened. After that, the two stores are independent — changes in one do not propagate to the other.
Origin scope
Section titled “Origin scope”Both stores are scoped to the origin: scheme + hostname + port.
| URL | Origin |
|---|---|
https://example.com | https://example.com:443 |
https://app.example.com | https://app.example.com:443 (different!) |
http://example.com | http://example.com:80 (different scheme!) |
https://example.com:8080 | https://example.com:8080 (different port!) |
So https://app.example.com and https://api.example.com each have their own completely separate localStorage even though they share the same registrable domain.
Runnable: writing to both stores
Section titled “Runnable: writing to both stores”Run the snippet below to see both stores written and read back in the same page context.
Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
localStorage | Data persists and is shared across every tab of the same origin — good for long-lived preferences | A write from any tab affects every tab, risking a race condition if multiple tabs write at once |
sessionStorage | Scoped to a single tab, reducing the risk of data leaking across tabs, and disappears when the tab closes | Must be re-populated every time a new tab opens — not suitable for long-lived preferences |
Sharing state via sessionStorage when duplicating a tab | The new tab gets the same context immediately on open (e.g. duplicating mid-form) | It’s a one-time copy — the two tabs can’t sync afterward and may diverge |
Common mistakes
Section titled “Common mistakes”- Assuming
sessionStoragesyncs across tabs likelocalStorage— each tab has its own separatesessionStorage; a write in Tab A is never visible in Tab B. - Storing an auth token in
localStorage— because JavaScript can read it directly, an XSS hole on the origin can steal it easily, unlike anHttpOnlycookie that JS can’t touch. - Confusing a subdomain with the same origin —
app.example.comandapi.example.comalways have separatelocalStorage, even though the registrable domain is the same.