Interview prep ยท questions
Browser and web platform interview questions
28 questions, from core to senior. Say your answer out loud first, then open the model answer and check it against the points a strong answer covers.
How does the browser turn HTML and CSS into pixels on the screen, and what can block rendering?
Show the model answer
The browser parses HTML into the DOM and CSS into the CSSOM, combines them into a render tree, and then runs layout, paint and composite. The render tree has only the nodes that are rendered, so an element with display none is not in it. Layout calculates the size and position of every box. Paint draws the pixels, usually into several layers. Compositing puts the layers together on the screen, often on the GPU. Two things block this path. CSS blocks rendering: the browser does not paint until the stylesheets in the head are loaded. A plain script blocks the parser: HTML parsing stops while the script downloads and runs. The script also waits for stylesheets that come before it. So in practice I keep the CSS in the head small, and I load scripts with defer or as modules.
A strong answer covers
- Names the steps in order: DOM, CSSOM, render tree, layout, paint, composite
- Says the render tree leaves out nodes with display: none
- Says CSS blocks rendering and a plain script blocks the HTML parser
- Gives a fix: defer or module scripts, and less CSS in the head
Likely follow-up
Why does the browser wait for CSS before it paints anything?
What is the difference between reflow and repaint, and why are transform and opacity animations cheap?
Show the model answer
Reflow, also called layout, is when the browser calculates the size and position of elements again. Repaint is when it draws pixels again without a change in geometry. A change to width, height, margin or font size, or adding an element, triggers layout, then paint, then composite. A change to color or background needs only paint and composite. A change to transform or opacity can skip both layout and paint. The compositor only moves or fades a layer that is already painted, often on the GPU and off the main thread. That is why I animate with these two properties. Layout thrashing is when code writes a style and then reads a layout value such as offsetHeight, many times in a loop. Each read forces the browser to run layout immediately. The fix is to do all the reads first and then all the writes.
A strong answer covers
- Separates layout (geometry), paint (pixels) and composite (layers)
- Gives an example property for each: width, color, transform
- Explains layout thrashing: a write and then a read, repeated in a loop
- Gives the fix: all reads first, then all writes
Likely follow-up
What does will-change do, and what does it cost if you put it on many elements?
How do these four script tags differ in how they load and run?
<script src="a.js"></script>
<script async src="b.js"></script>
<script defer src="c.js"></script>
<script type="module" src="d.js"></script>
Show the model answer
They differ in two things: whether they block the HTML parser, and when they run. A plain script stops the parser. The browser downloads it, runs it, and only then continues with the HTML. An async script downloads in parallel and runs as soon as it arrives, so the order is not guaranteed. It fits independent scripts such as analytics. A defer script also downloads in parallel, but it runs after the document is parsed, in document order, before DOMContentLoaded. A module script is deferred by default, and it can use import. For application code I use defer or modules. Preload and prefetch are different: they are resource hints in a link tag, and they only fetch. Preload tells the browser to fetch early something this page certainly needs, such as a font. Prefetch fetches, at low priority, something the next page will probably need.
A strong answer covers
- Says a plain script blocks the parser while it downloads and runs
- Says async runs when it arrives, in no fixed order
- Says defer runs after parsing, in document order
- Says a module script is deferred by default
- Separates preload (this page, early) from prefetch (a likely next page, low priority)
Likely follow-up
When would you still choose async over defer?
Compare cookies, localStorage, sessionStorage and IndexedDB. What do you use each one for?
Show the model answer
They differ in size, in lifetime, and in whether the server sees them. A cookie is small, about 4 kilobytes, and the browser sends it with every matching request, so it is the right place for a session ID. It lives until its expiry date, or until the browser session ends if it has none. JavaScript can read cookies, except the ones marked HttpOnly. localStorage holds about 5 megabytes for each origin, stays until someone clears it, and is never sent to the server. sessionStorage has the same API, but it belongs to one tab and is gone when the tab closes. Both are synchronous and store only strings. IndexedDB is an asynchronous database for larger structured data, for example offline data. Any script on the page can read these three, so I keep tokens out of them and use them for preferences and cached data.
A strong answer covers
- Says cookies travel with every matching request and the others do not
- Gives rough sizes: about 4 KB for a cookie, about 5 MB for localStorage
- Says sessionStorage belongs to one tab and ends with it
- Says HttpOnly hides a cookie from JavaScript
- Says IndexedDB is asynchronous and made for larger structured data
Likely follow-up
What do the Secure, HttpOnly and SameSite cookie attributes do?
What happens when you type a URL into the browser and press Enter?
Show the model answer
The browser finds the server, opens a secure connection, requests the HTML, and then builds and renders the page. First it resolves the domain name to an IP address with DNS, unless the answer is cached. Then it opens a TCP connection and does the TLS handshake, or it uses QUIC for HTTP/3. It sends a GET request with headers and cookies. The server, or a CDN in front of it, returns a status code, headers and the HTML. The browser parses the HTML while it arrives, builds the DOM, and finds the CSS, scripts, images and fonts, which it also requests. With the CSSOM ready it runs layout, paint and composite, and the scripts make the page interactive. In real work the slow parts are usually the server response and the render-blocking resources, so I look there first.
A strong answer covers
- Goes in order: DNS, TCP and TLS, request, response, parsing, rendering
- Mentions that caches (DNS, browser, CDN) can skip steps
- Says the browser finds and fetches CSS, scripts and images while it parses
- Stays short and offers to go deeper into one layer
Likely follow-up
Which of these steps would you measure first if the page feels slow?
Explain the main HTTP methods and the status codes you use most.
Show the model answer
GET reads, POST creates or starts an action, PUT replaces a resource, PATCH changes part of it, and DELETE removes it. GET is safe, which means it does not change state on the server. GET, PUT and DELETE are idempotent: a repeated request leaves the server in the same state as one request. POST is not idempotent, and PATCH is not guaranteed to be. For status codes, 2xx is success: 200 OK, 201 Created, 204 No Content. 3xx is redirection: 301 permanent, 302 temporary, 304 Not Modified. 4xx is a client error: 400 bad request, 401 not authenticated, 403 authenticated but not allowed, 404 not found, 409 conflict, 422 failed validation, 429 too many requests. 5xx is a server error: 500 for a bug, 503 when the service is unavailable for a while. Clients and proxies rely on these meanings, for example to decide what is safe to retry.
A strong answer covers
- Says GET is safe; GET, PUT and DELETE are idempotent; POST is not
- Names the families: 2xx success, 3xx redirect, 4xx client error, 5xx server error
- Separates 401 (not authenticated) from 403 (authenticated, not allowed)
- Uses 201 for a create, 204 for success with no body, 304 for an unchanged resource
Likely follow-up
PATCH is not always idempotent. Can you give an example?
How does HTTP caching work, and how do you set it up for a single-page application?
Show the model answer
The server tells the browser and the CDN how long they may reuse a response, mostly with the Cache-Control header. max-age is the lifetime in seconds. no-cache means: store it, but check with the server before each use. no-store means: do not store it at all. To check, the browser sends the saved ETag in an If-None-Match header. If nothing changed, the server answers 304 Not Modified with no body. For a single-page application I use two rules. JavaScript and CSS files have a content hash in the file name, so I cache them for a year and mark them immutable. A new build has new file names. The HTML file gets no-cache, so users always receive the newest file names. On a CDN, stale-while-revalidate lets the cache serve the old copy immediately and refresh it in the background.
A strong answer covers
- Separates no-cache (store, then revalidate) from no-store (never store)
- Explains ETag, If-None-Match and the 304 answer
- Says hashed file names allow a long max-age with immutable
- Says the HTML must be revalidated so that users get a new build
Likely follow-up
A user still sees the old version after a deploy. How do you find out why?
What is CORS, and what is this exchange for?
OPTIONS /orders/42 HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: content-type
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, PUT, DELETE
Access-Control-Allow-Headers: content-type
Access-Control-Max-Age: 600
Show the model answer
CORS is a way for a server to tell the browser that pages from another origin may read its responses. By default the same-origin policy stops a script from reading a response from a different origin. An origin is the scheme, host and port together. With CORS the browser sends an Origin header, and the server answers with Access-Control-Allow-Origin. This exchange is a preflight. The browser sends it before a request that is not simple: for example one with a JSON content type, a custom header, or a method like PUT or DELETE. It is an OPTIONS request that asks the server what is allowed. To send cookies, the request must include credentials, and the server must answer with Access-Control-Allow-Credentials true and the exact origin, not a wildcard. The browser enforces CORS, so a CORS error is fixed on the server, which must send the right headers.
A strong answer covers
- Defines an origin as scheme, host and port
- Names the preflight OPTIONS request and what triggers it
- Says the browser enforces it and the server fixes it
- Says requests with credentials need an exact origin, not a wildcard
- Knows CORS controls reading responses and is not a defense against CSRF
Likely follow-up
Why does the same request work from curl or Postman but fail in the browser?
What is cross-site scripting, and how do you protect a React application against it?
Show the model answer
Cross-site scripting, or XSS, is when an attacker gets their JavaScript to run on your page, with the rights of the logged-in user. Stored XSS is saved on the server, for example in a comment, and runs for every visitor. Reflected XSS comes from the request, such as a search parameter that the server puts back into the page. DOM-based XSS happens only in the browser, when code writes untrusted data into the page with something like innerHTML. React helps, because it escapes every string that I render in JSX. The risks that remain are dangerouslySetInnerHTML, URLs that come from users, and third-party scripts. When I must render HTML from users, I sanitize it first with a library such as DOMPurify. A Content Security Policy is a second layer: it tells the browser which scripts it may run, so most injected scripts are blocked.
A strong answer covers
- Names the three kinds: stored, reflected and DOM-based
- Says React escapes text in JSX by default
- Names dangerouslySetInnerHTML as the main way the risk comes back
- Gives two defenses: sanitizing HTML and a Content Security Policy
Likely follow-up
What can an attacker do once their script runs on your page?
A user who is logged in to bank.example opens this page on another site. What happens, and how do you prevent it?
<form action="https://bank.example/transfer" method="POST">
<input type="hidden" name="to" value="attacker" />
<input type="hidden" name="amount" value="1000" />
</form>
<script>
document.forms[0].submit();
</script>
Show the model answer
This is cross-site request forgery, or CSRF: the page makes the user's browser send a request that the user did not intend. The script submits the hidden form to the bank. Browsers attach cookies automatically, so without protection the session cookie goes with the request and the transfer looks real. The attacker cannot read the response, but the action still happens. The first defense is the SameSite cookie attribute. With Lax, the cookie is not sent on a cross-site POST, and with Strict it is not sent on any cross-site request. I set it explicitly, because browser defaults differ. The classic defense is a CSRF token: a random value that the server gives to the real page and checks on every request that changes state. A token that my JavaScript sends in an Authorization header is not affected, because the browser never adds it automatically.
A strong answer covers
- Names the attack and says the cause: cookies are sent automatically
- Names SameSite (Lax or Strict) as the first defense
- Explains a CSRF token: a secret value checked on every state-changing request
- Says a token sent in a header by JavaScript is not affected
- Says a GET request must never change state
Likely follow-up
With a SameSite=Lax session cookie, do you still need a CSRF token?
Where do you keep an authentication token in the browser, and why?
Show the model answer
My default is a cookie with HttpOnly, Secure and SameSite, because JavaScript cannot read it. With a server session, the cookie holds only a random ID and the server keeps the data, so the server can end the session. A JWT is a signed token with the user's claims inside. The server verifies it without a lookup, but it is hard to cancel before it expires. So the access token is short-lived, for example 15 minutes, and a long-lived refresh token gets a new one. In localStorage, any XSS bug lets an attacker read the token and send it away. An HttpOnly cookie is safe from that, but the browser sends it automatically, so it needs CSRF protection. XSS is serious in both cases, because the script can still send requests as the user. On logout I clear the cookie and revoke the refresh token on the server.
A strong answer covers
- Compares the risks: localStorage is open to XSS, a cookie needs CSRF protection
- Names the cookie flags: HttpOnly, Secure, SameSite
- Says a JWT is hard to cancel, so it is short-lived and paired with a refresh token
- Says logout must also end the session or refresh token on the server
Likely follow-up
Several requests fail with 401 at the same time. How do you refresh the token only once?
What does HTTPS give you that plain HTTP does not?
Show the model answer
HTTPS is HTTP over TLS, and it gives three things: encryption, integrity, and proof of the server's identity. Encryption means nobody on the network can read the data. Integrity means nobody can change it on the way without detection. Identity comes from the certificate: a certificate authority that the browser trusts has signed a statement that this public key belongs to this domain. In the handshake the two sides agree on session keys, and the rest of the traffic uses fast symmetric encryption. HSTS is a response header that tells the browser to use only HTTPS for this site for a set time, so later visits never start with plain HTTP. Mixed content is when an HTTPS page loads a resource over HTTP. Browsers block it for scripts, and they upgrade or block it for images. Many browser features, such as service workers and geolocation, work only on HTTPS.
A strong answer covers
- Names all three: encryption, integrity and server identity
- Explains a certificate in one sentence: a trusted authority signs the domain and its public key
- Says HSTS makes the browser use HTTPS on later visits
- Says what mixed content is and that browsers block or upgrade it
Likely follow-up
What does HTTPS not protect you from?
Can you keep an API key secret in a frontend application?
Show the model answer
No. Everything that I send to the browser is public, because the user can read the bundle and see every network request. Environment variables do not change this. In Vite, a variable with the VITE_ prefix is replaced by its value at build time and ends up as plain text in the JavaScript. Next.js does the same with the NEXT_PUBLIC_ prefix. These are configuration, not secrets. So a secret key, such as a payment secret key or a key for an AI API, must stay on a server. The browser calls my backend, the backend checks who the user is, adds the key, and calls the third party. Some keys are designed to be public, for example a Stripe publishable key or a maps key. They are fine in the frontend, and where the provider allows it, I restrict them to my domain.
A strong answer covers
- Says clearly that nothing in the browser is secret
- Says build-time environment variables are written into the bundle
- Moves the secret behind a backend endpoint that also checks the user
- Separates public (publishable) keys from secret keys
Likely follow-up
A secret key was committed to the repository by mistake. What do you do first?
What are the Core Web Vitals, and what counts as a good score for each?
Show the model answer
The Core Web Vitals are three metrics from Google for loading, responsiveness and visual stability: LCP, INP and CLS. Largest Contentful Paint is the time until the largest image or text block in the viewport is rendered. Good is 2.5 seconds or less. Interaction to Next Paint measures the time from a click, tap or key press until the next frame is painted. It looks at the interactions of the whole visit and reports close to the slowest one. Good is 200 milliseconds or less. INP replaced First Input Delay in March 2024. Cumulative Layout Shift measures how much the visible content moves without the user expecting it. Good is 0.1 or less. A page passes when the 75th percentile of real visits is in the good range. So I look at field data, and not only at one Lighthouse run on my laptop.
A strong answer covers
- Names all three and what each measures: loading, responsiveness, visual stability
- Gives the good thresholds: 2.5 seconds, 200 milliseconds, 0.1
- Says INP replaced FID in 2024
- Says the score is the 75th percentile of real users, not one lab run
Likely follow-up
INP is 400 milliseconds on mobile. How do you find the cause?
The Largest Contentful Paint of a page is 4 seconds. How do you improve it?
Show the model answer
First I find which element is the LCP element and where the time goes, and then I fix the largest part. The Performance panel or Lighthouse names the element, which is usually a hero image or a heading. The time has four parts: server response, the delay before the resource starts to load, the download, and the delay before it renders. If the server response is slow, I add caching or a CDN. If the image starts late, I put it in the HTML as an img element, not in JavaScript or a CSS background. For a page that only JavaScript renders, that means server rendering. I do not lazy-load the image, and I add fetchpriority high or a preload. If the download is slow, I serve a smaller file in AVIF or WebP at the right size. If rendering is late, I remove render-blocking CSS and scripts.
A strong answer covers
- Starts by finding the LCP element and measuring, not guessing
- Splits the time: server response, load delay, download, render delay
- Says the LCP image must be in the HTML and must not be lazy-loaded
- Names concrete fixes: a smaller image, fetchpriority or preload, a CDN, less blocking CSS
Likely follow-up
The LCP element is a heading, not an image. What do you check then?
How do you reduce the JavaScript bundle size of a React application?
Show the model answer
I measure first, and then I split, remove and replace. A bundle analyzer shows which modules take the space. Code splitting with dynamic import and React.lazy loads each route only when it is needed, and it does the same for heavy parts like a chart or an editor. Tree shaking removes exports that nothing imports, but it needs ES modules and packages without side effects. Then I look at the dependencies: I replace a heavy library with a lighter one or with a platform API, for example date-fns or Intl in place of Moment. The server compresses text files with Brotli or gzip. The trade-off is that many small chunks mean more requests and more loading states. So I split by route first, and I preload the next route when the user will probably go there.
A strong answer covers
- Measures with a bundle analyzer before changing anything
- Explains code splitting with dynamic import or React.lazy, by route first
- Says tree shaking needs ES modules and packages without side effects
- Mentions lighter dependencies and compression with Brotli or gzip
Likely follow-up
How do you stop the bundle from growing again after you have reduced it?
How do you load images and web fonts so that the page is fast and the layout does not shift?
Show the model answer
For images I send the right size in a modern format and I reserve the space. For fonts I avoid invisible text. AVIF and WebP files are clearly smaller than JPEG and PNG at similar quality. With srcset and sizes the browser chooses the file that fits the display size and the pixel density. I always set width and height, so the browser knows the aspect ratio, reserves the box, and nothing moves when the image arrives. Images further down the page get loading lazy, but never the LCP image. For fonts I use WOFF2 and load only the weights I need. font-display swap shows a fallback font immediately and changes to the web font later. I preload the one or two most important font files. The trade-off of swap is a small layout shift when the font changes, and I reduce it with a fallback font of similar size.
A strong answer covers
- Names modern formats: AVIF and WebP
- Explains srcset and sizes: the browser chooses a file for the screen
- Sets width and height so the space is reserved and CLS stays low
- Lazy-loads images lower on the page, but not the LCP image
- Uses font-display and preloads the main font file
Likely follow-up
What is the difference between font-display swap and optional?
What is semantic HTML, and when should you use ARIA?
Show the model answer
Semantic HTML means using the element that matches the meaning, such as button, nav, main, headings and label, and not a div for everything. The browser builds the accessibility tree from these elements. So a screen reader can announce a role, a name and a state, and the user can move between headings and landmarks. A native button is focusable and reacts to Enter and Space without extra code. ARIA is a set of attributes that add or change this information when HTML has no element for the job, for example tabs, or aria-expanded on a menu button. The first rule of ARIA is: do not use it when a native element does the job. ARIA changes only what is announced and adds no behavior, so a div with role button still needs tabindex and key handlers. Every control needs an accessible name, from its text, a label, or aria-label.
A strong answer covers
- Says native elements bring role, focus and keyboard behavior with no extra code
- States the first rule of ARIA: prefer a native element
- Says ARIA changes what is announced, not how the element behaves
- Explains what an accessible name is and where it comes from
Likely follow-up
A button shows only an icon. How do you give it an accessible name?
How do you make an interface, for example a modal dialog, work for people who use only a keyboard?
Show the model answer
Every action must be possible with the keyboard, the focus must always be visible, and the focus order must follow the visual order. I use native buttons, links and inputs, because they are focusable and already react to Enter and Space. I do not remove the focus outline; I style it with :focus-visible. I avoid positive tabindex values, because they break the natural order. Tabindex 0 puts an element into the order, and minus 1 makes it focusable only from code. A modal needs three steps. When it opens, focus moves into it. While it is open, Tab stays inside and Escape closes it. When it closes, focus returns to the button that opened it. The native dialog element with showModal does most of this for me. On pages with a long navigation, the first focusable element is a skip link to the main content.
A strong answer covers
- Says every action works with the keyboard alone
- Keeps a visible focus indicator, for example with :focus-visible
- Gives the three modal steps: move focus in, keep it inside, return it on close
- Mentions the native dialog element or a well-tested library
- Mentions a skip link to the main content
Likely follow-up
After a route change in a single-page application, where should the focus go?
This form shows an error after a failed submit. What is missing for accessibility, and how do you fix it?
<form>
<input type="email" placeholder="Email" />
<span class="error">Invalid</span>
<button>Send</button>
</form>
Show the model answer
Three things are missing: a real label, a link between the error and its field, and a way for a screen reader user to hear the error. A placeholder is not a label, so I add a label element and connect it to the input with for and id. When validation fails, I set aria-invalid to true. I point aria-describedby at the element with the error text, so the message is read together with the field. The text must say what is wrong and how to fix it, and it must not depend on color alone. After a failed submit I move focus to the first invalid field, or to an error summary. For an error that appears while the user types, I put the message in a live region, such as role alert. Related controls, such as radio buttons, go into a fieldset with a legend.
A strong answer covers
- Adds a label element connected to the input; a placeholder is not a label
- Links the error to the field with aria-describedby and sets aria-invalid
- Makes the error heard: focus moves to it, or it is in a live region
- Writes an error text that says how to fix the problem, and does not use color alone
- Groups related controls with fieldset and legend
Likely follow-up
When do you show an error: while the user types, on blur, or on submit?
Explain the CSS box model, and say when you choose flexbox and when grid.
Show the model answer
Every element is a box with four parts: content, padding, border and margin. With the default box-sizing, content-box, the width is only the content, so padding and border make the box larger. With border-box the width includes padding and border. That is easier to work with, so most projects set it on all elements. The margin is outside the border in both cases, and vertical margins of block elements can collapse into one. Flexbox lays out items in one dimension, a row or a column, and the size of the content decides the result. I use it for toolbars, navigation, and spacing items on a line. Grid works in two dimensions: I define rows and columns on the container and place items in them. I use it for page layouts and for cards that must align in both directions. They work well together: grid for the page, flexbox inside components.
A strong answer covers
- Names the four parts: content, padding, border, margin
- Explains content-box against border-box
- Says flexbox is one-dimensional and grid is two-dimensional
- Gives a real use for each: a toolbar for flexbox, a page layout for grid
Likely follow-up
How do you center an element both horizontally and vertically?
How does the browser decide which CSS rule wins, and why does z-index sometimes seem to do nothing?
Show the model answer
The cascade checks origin and importance first, then cascade layers, then specificity, then source order. Specificity counts selectors in three groups: IDs, then classes, attributes and pseudo-classes, then element names. With equal specificity the later rule wins, and an inline style beats any selector. !important overrides all of this, so it is a warning sign: the next person needs another !important to change it. I keep specificity low with single classes. Cascade layers, written with @layer, let me decide that one group of styles always beats another, whatever the specificity. z-index seems to do nothing in two cases. First, it applies only to positioned elements and to flex or grid items. Second, it compares only elements inside the same stacking context. Properties like opacity below 1, transform or filter create a new context, so a child with z-index 9999 still stays below anything that is stacked above its parent.
A strong answer covers
- Gives the order: importance, cascade layers, specificity, source order
- Explains specificity as IDs, then classes, then element names
- Says !important hides a specificity problem and is hard to override later
- Says z-index needs a positioned element, or a flex or grid item
- Explains a stacking context and names one property that creates it
Likely follow-up
How do you make sure that a modal or a tooltip is always on top?
What is the frame budget, and how do you keep the page responsive during heavy JavaScript work?
Show the model answer
At 60 frames a second the browser has about 16 milliseconds for each frame. In that time it must run JavaScript, style, layout and paint. If a script takes longer, frames are dropped and the animation is not smooth. The same main thread also handles input. So a long task, which is any task over 50 milliseconds, delays clicks and typing and makes INP worse. For visual updates I use requestAnimationFrame, because its callback runs just before the next paint. setTimeout is not tied to frames, so it can fire in the middle of a frame or miss one. For heavy work I have two choices. I split it into small chunks and give control back to the browser between them, with scheduler.yield where it exists or with setTimeout. Or I move pure computation to a Web Worker. In React, transitions and list virtualization also help.
A strong answer covers
- Gives the number: about 16 milliseconds a frame at 60 frames a second
- Defines a long task as over 50 milliseconds and links it to INP
- Says requestAnimationFrame runs before the next paint and setTimeout is not tied to frames
- Gives both fixes: split the work and yield, or move it to a Web Worker
Likely follow-up
How do you find the function that causes a long task for real users?
What is the difference between a Web Worker and a Service Worker, and when do you use each?
Show the model answer
A Web Worker runs heavy JavaScript away from the main thread, and a Service Worker is a proxy between the page and the network. A Web Worker belongs to the page that creates it. It has no access to the DOM, and it talks to the page with postMessage. The data is copied, or transferred for objects like an ArrayBuffer. I use it to parse a large file or search a big list, so the interface stays responsive. A Service Worker is registered for a scope on an origin and lives apart from any page. The browser starts it for events and stops it when it is idle. It can intercept fetch requests and answer from the Cache API, which gives offline support and custom caching. It also receives push messages, and it needs HTTPS. The trade-off is that a caching mistake stays in users' browsers until the worker updates.
A strong answer covers
- Says a Web Worker is for computation off the main thread
- Says a worker has no DOM access and communicates with postMessage
- Says a Service Worker intercepts network requests: offline, caching, push
- Says a Service Worker has its own life cycle, apart from the page, and needs HTTPS
Likely follow-up
How do you deliver a new version of a Service Worker to users who have the old one?
What changed from HTTP/1.1 to HTTP/2 and HTTP/3, and what does it mean for frontend work?
Show the model answer
Each version removes a bottleneck in how requests share a connection. In HTTP/1.1 a connection handles one request at a time, so browsers open about six connections for each host. Teams used heavy bundling and image sprites to send fewer requests, and domain sharding to get more connections. HTTP/2 sends many requests as parallel streams over one TCP connection and compresses the headers, so many small files cost much less. But it still runs on TCP: one lost packet stops all streams until it is sent again. That is head-of-line blocking. HTTP/3 runs on QUIC over UDP, where streams are independent, so a lost packet delays only its own stream. QUIC also has TLS 1.3 built in, connects faster, and survives a change of network. For frontend work, domain sharding is now harmful, and one large bundle is less necessary. Some bundling and code splitting still help compression and caching.
A strong answer covers
- Says HTTP/1.1 handles one request at a time on a connection, with about six connections per host
- Says HTTP/2 multiplexes streams on one TCP connection and compresses headers
- Explains TCP head-of-line blocking and how QUIC over UDP removes it
- Gives a conclusion for frontend work: no domain sharding, less need for one big bundle
Likely follow-up
With HTTP/2 and HTTP/3 everywhere, why do we still bundle JavaScript?
You need live updates in the browser. How do you choose between polling, Server-Sent Events and WebSocket?
Show the model answer
I choose by direction and by how fresh the data must be. Polling asks the server on a timer. It is the simplest, and it is fine when a delay of some seconds is acceptable, but it wastes requests. Long polling keeps the request open until the server has new data, which is faster but heavier for the server. Server-Sent Events is one long HTTP response in which the server pushes text events. It goes one way, it runs over normal HTTP, and the EventSource API reconnects by itself and sends the last event ID. WebSocket is a two-way connection that starts with an HTTP upgrade. It has the lowest cost per message, but reconnection, heartbeats and authentication are my own code, and with several servers I need a shared message broker. So notifications and a live dashboard fit Server-Sent Events or polling, and chat or collaborative editing fit WebSocket.
A strong answer covers
- Compares direction: SSE is server to client, WebSocket goes both ways
- Says EventSource reconnects by itself; WebSocket reconnection is your own code
- Says polling is a valid simple choice when a delay of seconds is fine
- Matches the cases: chat to WebSocket, notifications and dashboards to SSE or polling
Likely follow-up
How do you scale WebSocket connections across several server instances?
How do you measure frontend performance, and how do you stop it from getting worse over time?
Show the model answer
I measure with lab data and field data, and I protect the result with a budget in CI. Lab data comes from a controlled test, such as Lighthouse on a fixed device and network. It is repeatable, good for debugging, and can run on every pull request. Field data comes from real users: the Chrome User Experience Report, or my own real-user monitoring, where the web-vitals library sends LCP, INP and CLS to analytics. It shows what people get on slow phones and networks, and I read it at the 75th percentile, not the average. INP needs field data, because it depends on real interactions. To find a cause, I record a trace in the DevTools Performance panel with CPU throttling. To catch regressions, I set a performance budget, for example a maximum bundle size and a Lighthouse CI threshold, and the build fails when a change goes over it.
A strong answer covers
- Separates lab data (Lighthouse, repeatable) from field data (real users)
- Reads field data at the 75th percentile
- Names a tool to find causes: the DevTools Performance panel
- Describes a performance budget that fails the build in CI
Likely follow-up
Lighthouse shows a score of 95, but real users say the page is slow. How can both be true?
How do you test that a feature is accessible?
Show the model answer
I combine automated checks with manual checks, because automated tools find only part of the problems, roughly a third to a half by common estimates. Tools like axe and Lighthouse catch missing labels, missing alt text, wrong ARIA and low color contrast. They cannot tell me whether the focus order makes sense, whether alt text is useful, or whether a custom widget can really be used. So I do three manual checks. I go through the feature with only the keyboard: can I reach everything, see the focus, and close what I opened. I try it with a screen reader, VoiceOver or NVDA, and listen to the names, roles and states. I check contrast, zoom to 200 percent, and the reduced motion setting. The target is WCAG 2.2 level AA. In CI I run axe in component or end-to-end tests and a lint plugin such as jsx-a11y.
A strong answer covers
- Says automated tools catch only part of the issues
- Names a tool: axe or Lighthouse
- Describes a keyboard-only walk-through and a screen reader check
- Names WCAG 2.2 level AA as the target
- Runs automated checks in CI so that regressions are caught
Likely follow-up
Which accessibility problems can no automated tool find?