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 pageshowhide
explore
- DOM and Events36 questions
- The DOM Tree and Node Types5 questions
- Selection and Traversal APIs5 questions
- Creating and Moving Nodes6 questions
- Event Flow and Delegation5 questions
- addEventListener Options and Teardown5 questions
- Custom Events and EventTarget5 questions
- Pointer, Mouse, Keyboard, and Touch Events5 questions
- Web Components27 questions
- Custom Elements and Lifecycle6 questions
- Shadow DOM and Encapsulation5 questions
- Templates and Slots5 questions
- Styling Across the Shadow Boundary6 questions
- Framework Interop and Declarative Shadow DOM5 questions
- Rendering Pipeline30 questions
- Parsing, DOM, and CSSOM Construction5 questions
- Script Loading and Parser Blocking5 questions
- Render Tree, Layout, and Reflow5 questions
- Paint and Compositing Layers5 questions
- requestAnimationFrame and Frame Scheduling6 questions
- Forced Synchronous Layout4 questions
- Network APIs29 questions
- The Fetch API in Depth5 questions
- Streaming Bodies and AbortController4 questions
- XMLHttpRequest Essentials5 questions
- Consuming Server-Sent Events5 questions
- Using the Browser WebSocket API5 questions
- sendBeacon and keepalive Requests5 questions
- Client Storage and Persistence30 questions
- Cookies from JavaScript5 questions
- The IndexedDB Model5 questions
- The Cache API5 questions
- Quotas, Eviction, and Persistence5 questions
- Structured Clone Semantics5 questions
- Workers, Service Workers, and Offline33 questions
- Dedicated Web Workers5 questions
- Worker Message Passing5 questions
- Service Worker Lifecycle6 questions
- Service Worker Caching Strategies6 questions
- Browser Security Model29 questions
- Origins and the Same-Origin Policy5 questions
- CORS from the Frontend5 questions
- Deploying CSP in Practice5 questions
- DOM XSS Sinks and Trusted Types4 questions
- Sandboxing, COOP/COEP, and Site Isolation5 questions
- Permissions and Secure Contexts5 questions
- Navigation and Page Lifecycle31 questions
- Observers and Scheduling21 questions
- IntersectionObserver5 questions
- ResizeObserver5 questions
- MutationObserver6 questions
- requestIdleCallback and Task Scheduling5 questions
questions
266 · 9 sectionsIn 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?
basics
~20 sPut 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.
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?
basics
~20 sevent.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.
In the browser DOM, what can element.addEventListener('click', fn) do that assigning element.onclick = fn cannot?
basics
~10 saddEventListener 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.
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?
basics
~20 sBoth 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.
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?
basics
~20 sNode 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.
What rules does customElements.define() enforce on the tag name and the class you pass it, and what happens when you break them?
basics
~10 scustomElements.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.
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?
basics
~20 sHTML 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.
In the DOM, what does calling element.attachShadow({ mode: 'open' }) do, and what does the resulting shadow boundary actually encapsulate?
basics
~20 sattachShadow 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.
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?
basics
~20 sInheritance 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.
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?
basics
~20 sThe <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.
Why is requestAnimationFrame preferred over setInterval(draw, 16) for driving a browser animation?
basics
~20 srequestAnimationFrame 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.
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?
basics
~20 sdisplay: 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.
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?
basics
~20 sA 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.
In a browser page, when does the document's DOMContentLoaded event fire, and how is that different from the window load event?
basics
~20 sDOMContentLoaded 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.
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'; }
basics
~20 sEach 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.
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?
basics
~20 sfetch() 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.
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?
basics
~10 sEventSource 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.
In the browser, fetch() has no timeout option. How do you make a fetch request give up after five seconds?
basics
~10 sDrive 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.
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?
basics
~10 sThe 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.
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?
basics
~20 sAn 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.
In a browser, what exactly does reading `document.cookie` give you, and how would you extract the value of a single cookie named `theme` from it?
basics
~20 sReading document.cookie returns one flat string of every cookie the current page may read, as name=value pairs joined by "; ". Attributes and HttpOnly cookies are absent, so extracting one value means splitting and decoding the string yourself.
What does the global structuredClone() function do, and how does the copy it produces differ from JSON.parse(JSON.stringify(value))?
basics
~20 sstructuredClone() 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.
In the browser, how do localStorage and sessionStorage differ in scope and lifetime, and what do the two have in common?
basics
~20 sBoth 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.
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?
basics
~20 sCache 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.
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?
basics
~20 sThe 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.
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.
basics
~20 sNo. 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.
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.
basics
~20 sNo. 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.
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?
basics
~20 sWithout 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.
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?
basics
~20 sA 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.
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?
basics
~20 sAttach 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.
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?
basics
~20 sThe 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.
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?
basics
~20 sinnerHTML, 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.
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?
basics
~20 sAn 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.
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?
basics
~20 sPowerful 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.
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?
basics
~20 sA 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.
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?
basics
~20 sobserve() 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.
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?
basics
~20 sThe 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.
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?
basics
~20 sThe 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.
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?
basics
~20 schildList: 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.
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?
basics
~20 sObserving 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.