Describe the difference between a cookie, `sessionStorage` and `localStorage` in browsers
TL;DR
Cookies, localStorage, and sessionStorage all store data in the browser, but they differ in lifetime, scope, server interaction, and security controls. localStorage and sessionStorage implement the Web Storage API.
- Cookies: Small values that the browser sends with matching HTTP requests. They can be session or persistent cookies and support controls such as
HttpOnly,Secure, andSameSite. localStorage: Origin-scoped string storage that persists until it is cleared and is shared by same-origin tabs and windows.sessionStorage: Origin- and tab-scoped string storage for a page session. It survives reloads but is normally cleared when the tab or window closes.
Here's a table summarizing the 3 client storage mechanisms.
| Property | Cookie | localStorage | sessionStorage |
|---|---|---|---|
| Initiator | Client or server. Server can use Set-Cookie header | Client | Client |
| Lifespan | As specified | Until deleted | Until tab is closed |
| Persistent across browser sessions | If a future expiry date is set | Yes | No |
| Sent to server with matching HTTP requests | Yes, via the Cookie header | No | No |
| Typical storage limit | About 4 KB per cookie | Browser-dependent quota, commonly several MiB per origin | Browser-dependent quota, commonly several MiB per origin |
| Access | Across windows/tabs | Across windows/tabs | Same tab |
| Security | JavaScript cannot access HttpOnly cookies | None | None |
Scope and data flow
The mechanisms differ most in who can access the data, how long it lasts, and whether it travels with HTTP requests.
Web Storage is never attached automatically to requests; an HttpOnly cookie is intentionally unavailable to JavaScript.
Storage on the web
Cookies, localStorage, and sessionStorage are browser storage mechanisms. Client storage is useful for state such as themes, personalized layouts, draft form data, and identifiers needed by a server session. Sensitive authentication credentials require a threat-model-specific design; they should not be placed in Web Storage by default.
These client-side storage mechanisms have the following common properties:
- Client-side JavaScript can read and modify the values, except for
HttpOnlycookies. - Key-value based storage.
- They are only able to store values as strings. Non-strings will have to be serialized into a string (e.g.
JSON.stringify()) in order to be stored.
Use cases for each storage mechanism
Since cookies have a relatively low maximum size, it is not advisable to store all your client-side data within cookies. The distinguishing properties about cookies are that cookies are sent to the server on every HTTP request so the low maximum size is a feature that prevents your HTTP requests from being too large due to cookies. Automatic expiry of cookies is a useful feature as well.
With that in mind, cookies suit small values that the server needs, such as opaque session identifiers, analytics identifiers, consent choices, or language preferences used during server rendering. Sensitive cookies can benefit from HttpOnly, Secure, and SameSite; Expires or Max-Age controls persistence. The server must still validate and authorize every request.
localStorage and sessionStorage both implement the Web Storage API interface. Their quota is browser-dependent and can be exceeded, so applications should handle QuotaExceededError. Values stored in Web Storage are not automatically sent with HTTP requests.
While you can manually include values from Web Storage when making AJAX/fetch() requests, the browser does not include them in the initial request / first load of the page. Hence Web Storage should not be used to store data that is relied on by the server for the initial rendering of the page if server-side rendering is being used (typically authentication/authorization-related information). localStorage is most suitable for user preferences data that do not expire, like themes and layouts (if it is not important for the server to render the final layout). sessionStorage is most suitable for temporary data that only needs to be accessible within the current browsing session, such as form data (useful to preserve data during accidental reloads).
The following sections dive deeper into each client storage mechanism.
Cookies
Cookies are used to store small pieces of data on the client side that can be sent back to the server with every HTTP request.
- Storage capacity: Limited to around 4 KB per cookie. Browsers also limit how many cookies can be stored per domain.
- Lifespan: Cookies can have a specific expiration date set using the
ExpiresorMax-Ageattributes. Without one, they are session cookies, although browsers may restore session cookies as part of session restore. - Access: Cookies are domain-specific and can be shared across different pages and subdomains within the same domain.
- Security: Cookies can be marked as
HttpOnlyto prevent access from JavaScript, reducing the risk of XSS attacks. They can also be secured with theSecureflag to ensure they are sent only when HTTPS is used.
// Set a non-sensitive preference cookie with an expiry.document.cookie ='theme=dark; expires=Fri, 31 Dec 2100 23:59:59 GMT; path=/; SameSite=Lax; Secure';// Read all cookies. There's no way to read specific cookies using `document.cookie`.// You have to parse the string yourself.console.log(document.cookie); // theme=dark// Delete the cookie with the name/key `theme` by setting an// expiry date in the past. The value doesn't matter.document.cookie = 'theme=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
It is a pain to read/write to cookies. document.cookie returns a single string containing all the key/value pairs delimited by ; and you have to parse the string yourself. The js-cookie npm library provides a simple and lightweight API for reading/writing cookies in JavaScript.
A modern native way of accessing cookies is via the Cookie Store API which is only available on HTTPS pages.
// Set a cookie. More options are available too.cookieStore.set('theme', 'dark');// Async method to access a single cookie and do something with it.cookieStore.get('theme').then(...);// Async method to get all cookies.cookieStore.getAll().then(...);// Async method to delete a single cookie.cookieStore.delete('theme').then(() =>console.log('Cookie deleted'));
The Cookie Store API may not be supported in all browsers. Refer to caniuse.com for the latest compatibility.
localStorage
localStorage is used for storing data that persists even after the browser is closed and reopened. It is designed for long-term storage of data.
- Storage capacity: Typically around 5MB per origin (varies by browser).
- Lifespan: Data in
localStoragepersists until explicitly deleted by the user or the application. - Access: Data is accessible within all tabs and windows of the same origin.
- Security: All JavaScript on the page has access to values within
localStorage.
// Set a value in localStorage.localStorage.setItem('key', 'value');// Get a value from localStorage.console.log(localStorage.getItem('key'));// Remove a value from localStorage.localStorage.removeItem('key');// Clear all data in localStorage.localStorage.clear();
sessionStorage
sessionStorage is used to store data for the duration of the page session. It is designed for temporary storage of data.
- Storage capacity: Typically around 5MB per origin (varies by browser).
- Lifespan: Data in
sessionStorageis cleared when the page session ends (i.e., when the browser or tab is closed). Reloading the page does not destroy data withinsessionStorage. - Access: Data is accessible only within the current tab (or browsing context). Different tabs share different
sessionStorageobjects even if they belong to the same browser window. In this context, window refers to a browser window that can contain multiple tabs. - Security: All JavaScript on the same page has access to values within
sessionStoragefor that page.
// Set a value in sessionStorage.sessionStorage.setItem('key', 'value');// Get a value from sessionStorage.console.log(sessionStorage.getItem('key'));// Remove a value from sessionStorage.sessionStorage.removeItem('key');// Clear all data in sessionStorage.sessionStorage.clear();
Security
A side-by-side feature comparison hides the most important practical difference between these three: how each one behaves under XSS.
| Cookie | localStorage / sessionStorage | |
|---|---|---|
| Reachable from arbitrary JS on the origin | Only if not HttpOnly | Always |
HttpOnly flag (cannot be read from JS) | Yes | No equivalent |
Secure flag (HTTPS-only transport) | Yes | N/A (values never leave the client unless your code sends them) |
SameSite flag (CSRF defense: Strict, Lax, None) | Yes | N/A |
| Partitioned cookies (CHIPS, isolated per top-level site) | Yes (modern browsers) | N/A |
| Can an injected script read the value? | Not if the cookie is HttpOnly | Yes |
The practical takeaways:
- Opaque session identifiers are commonly stored in
HttpOnly; Secure; SameSite=Laxcookies. Any injected script on the origin can read Web Storage.HttpOnlyprevents it from reading the cookie value, but it does not make the application survive XSS: the script may still perform authenticated same-origin actions or steal data visible to the page. - Cookie authentication needs CSRF defenses.
SameSitemitigates many cross-site requests, and applications may also validate CSRF tokens,Origin, or Fetch Metadata. These controls do not stop a script already executing on the application's own origin. localStorageis fine for non-sensitive client state: theme preferences, layout settings, draft text, recently viewed items.
Real-world set, get, and remove
The three APIs look superficially similar but have meaningfully different ergonomics. Here is the same operation in each:
// localStorage: synchronous, string-onlylocalStorage.setItem('user', JSON.stringify({ id: 1, name: 'Ada' }));const user = JSON.parse(localStorage.getItem('user') ?? 'null');localStorage.removeItem('user');// sessionStorage: same shape, scoped to the tabsessionStorage.setItem('draft', 'hello');const draft = sessionStorage.getItem('draft');sessionStorage.removeItem('draft');// Cookie: modern async API (where supported)await cookieStore.set({name: 'theme',value: 'dark',sameSite: 'Lax',secure: true,expires: Date.now() + 7 * 24 * 60 * 60 * 1000,});const theme = await cookieStore.get('theme');await cookieStore.delete('theme');// Cookie: legacy `document.cookie` API (universally supported)document.cookie = 'theme=dark; Path=/; Max-Age=604800; SameSite=Lax; Secure';// Reading a specific cookie still requires parsing the string yourself:const value = document.cookie.split('; ').find((row) => row.startsWith('theme='))?.split('=')[1];
A few common mistakes:
localStorageandsessionStorageonly store strings.localStorage.setItem('count', 0)stores"0"andlocalStorage.getItem('count')returns"0"(a string). Always serialize and deserialize explicitly.- Assigning
document.cookie = '...'does not clear other cookies. Each assignment sets or updates a single cookie. To delete one, set it again withMax-Age=0or anexpiresdate in the past. cookieStore.setis async;localStorage.setItemis synchronous. Mixing them in the same logic without awaiting the cookie call leads to ordering bugs.
Beyond these three: IndexedDB and Cache Storage
Modern apps frequently need more than what these three APIs offer. Two more are worth knowing:
- IndexedDB: an in-browser, asynchronous, transactional database. Use it for large structured data (offline app state, large user-generated content, search indexes), MBs to GBs of storage, and queryable data. Wrappers like Dexie.js and idb make the API more pleasant.
- Cache Storage (
caches): paired with Service Workers, this stores HTTP request/response pairs for offline-capable apps and PWAs. It is not a general-purpose key-value store; it is specifically for caching network responses. localStorageis for simple key-value config only. If you find yourself JSON-stringifying complex nested data intolocalStorage, IndexedDB is usually a better fit.
Notes
There are also other client-side storage mechanisms like IndexedDB which is more powerful than the above-mentioned technologies but more complicated to use.