Web Platform interview questions – HTML, CSS, JavaScript & browser APIs
Reviewed by Mark Dickie · Last updated
The Web Platform is the collection of open standards — HTML, CSS, JavaScript, and browser-exposed APIs — that lets developers build and run applications inside a web browser without proprietary plugins. For interviews, you need a working mental model of how the browser parses and renders a page, how JavaScript executes (the event loop, the call stack, microtasks vs. macrotasks), and where CSS specificity and the box model trip candidates up. Security primitives like the same-origin policy and HTTPS also appear regularly, even at mid-level interviews.
What the Web Platform covers
The platform spans several overlapping layers. Knowing which layer a feature lives in helps you answer "why does X work that way?" questions on the spot.
| Layer | Core technologies | Common interview topics | |---|---|---| | Structure | HTML5 | Semantic elements, accessibility, form validation | | Presentation | CSS3 / CSS4 modules | Specificity, cascade, Flexbox, Grid, custom properties | | Behaviour | JavaScript (ES2024) | Event loop, closures, prototypes, async/await | | Browser APIs | Fetch, Web Storage, Service Workers, WebSockets | Caching, offline, real-time updates | | Security | CORS, CSP, HTTPS, SOP | Attack vectors, header config, authentication flows |
How to prioritise your prep
There is no single "Web Platform spec" to read cover to cover, so you need a structured path. Work through this order rather than jumping around:
- HTML semantics and accessibility — the foundation interviewers assume you have. Know when to use
<article>vs<section>, and what ARIA roles do. - The CSS cascade and layout models — specificity rules, how inheritance works, Flexbox vs Grid trade-offs, and the stacking context.
- JavaScript core mechanics — the event loop, how the call stack and task queues interact, closures,
thisbinding, and prototypal inheritance. - Async patterns — callbacks, Promises,
async/await, and where each breaks down under error handling. - Browser rendering — the critical rendering path, reflow vs repaint, and what
will-changeactually does. - Security model — same-origin policy, how CORS headers work, and what a Content Security Policy prevents.
- Modern browser APIs — Service Workers and the Cache API,
IntersectionObserver,fetchvsXMLHttpRequest, and Web Storage vs cookies.
A note on browser support
Always pair a feature answer with a browser-support caveat. Interviewers at product companies care whether you reach for something that needs a polyfill or a feature flag. Checking MDN's compatibility tables before an interview is a fast way to add that layer to your answers.
At a glance
| Questions | 15 |
|---|---|
| Difficulty | 1–4 of 5 |
| Formats | Multiple choice, Multiple answer, True / false, Code output, Short answer |
What you'll review
- dom traversal
- event delegation
- fetch api
- storage apis
- web workers
- cors
- lazy loading
- xss
- aria basics
- csrf
- event bubbling
- debounce throttle
- critical rendering path
- script loading
- core web vitals
Practice questions
Web Platform/dom/dom-traversal
Given several <button class="btn"> elements, what does document.querySelector(".btn") return?
Options
- The first matching element, or
nullif none match - A live
HTMLCollectionof all matching elements - A static
NodeListof all matching elements - An array of all matching elements
Show answer
It returns the first element matching the selector in document order, or null if nothing matches. querySelector never returns a collection, no matter how many elements match — for all matches you need querySelectorAll, which gives back a static NodeList.
querySelector returns only the first element matching the CSS selector (in document order), or null if there are no matches. To get all matches use querySelectorAll, which returns a static NodeList.
Web Platform/dom/event-delegation
You attach one click listener to a <ul> to handle clicks on its <li> children (event delegation). Inside the handler, which property identifies the actual <li> that was clicked?
Options
event.currentTargetevent.targetthisevent.srcElement.parentNode
Show answer
event.target identifies the actual <li> that was clicked, because it points to the element where the event originated. By contrast event.currentTarget (and this) refers to the <ul> the listener is bound to. Delegation reads event.target and usually walks up with closest() to find the intended item.
event.target is the element where the event originated (the clicked <li> or something inside it). event.currentTarget (and this) is the element the listener is bound to — the <ul>. Delegation typically reads event.target and walks up with closest() to find the intended item.
Web Platform/browser-apis/fetch-api
A fetch() request gets back an HTTP 404 response. What happens to the returned promise?
Options
- It rejects, so the
.catchblock runs - It resolves with a
Responsewhoseokisfalseandstatusis404 - It resolves with
null - It throws synchronously before returning a promise
Show answer
The promise resolves with a Response whose ok is false and status is 404. fetch only rejects on network-level failures like DNS errors, being offline, an abort, or a CORS block — a completed HTTP response is a success as far as the promise is concerned. You must inspect response.ok yourself and throw to treat error statuses as failures.
fetch only rejects on network failures (DNS error, offline, aborted, CORS block). Any completed HTTP response — including 404 or 500 — resolves the promise. You must check response.ok (or response.status) yourself and throw to treat error statuses as failures.
Web Platform/browser-apis/storage-apis
After localStorage.setItem("n", 42), what does typeof localStorage.getItem("n") evaluate to?
Options
- "number"
- "string"
- "object"
- "undefined"
Show answer
It evaluates to string. Web Storage stores only strings, so the number 42 is coerced to '42' on write and getItem always returns a string (or null for a missing key). To round-trip structured data, serialize with JSON.stringify on write and JSON.parse on read.
Web Storage stores only strings. The number 42 is coerced to the string "42" on write, and getItem returns a string (or null for a missing key). Serialize structured data with JSON.stringify on write and JSON.parse on read.
Web Platform/browser-apis/web-workers
Which statement about a dedicated Web Worker is true?
Options
- It runs on a separate thread and cannot access the DOM or
window - It shares the main thread but gets a higher priority
- It can update the DOM directly as long as it uses
requestAnimationFrame - It blocks the main thread while it runs
Show answer
A dedicated Web Worker runs on a separate thread and cannot access the DOM or window. Because it runs in the background, long computations there don't block the UI. It communicates with the page only through postMessage and the message event, with data copied via structured clone or transferred.
A Web Worker runs script on a background thread, so long computations don't block the UI. It has no access to the DOM, window, or document; it communicates with the page only via postMessage and the message event, and data is copied (structured clone) or transferred.
Web Platform/web-security/cors
Before sending a cross-origin PUT request with a Content-Type: application/json header, the browser first sends which request?
Options
- An
OPTIONSpreflight request - A
HEADrequest - A
GETrequest to the same URL - No extra request — it sends the
PUTdirectly
Show answer
The browser first sends an automatic OPTIONS preflight request. A cross-origin PUT carrying Content-Type: application/json is not a CORS simple request, so the browser checks permission before sending it. The server must answer the preflight with Access-Control-Allow-Origin, -Allow-Methods, and -Allow-Headers permitting the call before the real PUT goes out.
A PUT with Content-Type: application/json is not a CORS "simple request", so the browser sends an automatic OPTIONS preflight first. The server must answer with Access-Control-Allow-Origin, -Allow-Methods, and -Allow-Headers permitting the call before the browser will send the actual PUT.
Web Platform/web-performance/lazy-loading
Which attribute tells the browser to defer loading an off-screen <img> until it is about to scroll into view, with no JavaScript?
Options
loading="lazy"defer="true"asyncpreload="none"
Show answer
The loading="lazy" attribute does this, with no JavaScript. Set on an <img> (or <iframe>), it lets the browser defer fetching the resource until it nears the viewport, saving bandwidth and speeding up the initial load. The async and defer attributes apply to <script> elements, not images.
The native loading="lazy" attribute on <img> (and <iframe>) lets the browser postpone fetching the resource until it nears the viewport, saving bandwidth and speeding up initial load. async/defer apply to <script>, not images.
Web Platform/web-security/xss
Which of these meaningfully reduce the risk of cross-site scripting (XSS)?
Options
- Rendering user input as text (e.g.
textContent) instead ofinnerHTML - Context-aware output encoding / escaping of untrusted data
- A Content-Security-Policy that restricts which scripts may run
- Storing the session token in
localStorageinstead of a cookie
Show answer
Three of these help: rendering user input as text via textContent, context-aware output encoding, and a Content-Security-Policy that restricts which scripts run all cut off injection or execution. Storing the session token in localStorage does the opposite — it makes the token readable by any injected script, worsening XSS impact rather than reducing it.
XSS is injecting attacker-controlled script into a page. Treating input as text (textContent), escaping output for its context, and a strict CSP all cut off injection or execution. Moving the token to localStorage does the opposite — it makes the token readable by any injected script, so it worsens XSS impact.
Web Platform/accessibility/aria-basics
Which statements about ARIA are correct?
Options
- Prefer a native element (e.g.
<button>) over a<div role="button">when one exists aria-labelprovides an accessible name when there is no visible text label- Adding
roleand ARIA attributes automatically makes a custom widget keyboard-operable aria-hidden="true"removes an element from the accessibility tree
Show answer
Three statements are correct: prefer a native element like <button> over <div role="button"> when one exists, aria-label provides an accessible name when there's no visible text, and aria-hidden="true" removes an element from the accessibility tree. The false one is that ARIA makes a widget keyboard-operable — ARIA only changes semantics, so keyboard handling must still be coded in JavaScript.
The first rule of ARIA is to use native semantics where possible; aria-label supplies an accessible name; aria-hidden="true" hides a node from assistive tech. ARIA only changes semantics — it adds no behavior, so keyboard handling (focus, Enter/Space, arrow keys) must still be implemented in JavaScript.
Web Platform/web-security/csrf
A CSRF attack works by tricking the victim's browser into sending a state-changing request to a site where the victim is already authenticated, relying on cookies being attached automatically.
Show answer
True. CSRF abuses ambient authority: the browser automatically attaches a site's cookies to any request to that origin, so a forged request from a malicious page can act as the logged-in user. Defenses include anti-CSRF tokens and SameSite cookies, which break that automatic credential attachment for cross-site requests.
Cross-site request forgery abuses ambient authority: the browser automatically attaches the target site's cookies to any request to that origin, so a forged request from a malicious page can act as the logged-in user. Defenses include anti-CSRF tokens and SameSite cookies, which break this automatic credential attachment for cross-site requests.
Web Platform/dom/event-bubbling
#child is inside #parent. The user clicks #child. What is logged, in order?
const parent = document.getElementById('parent');
const child = document.getElementById('child');
parent.addEventListener('click', () => console.log('parent capture'), true);
parent.addEventListener('click', () => console.log('parent bubble'));
child.addEventListener('click', () => console.log('child'));
// user clicks #childShow answer
parent capture
child
parent bubble
An event runs in three phases: capture (root → target), target, then bubble (target → root). The parent's capturing listener (third arg true) fires first on the way down, then the listener on the clicked target #child, then the parent's default (bubbling) listener on the way back up.
Web Platform/web-performance/debounce-throttle
A trailing-edge debounce delays the call until 100ms after the last invocation. f is called three times in quick succession (well under 100ms apart). How many times does g run, and with what argument?
function debounce(fn, wait) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), wait);
};
}
const g = debounce((x) => console.log(x), 100);
g('a');
g('b');
g('c');Show answer
c
Each call clears the previous pending timer and schedules a new one, so the first two never fire. Only the timer from the last call (g('c')) survives the 100ms of quiet, so fn runs exactly once with the most recent argument, logging c.
Web Platform/web-performance/critical-rendering-path
Describe the critical rendering path: the main steps the browser takes to turn HTML and CSS into pixels on screen.
Show answer
The browser parses HTML into the DOM tree and CSS into the CSSOM, then combines them into the render tree (only visible nodes with their computed styles). It runs layout (reflow) to compute each node's geometry, then paints those boxes into layers, and finally composites the layers to the screen. Render-blocking CSS and synchronous scripts delay this path, so minimizing and deferring them speeds up first paint.
The pipeline is DOM + CSSOM → render tree → layout → paint → composite. Knowing it explains why blocking CSS/JS hurts first paint and why operations that force synchronous layout (layout thrashing) are expensive.
Web Platform/web-performance/script-loading
A page's <head> contains <script async src="a.js"> and <script defer src="d.js">. Both download in parallel without blocking the parser. When does each one execute?
Options
- There is no practical difference — both wait until HTML parsing finishes;
asyncis simply the older spelling ofdefer deferexecutes the moment its download finishes;asyncwaits until HTML parsing is completeasyncexecutes as soon as its download finishes, pausing the parser while it runs;deferwaits until HTML parsing is complete- Both pause the parser at the tag and execute immediately, blocking parsing while they download and run
Show answer
An async script executes as soon as its download finishes, pausing the parser while it runs, whereas a defer script waits until HTML parsing is complete. Deferred scripts then run in document order before DOMContentLoaded, which makes defer the safe default for scripts that touch the DOM or depend on each other; async suits independent scripts like analytics.
Both attributes make the download happen in parallel with parsing — the difference is execution timing. An async script runs as soon as it has arrived, whenever that is: script execution always happens on the main thread, so the parser pauses while it runs, and multiple async scripts execute in whatever order their downloads finish, not document order. A defer script waits until the parser has finished the whole document, then deferred scripts run in document order (even if a later one downloaded first), and all of them run before DOMContentLoaded fires. That makes defer the safe default for scripts that touch the DOM or depend on each other; async suits independent scripts like analytics.
Web Platform/web-performance/core-web-vitals
Which of these contribute to a page's CLS (Cumulative Layout Shift) score?
Options
- An
<img>with nowidth/height(or CSSaspect-ratio) pushing the content below it down when it loads - A slow server response that delays the page's first paint by two seconds
- A cookie banner injected above existing content a few seconds after load
- A web-font swap whose different metrics reflow the surrounding paragraphs
- An accordion expanding and pushing content down 200ms after the user clicks it
Show answer
Three contribute to CLS: an <img> with no reserved dimensions pushing content down when it loads, a cookie banner injected above existing content after load, and a web-font swap that reflows surrounding text. A slow server response delays rendering but moves nothing already painted, so it hurts TTFB and LCP, not CLS. The accordion shift is excluded because it follows the user's click within 500ms.
CLS counts unexpected movement of visible elements: an image loading into unreserved space, a banner inserted above existing content, and a font swap that changes text metrics all move rendered content and produce layout-shift entries (CLS sums these over short session windows and reports the worst window). A slow server response delays rendering but moves nothing that was already painted — that hurts TTFB and LCP, not CLS. And a shift within 500ms of a discrete user interaction (like the accordion click) carries the hadRecentInput flag and is excluded, because movement the user just asked for is expected, not jank.
Sources
The official documentation these questions are checked against:
Related interview questions
Job market
See web-platform salaries and hiring demand from live job postings.
Practice this for real
CodePrep turns your target job description into an adaptive quiz from a bank of tagged questions, scores your answers, and resurfaces the topics you miss.