Your application sets a cookie with 'Set-Cookie: prefs=x; Max-Age=31536000' and expects it a month later, yet some users lose it. What browser behaviours remove a cookie before its stated expiry, and how do you design around that?
answer
- Max-Age is an upper bound, not a promise
- ~400-day cap; ITP cuts script-set cookies to 7 days / 24h
- per-domain ~180 cookies + byte cap, LRU eviction
- noisy sibling subdomain can evict yours
- cookie = pointer + cache; server holds the truth; refresh on use
basics
~20 sCookies are best-effort storage. Browsers cap lifetimes (roughly 400 days, and far less for script-set cookies under some privacy engines), evict by least-recently-used when per-domain or total cookie limits are hit, clear on user or policy action, and drop everything in private mode. Treat cookies as a cache, keep the truth server-side, and refresh on use.
solid answer
~60 sA stated `Max-Age` is an upper bound the browser may shorten, never a guarantee. Four causes:\n\n1. **Lifetime caps.** Current browsers clamp cookie expiry to about **400 days** regardless of what you send. Privacy engines cut script-written cookies far shorter — Safari's ITP caps `document.cookie` cookies to 7 days, or 24 hours in some cross-site cases. `Set-Cookie` from the server avoids the harshest caps.\n2. **Eviction.** Cookie jars are bounded — on the order of ~180 cookies per domain and a few thousand overall, plus a per-domain byte budget. Exceeding it evicts, typically least-recently-used, and yours can be the victim of a noisy sibling subdomain.\n3. **Clearing.** \"Clear browsing data\", clear-on-exit settings, enterprise policy, profile resets, and private windows, which discard everything on close.\n4. **Storage pressure and device changes**, plus a different browser, profile or device entirely.\n\nDesign: cookies hold *pointers and preferences*, never the only copy of anything. Keep authoritative state server-side keyed by an ID, re-issue the cookie on each response so active users get a sliding window, keep the cookie count and size small, and make every read tolerate absence with a sane default.
code
http · 1 lineHTTP/1.1 200 OK\nSet-Cookie: prefs=u9f2; Path=/; Max-Age=2592000; Secure; HttpOnly; SameSite=Lax\nCache-Control: private, no-storego deeper
Know that cookies can be cleared by the user or lost, so code must handle the cookie simply not being there.
Name the concrete mechanisms — lifetime clamps, per-domain limits with LRU eviction, private mode — and the fix of re-issuing on use.
Diagnose it: distinguish policy caps from your own bug by measuring cookie-absent rates, audit jar usage across subdomains, and restructure to an opaque ID plus server-side state.
Own the platform policy: which cookies may exist per domain, size and count budgets, server-set only for anything important, and a plan for continuing browser privacy tightening.
## The premise to unlearn\n\n`Max-Age=31536000` is a *request* for one year of storage. The browser is free to store it for less, and routinely does. Any design that assumes "I set it, therefore it is there" will fail for a measurable slice of users, and the failures look random because they depend on the user's browser, settings and behaviour rather than on your code.\n\n## 1. Lifetime caps imposed by the browser\n\nChromium and Firefox clamp cookie expiry to a maximum of roughly **400 days** from the time of setting, in line with the direction of RFC 6265bis; anything longer is silently reduced. That alone breaks the common "set it for ten years" idiom.\n\nPrivacy engines go much further, and the key distinction is **who wrote the cookie**:\n\n- Cookies written by JavaScript via `document.cookie` are treated with suspicion. Safari's Intelligent Tracking Prevention caps them at **7 days**, and as little as **24 hours** when the page was reached from a classified cross-site navigation.\n- Cookies written by the server in a `Set-Cookie` response header are not subject to that particular cap, which is a concrete architectural reason to set important cookies from the server rather than from script (on top of the fact that server-set cookies can be `HttpOnly`).\n\nThird-party (cross-site) cookies are a separate story again: increasingly blocked outright, so anything relying on them should already be re-architected.\n\n## 2. Eviction under limits\n\nBrowsers bound the cookie jar. The usual figures are around **180 cookies per domain**, a few thousand in total, and a per-domain byte cap (roughly 4 KB per cookie including attributes, plus an aggregate budget). When a limit is reached, the browser evicts — commonly the least-recently-used, sometimes preferring already-expired or non-secure entries.\n\nThe cruel part is the blast radius: limits are per *registrable domain*, so a marketing subdomain that writes 150 tracking cookies can push your carefully-set preference cookie out of the jar. Every cookie you add is a small tax on every other cookie on the domain, and on the size of every single request you send.\n\n## 3. User, policy and mode\n\nUsers clear browsing data; browsers offer "clear cookies on exit"; enterprise policy and antivirus "privacy cleaners" wipe jars on a schedule; private/incognito windows keep a separate jar destroyed on close; a profile switch or a second device has no shared jar at all. None of these is exotic.\n\n## 4. Storage pressure and corruption\n\nUnder disk pressure or after an unclean shutdown, browsers may discard part of the cookie store. Rare, but it happens, and it happens most to the users with the oldest, fullest devices.\n\n## Designing for a lossy store\n\n**Server-side truth.** The cookie holds an opaque identifier; everything meaningful lives server-side under that key. Losing the cookie then costs a re-login or a reset to defaults, never data.\n\n**Sliding refresh.** Re-send `Set-Cookie` with a fresh `Max-Age` on responses (or periodically, to avoid a header on every response). Active users keep a rolling window and never trip the absolute cap; inactive ones age out, which is usually what you wanted anyway.\n\n**Budget the jar.** One cookie, not eight. Do not shard preferences across cookies; store an ID and keep the payload on the server. Keep cookies small — they are sent on *every* matching request, so a 3 KB cookie is a permanent upload tax on your own latency as well as an eviction risk.\n\n**Scope narrowly.** Setting `Domain=example.com` shares your cookie with every subdomain and drags it into their requests; host-only cookies keep the blast radius small in both directions.\n\n**Absence is a normal state.** Every read path needs a default. "Cookie missing" means anonymous, or default theme, or re-authenticate — never a 500, never a broken page, never lost user work.\n\n**Fallbacks, chosen deliberately.** `localStorage` survives some cookie clearing but not most privacy clearing, is origin-scoped, is not sent automatically, and is readable by script — so it is fine for a UI preference and wrong for a credential. Server-side per-account preferences are the durable answer for signed-in users.\n\n**Measure it.** Instrument the rate of requests arriving without the cookie that you would have expected to have one. It is the only way to tell a browser policy change from a bug in your own code, and the number moves whenever a browser ships a new privacy release.
- Would moving the value to localStorage solve the disappearing-state problem?Only partly, and it trades one problem for others. localStorage survives some cookie-specific clearing but is wiped by the same 'clear site data' actions and by privacy engines, is origin-scoped, is not sent automatically with requests, and is readable by any script on the page — so it must never hold a credential. For signed-in users, per-account server-side storage is the durable answer.
- Why prefer setting an important cookie from the server rather than from document.cookie?Server-set cookies escape the harshest privacy lifetime caps that apply to script-written cookies — Safari's ITP clamps document.cookie cookies to about 7 days. Server-set cookies can also be HttpOnly, so cross-site scripting cannot read them, and SameSite and Secure are applied consistently in one place rather than in scattered client code.
saying these in an interview costs you the question
- Assuming a cookie with a future Max-Age is guaranteed to still exist
- Setting a ten-year expiry and being unaware of the ~400-day clamp
- Storing the only copy of user state in a cookie with no server-side record
- Ignoring that per-domain cookie limits are shared with sibling subdomains
- Treating a missing cookie as an error condition rather than a normal anonymous state