skip to content

Browser Platform & Web APIs

You will learn what the browser itself hands you once your script is running: the DOM and event system, the rendering pipeline, network and storage APIs, workers and service workers, the security model, and navigation. Interviewers lean on this area because framework knowledge evaporates without it — almost every React or Vue answer eventually bottoms out in a platform behavior.

on this pageshow

explore

questions

266 · 9 sections

In the browser DOM, how do you attach a data payload to an event you dispatch yourself with CustomEvent, and how does a listener read that payload back?

level: juniorimportance: must knowfreq 68%
basics
~20 s

Put the payload in the CustomEvent constructor's detail option — new CustomEvent('cart:add', { detail: { id, qty } }) — and dispatch it on a node with dispatchEvent. Listeners read it as event.detail. A plain Event has no detail.

open as a page

In a DOM click listener registered on a <ul> element, what is the difference between event.target and event.currentTarget, and which of the two changes as the event travels?

level: juniorimportance: must knowfreq 76%
basics
~20 s

event.target is the deepest node the event was dispatched on and is identical for every listener in the path. event.currentTarget is the node whose listener is executing right now, so it differs from listener to listener.

open as a page

In the browser DOM, what can element.addEventListener('click', fn) do that assigning element.onclick = fn cannot?

level: juniorimportance: must knowfreq 68%
basics
~10 s

addEventListener registers any number of independent listeners on one element and accepts an options argument — capture, once, passive, signal. element.onclick is a single property slot, so each assignment silently replaces the previous handler.

open as a page

In the browser DOM, what is the difference between document.getElementById('user-menu') and document.querySelector('#user-menu'), and when does the selector form fail on an id that getElementById finds?

level: juniorimportance: must knowfreq 72%
basics
~20 s

Both return the first matching element or null, but getElementById takes a raw id string and exists only on Document, while querySelector parses a CSS selector, so an id such as 3d-view needs CSS.escape or an attribute selector.

open as a page

In the browser DOM, what is the difference between a Node and an Element, and which kinds of node besides elements does a parsed HTML document contain?

level: juniorimportance: must knowfreq 55%
basics
~20 s

Node is the base type for everything in the DOM tree; Element is the subtype for tags. A parsed HTML document also contains Text nodes (including whitespace between tags), Comment nodes, the Document itself, and its DocumentType node.

open as a page

What rules does customElements.define() enforce on the tag name and the class you pass it, and what happens when you break them?

level: juniorimportance: must knowfreq 70%
basics
~10 s

customElements.define(name, class) requires a lowercase name that contains a hyphen and a class that extends HTMLElement. An invalid name throws SyntaxError; registering a name twice, or one class under two names, throws NotSupportedError.

open as a page

A custom element <user-card> needs to receive an array of user objects from the surrounding page. Why can that array not be passed as an HTML attribute, and what is the standard way to hand it over?

level: juniorimportance: must knowfreq 62%
basics
~20 s

HTML attributes can hold only strings, so an array survives as an attribute only if you serialize it to text and parse it back. Pass rich data as a JavaScript property instead — el.users = [...] — which hands over the real object by reference.

open as a page

In the DOM, what does calling element.attachShadow({ mode: 'open' }) do, and what does the resulting shadow boundary actually encapsulate?

level: juniorimportance: must knowfreq 70%
basics
~20 s

attachShadow creates a ShadowRoot: a separate DOM subtree rendered in place of the element's own children. Its boundary scopes selectors both ways, so outer CSS and document.querySelector cannot reach inside, and styles defined inside cannot leak out.

open as a page

A custom element attaches a shadow root and renders <span class="label">Hi</span> inside it. Why does the page's `body { font-family: Georgia }` change that text, while the page's `body .label { color: red }` does nothing?

level: juniorimportance: must knowfreq 55%
basics
~20 s

Inheritance crosses a shadow boundary; selector matching does not. font-family is an inherited property, so its computed value flows from the host down into the shadow tree. A page selector can never match a node inside another tree's shadow root.

open as a page

In HTML, what does the <template> element do with the markup written inside it, and how do you get a copy of that markup onto the page?

level: juniorimportance: must knowfreq 58%
basics
~20 s

The <template> element parses its markup into an inert DocumentFragment exposed as template.content: nothing renders, scripts do not run, images are not fetched. To use it, clone that fragment with cloneNode(true) and insert the copy.

open as a page

Why is requestAnimationFrame preferred over setInterval(draw, 16) for driving a browser animation?

level: juniorimportance: must knowfreq 78%
basics
~20 s

requestAnimationFrame runs a callback once per frame, just before paint and in step with the display's refresh rate. A 16 ms interval drifts against that rhythm, duplicating or skipping frames, and it keeps firing in hidden tabs.

open as a page

In the browser rendering pipeline, how do the CSS declarations `display: none` and `visibility: hidden` differ in whether the element gets a box, whether it occupies space, and what work the browser redoes when you switch each one back on?

level: juniorimportance: must knowfreq 72%
basics
~20 s

display: none keeps the element out of the render tree entirely: no box, no space, nothing painted. visibility: hidden still generates a box that occupies its layout space and only skips painting, so revealing it costs a repaint rather than a relayout.

open as a page

An HTML document has <script src="app.js"></script> in its <head> with no other attributes. What does the HTML parser do when it reaches that tag, and why does moving the same tag to just before </body> change what the user sees?

level: juniorimportance: must knowfreq 80%
basics
~20 s

A classic script with no attributes is parser-blocking: the parser stops at the tag, waits for app.js to download and execute, then resumes. Placed before </body> the whole document is already parsed, so the user sees content first.

open as a page

In a browser page, when does the document's DOMContentLoaded event fire, and how is that different from the window load event?

level: juniorimportance: must knowfreq 75%
basics
~20 s

DOMContentLoaded fires as soon as the HTML has been fully parsed into a DOM and deferred scripts have run. It does not wait for images, iframes or stylesheets. The window load event fires later, once every subresource has finished loading.

open as a page

This loop runs over 500 elements and is dramatically slower than measuring every element first and then writing every new width. What is the browser doing on each iteration, and why does the split version avoid it? for (const box of document.querySelectorAll('.box')) { const w = box.getBoundingClientRect().width; box.style.width = w + 10 + 'px'; }

level: middleimportance: must knowfreq 60%
basics
~20 s

Each write leaves layout invalidated, and the next iteration's getBoundingClientRect forces the browser to recompute it, so 500 elements cost roughly 500 synchronous layouts. Reading all the widths first and writing afterwards collapses that to a single layout.

open as a page

A fetch() call receives an HTTP 500 response from the server, yet the .catch() handler never runs and the .then() branch executes instead. Why does the Fetch API behave this way, and how do you detect the failure?

level: juniorimportance: must knowfreq 85%
basics
~20 s

fetch() resolves for any completed HTTP exchange, including 404 and 500, and rejects only when no response arrives at all — a network or DNS failure, or a blocked request. Detect HTTP failures yourself with response.ok or response.status.

open as a page

A server-sent events stream in the browser delivers messages tagged with an event name such as `priceUpdate`, but the page's `eventSource.onmessage` handler never fires. Why does that happen, and how do you receive those messages?

level: juniorimportance: must knowfreq 60%
basics
~10 s

EventSource dispatches a named message as an event of that name, so onmessage never sees it. Register eventSource.addEventListener('priceUpdate', handler) instead, and expect event.data as a string you parse yourself.

open as a page

In the browser, fetch() has no timeout option. How do you make a fetch request give up after five seconds?

level: juniorimportance: must knowfreq 62%
basics
~10 s

Drive it from an abort signal: fetch(url, { signal: AbortSignal.timeout(5000) }) aborts the request when the timer fires. The manual equivalent is an AbortController plus setTimeout calling controller.abort(), with the timer cleared afterwards.

open as a page

In the browser you write `const ws = new WebSocket(url); ws.send('hello');` on the very next line. What happens, and what does the WebSocket readyState tell you about when send() is legal?

level: juniorimportance: must knowfreq 68%
basics
~10 s

The send() call throws an InvalidStateError, because the socket is still in the CONNECTING state — the constructor only starts the connection. Send once the open event has fired and readyState is WebSocket.OPEN.

open as a page

A page calls fetch('/collect', { method: 'POST', body }) from inside a pagehide handler, and the request frequently never reaches the server. Why does the browser drop it, and what does navigator.sendBeacon do differently?

level: middleimportance: must knowfreq 65%
basics
~20 s

An ordinary fetch is tied to the document that started it, so when the document is destroyed the browser aborts the request still in flight. sendBeacon marks the request as keepalive, handing it to the browser to complete after the page is gone.

open as a page

What does the global structuredClone() function do, and how does the copy it produces differ from JSON.parse(JSON.stringify(value))?

level: juniorimportance: must knowfreq 55%
basics
~20 s

structuredClone() returns a deep copy made by the browser's structured clone algorithm. Unlike a JSON round-trip it preserves Map, Set, Date, RegExp, typed arrays, Blobs and cyclic references, but it throws a DataCloneError on functions and DOM nodes.

open as a page

In the browser, how do localStorage and sessionStorage differ in scope and lifetime, and what do the two have in common?

level: juniorimportance: must knowfreq 85%
basics
~20 s

Both hold string key/value pairs scoped to one origin and share the same API. localStorage persists until something clears it and is visible to every tab on that origin; sessionStorage is confined to a single tab and is discarded when that tab closes.

open as a page

You stored a response with the browser's Cache API and are sure the URL is right, yet `cache.match(request)` resolves with `undefined`. What are the usual causes, and which `match` options address them?

level: middleimportance: must knowfreq 58%
basics
~20 s

Cache API lookups key on the full URL including the query string, honour the stored response's Vary header, and only match GET requests. The ignoreSearch, ignoreVary and ignoreMethod options relax each rule; a wrong cache name is the fourth cause.

open as a page

The browser gives you the Cache API (the global `caches` / `CacheStorage`) alongside the HTTP cache it manages itself. How do the two differ, and what does that difference mean for code that stores responses?

level: middleimportance: must knowfreq 68%
basics
~20 s

The Cache API is a script-owned store of Request/Response pairs that you fill, look up and delete explicitly, with no expiry of its own. The HTTP cache is browser-managed, driven by response headers, and unreachable from JavaScript.

open as a page

In a browser, a page calls worker.postMessage(payload) on a dedicated Web Worker and then immediately mutates payload. Does the worker see the mutation? Explain what the worker actually receives and how it reads it.

level: juniorimportance: must knowfreq 52%
basics
~20 s

No. postMessage sends a copy made at the moment of the call, so later edits to the original never reach the worker. The worker gets a message event asynchronously and reads the copy from event.data; the two threads share no objects.

open as a page

A page calls navigator.serviceWorker.register('/sw.js') on a visitor's first visit, and that worker has a fetch handler. Does the worker intercept the requests of the page load that registered it? Explain what decides whether a document is controlled.

level: juniorimportance: must knowfreq 65%
basics
~20 s

No. A document's controller is chosen when it is navigated to, and on a first visit no worker is active yet, so navigator.serviceWorker.controller is null and every request on that load goes straight to the network.

open as a page

In a browser, what is the difference between calling `worker.postMessage(buffer)` and `worker.postMessage(buffer, [buffer])` for an ArrayBuffer — what does the worker receive in each case, and what is left in the sending context?

level: juniorimportance: must knowfreq 55%
basics
~20 s

Without a transfer list the ArrayBuffer is copied, so both sides hold their own bytes. With the buffer in the transfer list its memory is handed over instead: the worker gets the original bytes and the sender's buffer is detached, byteLength 0.

open as a page

Inside a dedicated Web Worker in the browser, why can you not touch the page's DOM, and which browser APIs are still available on the worker's global scope?

level: juniorimportance: must knowfreq 80%
basics
~20 s

A dedicated Web Worker runs on its own thread with its own global, DedicatedWorkerGlobalScope, so window, document and the DOM are absent — the DOM is not thread-safe. Workers still get fetch, WebSocket, indexedDB, timers and crypto.

open as a page

A dedicated Web Worker replies through a single message event on the page, so every reply lands in the same handler. How would you build a promise-based request/response API on top of postMessage so that several concurrent callers each get their own answer?

level: middleimportance: must knowfreq 46%
basics
~20 s

Attach a unique id to every outgoing message and keep a map from id to that request's promise resolvers. The worker echoes the id back in its reply; the single message handler looks up the id, settles that promise, and deletes the entry.

open as a page

A fetch() call from a page on https://app.example.com to https://api.other.com fails with a console message saying the response was blocked by CORS policy. Who produced that error, and why does adding an Access-Control-Allow-Origin header to the request headers of your fetch call not fix it?

level: juniorimportance: must knowfreq 82%
basics
~20 s

The browser produced the error, not the server and not your code. CORS permission is granted by headers on the server's response, so Access-Control-Allow-Origin can only be set by the API. Sending it as a request header changes nothing.

open as a page

In the browser DOM, which APIs turn a plain string into live markup, and why does assigning "<script>alert(1)</script>" to an element's innerHTML not run the script while "<img src=x onerror=alert(1)>" does?

level: juniorimportance: must knowfreq 72%
basics
~20 s

innerHTML, outerHTML, insertAdjacentHTML and document.write parse a string as HTML, so untrusted data becomes markup. HTML fragment parsing marks script elements inserted that way as already-started, so they never run, but event-handler attributes such as onerror still fire. Use textContent instead.

open as a page

A page is served from https://shop.example.com/cart. In browser terms, what is that page's origin, and which of https://shop.example.com:443/help, http://shop.example.com/cart and https://api.example.com/cart are same-origin with it?

level: juniorimportance: must knowfreq 80%
basics
~20 s

An origin is scheme plus host plus port: here https://shop.example.com on port 443. Only the :443/help URL is same-origin; switching the scheme to http or the host to api.example.com produces a different origin. Path is never part of it.

open as a page

A page calls navigator.clipboard.writeText() and registers a service worker. Both work when the site is served from http://localhost:3000, but on http://staging.internal the browser reports navigator.clipboard and navigator.serviceWorker as undefined. What rule is the browser applying, and which origins satisfy it?

level: juniorimportance: must knowfreq 68%
basics
~20 s

Powerful browser APIs are gated on secure contexts: HTTPS and WSS origins plus loopback hosts (localhost, 127.0.0.1, ::1). Plain HTTP on staging is not one, so those APIs are absent entirely; window.isSecureContext reports the verdict.

open as a page

A cross-origin fetch to your API worked until you added credentials: 'include' so the session cookie would be sent; now the browser rejects the response and complains about the wildcard in Access-Control-Allow-Origin. What must the API return instead, and why does '*' stop being acceptable once credentials are involved?

level: middleimportance: must knowfreq 68%
basics
~20 s

A credentialed cross-origin response must name the exact calling origin in Access-Control-Allow-Origin and also send Access-Control-Allow-Credentials: true. The wildcard is banned there because '*' means any site may read the response — which, with the user's cookies attached, would expose their logged-in data to every origin.

open as a page

A user presses the browser's Back button and the previous page appears instantly — scroll position and typed form values intact, and the Network panel shows no request for the document. What is the browser's back/forward cache doing here, and does the page's JavaScript start over?

level: juniorimportance: must knowfreq 50%
basics
~20 s

The back/forward cache keeps the whole page — DOM plus the JavaScript heap — frozen in memory when you navigate away, then restores that snapshot on Back. Scripts do not re-run, and DOMContentLoaded and load do not fire again.

open as a page

In a browser page, how do you detect that the tab has been hidden, and what values can `document.visibilityState` take?

level: juniorimportance: must knowfreq 70%
basics
~20 s

Listen 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.

open as a page

Your page embeds `<iframe id="widget" src="https://widget.example.com">` and you call `document.getElementById('widget').postMessage(data, 'https://widget.example.com')`, which throws a TypeError. What is the correct call, and what does each argument mean?

level: juniorimportance: must knowfreq 55%
basics
~20 s

An iframe element has no postMessage method; its window does. Call iframe.contentWindow.postMessage(data, 'https://widget.example.com') — the first argument is the copied payload, the second is the origin the frame must currently have or the browser silently drops the message.

open as a page

In browser JavaScript, what is the second argument to the URL constructor for, and why does new URL('/checkout') throw while new URL('/checkout', 'https://shop.example.com/cart/items') does not?

level: juniorimportance: must knowfreq 70%
basics
~20 s

The second argument is a base URL that a relative reference is resolved against. A relative string on its own has no scheme or host, so new URL('/checkout') throws a TypeError; with the base it resolves to https://shop.example.com/checkout.

open as a page

In a browser page you call observer.observe(el) on an element that sits far below the fold, and your IntersectionObserver callback runs once right away even though the user has not scrolled. Why does that happen, and what should the callback do with that first entry?

level: juniorimportance: must knowfreq 66%
basics
~20 s

observe() queues an initial observation, so the callback always fires once with a baseline entry describing the target's current position. For an off-screen element that entry has isIntersecting false, so the callback must branch on isIntersecting rather than assume it was called because the element became visible.

open as a page

A sidebar can be dragged wider by the user and also shrinks when a neighbouring panel opens, while the browser window stays the same size. Why is a window 'resize' event listener not enough to react to that element's own size changing, and what does ResizeObserver give you instead?

level: juniorimportance: must knowfreq 72%
basics
~20 s

The window resize event fires only when the viewport changes size. An element's own box can change from dragging, sibling layout, content, or CSS while the viewport never moves. ResizeObserver watches a chosen element and reports its new dimensions.

open as a page

An IntersectionObserver is constructed with { threshold: [0, 0.5, 1] }. When exactly does its callback run as the user scrolls, and what does entry.intersectionRatio mean at that moment?

level: middleimportance: must knowfreq 58%
basics
~20 s

The callback runs whenever the visible fraction of the target crosses one of the listed thresholds, in either direction — not continuously while scrolling. intersectionRatio is that fraction: the intersecting area divided by the target's own bounding-box area, between 0 and 1.

open as a page

You call `observer.observe(el, { childList: true })` on a MutationObserver. Which changes under `el` will fire the callback, which will be ignored, and what happens if you pass `{}` instead?

level: middleimportance: must knowfreq 62%
basics
~20 s

childList: true reports only direct children of el being added or removed. Attribute edits, in-place text changes, and anything deeper in the tree are ignored unless you also pass attributes, characterData or subtree. Calling observe with {} throws a TypeError.

open as a page

When you call observe(el) on a ResizeObserver, when does the callback first run, and what do unobserve() and disconnect() each do when the code that created the observer is torn down?

level: middleimportance: must knowfreq 58%
basics
~20 s

Observing delivers an initial callback at the next rendering opportunity with the element's current size, even though nothing changed. unobserve(target) stops watching one element; disconnect() stops watching all of them and is what teardown should call, since the observer stays live otherwise.

open as a page