In a browser page, how do you detect that the tab has been hidden, and what values can `document.visibilityState` take?
answer
- two states, not three
- event fires on document
- boolean shorthand exists
- hidden means nothing is presented
- check it at startup too
basics
~20 sListen for the visibilitychange event on document and read document.visibilityState, which is either "visible" or "hidden" (document.hidden is the boolean shorthand). It flips to hidden on tab switches, window minimising, app switching and screen lock.
solid answer
~50 sThe Page Visibility API exposes `document.visibilityState`, which in current browsers is either `"visible"` (some part of the document is being presented to the user) or `"hidden"` (none of it is). `document.hidden` is the boolean shorthand for `visibilityState === "hidden"`. Whenever that value changes the browser fires a `visibilitychange` event at `document`, so you register `document.addEventListener('visibilitychange', handler)` and read the state inside the handler — the event object itself carries no payload. It flips to hidden when the user switches to another tab, minimises the window, switches apps on mobile, or the screen locks. Use it to pause work nobody can see — animations, polling, media — and, more importantly, as the moment to flush unsaved state, because it is the last transition the browser reliably delivers. Also check the state once at startup: a page can begin life hidden.
go deeper
Be ready to name the pair out loud: the visibilitychange event on document, and document.visibilityState returning "visible" or "hidden". Mention document.hidden as the boolean shorthand and give one use, such as pausing a timer.
Explain that the event carries no payload so the state is read from the document, that a page can start hidden, and that focus and visibility are separate signals with separate events.
Show the operational instinct: the hidden transition is where you stop wasted CPU and battery work and where you persist anything the user would hate to lose, because a backgrounded tab may never run code again.
Frame it as a contract for the whole application: define once what suspends when hidden, what state must be durable at that instant, and how features are reviewed against that policy so each team does not reinvent its own lifecycle handling.
## What the API reports Every document has a *visibility state*, read through `document.visibilityState`. In current browsers it has exactly two values: - `"visible"` — at least part of the document is being presented to the user (the page is in the foreground tab of a non-minimised window). - `"hidden"` — none of the document is being presented: it is a background tab, the window is minimised, the app has been switched away from on mobile, or the device screen is locked. `document.hidden` is a convenience boolean equal to `visibilityState === "hidden"`. An older `"prerender"` value existed in early drafts and was removed, so code that branches on three values is out of date. ## The event When the value changes, the browser fires a `visibilitychange` event at `document`. It is a plain `Event` — there is no `event.visibilityState` property, so read the current value from the document: ```js document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'hidden') { pauseExpensiveWork(); flushUnsavedState(); } else { resumeExpensiveWork(); } }); ``` ## What flips it, and what does not Flips it to hidden: switching to another tab in the same window; minimising the window; on a phone, backgrounding the browser or going to the home screen; the device locking. Navigating away also marks the page hidden before it is torn down. Does **not** flip it: the page merely losing keyboard focus. If the user clicks into a second application window that sits beside the browser, or opens DevTools in a separate window, the page is still on screen and still `"visible"` — only `window`'s `blur` event tells you focus moved. Conflating focus with visibility is the most common mistake in this area. ## Reading the state at startup A page does not always begin visible. A tab opened in the background, a link opened with a middle click, or a page restored on browser startup can run its scripts while `document.visibilityState` is already `"hidden"`. If your logic lives only in the event handler, that page never gets initialised correctly. The safe shape is a single function called both on load and on every change: ```js function applyVisibility() { /* start or stop work */ } applyVisibility(); document.addEventListener('visibilitychange', applyVisibility); ``` ## What it is actually used for Two distinct jobs, and interviewers care about both. **Stopping waste.** Animations, timers, polling and media playback in a tab nobody is looking at burn CPU and battery for no benefit, and on a laptop that is a measurable cost. Hidden is the cue to stop; visible is the cue to resume and, usually, to fetch once so the user sees fresh data immediately. **Persisting state.** The transition to hidden is the last moment a browser can be counted on to run your code. A backgrounded tab may be frozen, discarded, or killed by the operating system with no further notice and no further events. So anything the user would be upset to lose — a half-written form, scroll position, an in-progress selection — should be written to storage when the page goes hidden, not when it unloads. ## Pitfalls worth naming - **The event does not say why.** Hidden covers both "the user glanced at another tab" and "the page is going away forever". Treat every hidden transition as potentially final rather than trying to guess. - **There is no guaranteed matching visible event.** A page that goes hidden may never come back, so do not put required cleanup only in the visible branch. - **Do not do heavy work in the handler.** The browser is often mid-transition; keep the hidden path to a small, cheap, already-prepared write. - **Per-document, not per-element.** Visibility here is about the tab, not about whether an element is scrolled into view — that is a different observer entirely. ## How to say it in an interview "`document.visibilityState` is `visible` or `hidden`, `visibilitychange` fires on `document` when it changes, I read the state in the handler and also once at startup, and I use the hidden transition to pause work and save state." That sentence covers everything a screening question is looking for.
- Does visibilitychange fire when the user locks their phone or minimises the browser window?Yes. Visibility is about whether the document is being presented at all, so locking the screen, minimising the window, switching apps on mobile and switching tabs all move the state to `"hidden"` and fire the event. That is exactly why it is a better save signal than anything tied to navigation, which none of those actions perform.
- Does the visibilitychange event object carry the new state?No. It is a plain `Event` with no state-specific properties, so you read `document.visibilityState` (or `document.hidden`) inside the handler. Code written against an imagined `event.visibilityState` reads `undefined` and silently takes the wrong branch.
- Why should you read the visibility state once when the script first runs rather than only listening for changes?Because a page can start hidden — a background-opened tab, a restored session, a link opened in a new tab behind the current one. No change event has fired yet, so a listener-only design leaves that page running animations and polling it should have suspended. Factor the logic into one function you call at startup and on every change.
saying these in an interview costs you the question
- Says visibilityState is true or false
- Confuses tab visibility with window focus
- Reads event.visibilityState from the event object
- Assumes the page always starts visible
- Expects a matching visible event to always follow