A logout handler runs `document.cookie = "session=; max-age=0"`, but the cookie is still sent on the next request. Give every reason a JavaScript cookie deletion can silently fail, and the correct way to delete one.
answer
- no delete API, only expiry
- identity is name plus domain plus path
- default path is not the site root
- HttpOnly ones are the server's to clear
- silent failure, so read back
basics
~20 sThere is no delete API — you expire a cookie by rewriting it with the same name, path and domain plus max-age=0. A mismatched path or domain writes a different cookie, and an HttpOnly or Secure cookie cannot be touched from script at all.
solid answer
~50 sDeleting is really overwriting: you re-set the same cookie with `max-age=0` or an `expires` date in the past, and the browser drops it. The catch is that a cookie's identity is the triple *name + domain + path*, so the delete write must reproduce the original's path and domain exactly. `"session=; max-age=0"` with no `path` defaults to the current document's directory, so on `/app/logout` it expires a cookie scoped to `/app` — while the real `/`-scoped one survives untouched. The other failure modes: the cookie is `HttpOnly`, so script cannot modify it and only a server `Set-Cookie` can clear it; it is `Secure` and the page is not; it was set with `Domain=.example.com` while you are deleting host-only; or it carries a `__Host-`/`__Secure-` name prefix whose attribute requirements your delete write does not satisfy. And since the getter never returns attributes, you cannot discover the right path by inspection — whoever writes a cookie has to record its scope.
code
javascript · 13 linesfunction deleteCookie(name, { path = '/', domain } = {}) {
const scope = domain ? `; domain=${domain}` : '';
document.cookie = `${name}=; path=${path}${scope}; max-age=0`;
// Read back: the only available (partial) confirmation.
return !document.cookie.split('; ').some((p) => p.startsWith(name + '='));
}
// Wrong on /app/logout: defaults to path=/app, misses the /-scoped cookie.
document.cookie = 'session=; max-age=0';
// Right: reproduce the scope the cookie was written with.
deleteCookie('session', { path: '/' });
deleteCookie('pref', { path: '/', domain: 'example.com' });go deeper
Know that you remove a cookie by re-setting it with max-age=0 or a past expires date, and that the value you write does not matter.
Explain that a cookie is identified by name, domain and path together, so a delete that omits path targets the current directory and quietly misses the real cookie.
Diagnose the whole failure set under time pressure — path, domain, HttpOnly, Secure, name prefixes, silent writes — and argue for a single helper that records each cookie's scope because the read API cannot recover it.
Own logout as a server-side operation: invalidating the session record rather than the browser's copy, and setting the org-wide rule that credential cookies are never script-managed.
## There is no delete operation The `document.cookie` API exposes exactly two operations, read-all and write-one. Deletion is expressed as a write whose effect is immediate expiry: ```js document.cookie = 'session=; path=/; max-age=0'; // equivalently document.cookie = 'session=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT'; ``` The value is irrelevant — the browser removes the cookie because its lifetime has already elapsed. `Max-Age=0` and a past `Expires` are equivalent; `Max-Age` wins if you supply both. ## Cookie identity is a triple A cookie is keyed by **name + domain + path**, not by name alone. Two cookies named `session`, one at `Path=/` and one at `Path=/app`, are two distinct entries that both appear as `session=...` when you read the jar. A write only replaces an existing cookie when all three parts match; otherwise it **creates a new one**. That is exactly what the failing handler does. Run on `/app/logout` with no `path` attribute, the default path becomes `/app`, so the browser either expires an `/app`-scoped `session` that never existed or briefly creates and drops one. The `/`-scoped cookie the server set is untouched and continues to be sent. ```js // On /app/logout document.cookie = 'session=; max-age=0'; // targets /app — wrong cookie document.cookie = 'session=; path=/; max-age=0'; // targets / — correct ``` The symmetrical trap is `Domain`. A cookie set with `Domain=example.com` is shared across subdomains; one set with no `Domain` is host-only for exactly `app.example.com`. Deleting requires matching that choice: ```js document.cookie = 'session=; path=/; domain=example.com; max-age=0'; ``` And because the getter never returns attributes, **there is no way to find out** which of the two variants exists. You cannot inspect your way to the answer; the code that wrote the cookie must know the scope, which is the argument for routing every cookie write and delete through one helper that carries the scope with the name. ## Failures script cannot fix at all - **`HttpOnly`.** Script cannot read, modify, or expire such a cookie. A same-named write creates or touches a separate non-HttpOnly cookie while the real one survives. Logout must be a server request answered with `Set-Cookie: session=; Path=/; Max-Age=0`. - **`Secure` from an insecure page.** A page on `http:` cannot overwrite a `Secure` cookie, so the delete is dropped. - **Name prefixes.** A cookie named `__Host-session` or `__Secure-session` is only accepted by the browser when the write carries the attribute set its prefix requires; a delete write that omits them is rejected like any other non-conforming write, and the cookie stays. - **Nothing reports any of this.** The setter returns nothing and throws nothing, so every one of these failures is silent. ## Verifying, and its limits Read the jar back: ```js function deleteCookie(name, { path = '/', domain } = {}) { const d = domain ? `; domain=${domain}` : ''; document.cookie = `${name}=; path=${path}${d}; max-age=0`; return !document.cookie.split('; ').some((p) => p.startsWith(name + '=')); } ``` This tells you whether *a* cookie of that name is still visible — useful, but it cannot distinguish "deleted" from "there was a second one at another path", and it says nothing about HttpOnly cookies, which were never visible. A robust logout therefore does not rely on client-side deletion at all: it calls the server, lets the server expire its own cookies, and treats the subsequent `401` as the state transition. Client-side deletion is for cookies script itself created — a UI preference, an A/B bucket, a dismissed-banner marker. ## The shortcut people reach for, and why it is wrong "Loop over `document.cookie` and expire everything" fails for the same reasons: it can only see script-visible cookies, it does not know their paths, and it will happily leave duplicates at other scopes. If a flow genuinely needs the whole origin cleared, that is a server-side sign-out plus, at most, expiring the specific cookies you own with the scopes you recorded when you wrote them.
- How would you find out which path an existing cookie is scoped to, so you can delete it?From `document.cookie` you cannot — the getter returns names and values only, and two cookies of the same name at different paths look identical. Either the writing code records the scope, or you read the `Set-Cookie` header in devtools to see what the server chose. This is why cookie writes and deletes belong in one module that keeps the scope next to the name.
- Is `Max-Age=0` different from an `Expires` date in the past?Not in effect — both mark the cookie as already expired so the browser removes it. `Max-Age` is a relative lifetime in seconds and is the clearer choice because it does not depend on the client's clock being right or on formatting an HTTP date correctly. When a write supplies both, `Max-Age` takes precedence.
- On logout, why is a server round trip better than clearing cookies in the browser?Because the credential is normally HttpOnly and unreachable from script, and because deleting the browser's copy would not invalidate the session server-side — the same value replayed from anywhere would still work. The server expires its own cookie with a matching `Set-Cookie` and drops the session record; the client then just handles the resulting 401.
saying these in an interview costs you the question
- Expects a document.cookie delete method or removeCookie call
- Ignores path and domain when expiring a cookie
- Thinks setting the value to an empty string removes the cookie
- Believes script can expire an HttpOnly session cookie
- Assumes a failed delete reports an error