Google JavaScript Frontend Developer Interview Questions & Answers

google icon

Questions with Detailed ExplanationsWith Detailed Explanations

(Last Updated: August 28, 2026)

11. How would you assess cross-site leaks through timing and observable window state?SecurityHardGoogle

Question Details

An attacker cannot read an authenticated application's response, but can embed or open selected URLs and measure load timing, frame count, navigation behavior, cache state, or whether a popup closes. Identify the secret state being inferred, attacker capabilities, browser isolation boundary, and observable side channel. Design mitigations using response uniformity, state-changing protections, framing and opener controls, cache partitioning assumptions, cross-origin resource policies where applicable, and reduced distinguishability. Include test cases for logged-in versus logged-out resources, existence of a private document, cached versus uncached responses, blocked framing, and noisy timers. Explain residual risk without claiming CORS prevents information leaks caused by observable behavior.

Short Interview Answer (30-60 seconds)

I would model the leak as secret state plus an attacker-controlled cross-site action plus an observable difference. I would remove distinguishable responses, protect state changes, restrict frames and openers, avoid relying on cache isolation, apply relevant cross-origin policies, and test login, private-resource, cache, framing, and timing cases.

Detailed Explanation

The attacker cannot directly see the private page, but they may still learn something by watching how the browser behaves. For example, a page may load faster, open differently, stay open, close, or behave differently when a person is signed in or owns a private item. The goal is to find which hidden fact can be guessed from those visible differences. Then we make those differences harder or impossible to observe, especially when they reveal whether a person is signed in, whether private content exists, or whether an action succeeded.

Useful Questions to Ask the Interviewer
  1. Which secret states matter most: login status, private document existence, account membership, or another user-specific fact?
  2. Can the attacker embed the target in a frame, open it in a popup, navigate a window, or only load passive resources?
  3. Are state-changing endpoints in scope, or only read-only URLs?
  4. Which browsers and isolation features should I assume for the 2026 baseline?
  5. Can the application change server response behavior, cache policy, framing policy, and opener behavior?
How would you assess cross-site leaks through timing and observable window state? diagram
How to Explain It in an Interview

I would start with a four-part threat model.

  1. Identify the secret state being inferred. The secret might be whether the victim is logged in, whether a private document exists, whether the victim has access to a resource, whether a message is present, or whether some user-specific state affects caching. I would name the exact bit of information the attacker wants to learn.
  1. Identify attacker capabilities. The attacker may control another origin and persuade the victim to visit it. From that page, the attacker may create an iframe, open a popup, navigate a window, load a cross-origin resource, or measure elapsed time and browser-visible events or state. The attacker still cannot directly read a protected cross-origin document because of the same-origin policy, but direct reading is not required for an XS-Leak.
  1. Identify the browser isolation boundary. The main boundary is the same-origin policy. It restricts one origin from reading another origin's DOM and many response details. CORS selectively grants cross-origin response-reading permission to certain APIs such as fetch. CORS is not a general defense against XS-Leaks, so I would never claim that CORS prevents timing, navigation, popup, framing, cache, or other leaks caused by observable behavior.
  1. Identify the observable side channel. I would enumerate only behavior the browser actually exposes to the attacker in the chosen attack primitive. Examples can include total operation timing, whether embedding is blocked, whether a window remains open or becomes closed, navigation behavior, load or error signals available for the particular resource type, and cache-dependent timing. I would not assume that arbitrary cross-origin DOM properties or detailed navigation state are readable. The important question is whether two secret states produce measurably different permitted observations.

From there, I would reduce distinguishability and unnecessary attacker capabilities.

Response uniformity is an important application-level control. For sensitive existence checks, I would avoid unnecessarily different externally observable paths for 'exists and authorized', 'exists but unauthorized', and 'does not exist' when that distinction itself is sensitive. The trusted server must still perform authorization for every request. Authentication tells us who the user is; authorization determines whether that user may access the resource. Where practical, unauthorized and nonexistent private resources should have similar status behavior, redirect behavior, response size, rendering work, and major backend processing so an attacker has fewer stable differences to measure. Perfect constant-time behavior is rarely realistic for a distributed web application, so the goal is to remove strong deterministic signals rather than promise mathematically identical timing.

For state-changing requests, I would prevent unauthorized cross-site triggering rather than merely hide the result. Use server-side CSRF protections appropriate to the application, such as SameSite cookie settings, unpredictable anti-CSRF tokens when needed, and Origin or Referer validation where appropriate. State-changing operations should use methods with correct HTTP semantics rather than GET. The trusted server must authenticate and authorize the request and fail safely. An information leak is more damaging if the attacker can also cause an action.

For framing, I would use the Content-Security-Policy frame-ancestors directive to restrict which origins may embed sensitive pages. X-Frame-Options can provide compatibility protection for older clients where required, but frame-ancestors is the modern, more expressive control. Blocking framing removes many iframe-based attack opportunities and also reduces clickjacking risk. I would test the denied case because differing frameability between secret-dependent endpoints can itself provide an observable distinction.

For windows and opener relationships, I would remove unnecessary relationships between an attacker-controlled page and a sensitive page. Links that open a new browsing context should use rel="noopener" when the opener is unnecessary. A programmatic window.open can request the noopener feature. Cross-Origin-Opener-Policy can provide stronger browsing-context-group isolation when the application can accept its compatibility effects. These controls reduce classes of attacks that rely on retaining a reference to a cross-origin popup and observing limited window state or navigation-related effects.

For cache-based leaks, I would not assume one global browser cache is shared equally across every site. Current browsers partition many caches and storage mechanisms by top-level site or other keys, which blocks or weakens many historical cross-site cache probes. However, the exact partition key and protected resource type vary across browser implementations and can evolve. I would therefore treat partitioning as defense in depth, not the application's security boundary. Sensitive personalized responses need deliberate Cache-Control behavior, and shared intermediaries must never serve authenticated content to the wrong user.

For cross-origin resource policies, I would apply a policy only when it matches the resource and threat. Cross-Origin-Resource-Policy can prevent certain no-cors cross-origin resource loads by requiring an allowed same-origin or same-site relationship. Cross-Origin-Embedder-Policy can be part of a stronger cross-origin-isolated deployment when the application needs it, but it has compatibility requirements and is not a universal XS-Leak fix. These headers should remove a specific attacker capability rather than be treated as a generic collection of security headers.

I would also remove secret-dependent window and navigation behavior. A popup should not close only when a secret condition is true if an untrusted opener can observe that difference. Avoid secret-dependent redirect chains, distinguishable navigation behavior, frame counts, or substantially different rendering and processing paths when those differences are observable across origins. The correct mitigation depends on the particular primitive, so I would first verify what the browser exposes instead of assuming every window property is available cross-origin.

Timer noise and reduced timer precision are useful defenses but are not sufficient by themselves. An attacker may repeat a measurement many times and use statistical analysis to recover a stable difference. Normal network noise also does not guarantee safety. The application should first remove large deterministic differences, then treat browser timer limitations as additional defense in depth.

I would verify the design with explicit paired tests.

For logged-in versus logged-out resources, I would repeatedly trigger the same target from an attacker origin in both states and compare only attacker-visible signals such as timing, permitted load or error behavior, redirect-related effects, popup state, and framing results. The expected result is that the attacker cannot reliably classify authentication state.

For private document existence, I would compare an identifier for an existing but unauthorized document with an identifier that does not exist. Authorization must deny access in both cases. I would look for differences in externally observable navigation behavior, redirects, major processing time, cache effects, framing behavior, or other signals exposed by the chosen primitive. The attacker should not gain a reliable document-existence oracle.

For cached versus uncached responses, I would test a cold state and an intentionally warmed state using the supported browser's real cache-partitioning behavior. I would confirm whether an attacker origin can observe any useful difference and verify that a cache hit cannot reveal user-specific private state. I would not claim safety solely because one browser currently partitions a particular cache.

For blocked framing, I would attempt to embed sensitive pages from an untrusted origin and verify that frame-ancestors prevents embedding. I would also verify that secret-dependent endpoints do not have inconsistent framing policies that expose a useful yes-or-no distinction.

For noisy timers, I would collect many samples for each secret state rather than comparing one measurement. I would compare their distributions under realistic browser, CPU, and network noise. If a practical classifier can still distinguish the states reliably, the signal is still useful to an attacker and the mitigation needs improvement.

Residual risk remains because some cross-origin interactions intentionally expose limited observable state, browsers change over time, network and processing behavior can remain statistically distinguishable, and repeated measurements can amplify small signals. The safest design minimizes secret-dependent observable differences, restricts unnecessary embedding and opener relationships, protects state-changing actions, enforces authorization on the trusted server, and tests supported browsers continuously. CORS should be described only as a mechanism that controls cross-origin response reading for applicable APIs, not as a guarantee against information leaks caused by observable behavior.

Technical Approach
  1. Define the exact secret bit the attacker wants to infer.
  2. Enumerate attacker actions such as iframe embedding, popup opening, navigation, cross-origin resource loading, and repeated timing.
  3. State the browser isolation boundary, especially the same-origin policy, and identify what remains legally observable despite it.
  4. Build paired secret states and find deterministic differences in timing, redirects, framing, popup state, cache behavior, navigation, or resource events.
  5. Remove strong differences through response uniformity while preserving correct trusted-server authorization.
  6. Remove unnecessary attack primitives with CSRF protections, frame-ancestors, opener isolation, and relevant resource policies.
  7. Treat cache partitioning and timer noise as defense in depth rather than guarantees.
  8. Test logged-in versus logged-out, existing versus nonexistent private resources, cached versus uncached, allowed versus blocked framing, and repeated noisy timing samples.
  9. Reassess residual signals across supported browsers after application or browser changes.
Practical Insights

The logic for one test is usually small, but reliable testing needs repeated measurements because browser, CPU, server, and network timing are noisy. If there are S secret-state pairs, V observable signals, B supported browsers, and R repetitions, the measurement work grows roughly with S × V × B × R. Memory use is normally small because the test only needs measurement samples and sanitized logs. Server cost can rise if sensitive response paths are made more uniform or if fast-path differences are removed. Operational cost includes maintaining authorization rules, CSRF defenses, security headers, cache policies, and browser regression tests. Maintenance is important because browser isolation, cache partitioning, and observable APIs can change.

Why Interviewers Ask This

This question tests whether the candidate understands that browser isolation can stop direct cross-origin reads while information can still leak through observable behavior. It evaluates threat modeling, same-origin policy limits, XS-Leak side channels, timing and cache reasoning, framing and opener defenses, server-side response design, protections for state-changing requests, browser privacy controls, and practical verification of residual risk.

Common interview mistakes

A common mistake is saying that the same-origin policy or CORS prevents all cross-site information leaks. The same-origin policy blocks many direct reads, while CORS grants response-reading permission to selected origins for applicable APIs; neither statement means observable side channels disappear. Another mistake is measuring one request instead of repeated distributions. Candidates may also fail to identify the exact secret, attacker primitive, isolation boundary, and permitted observable signal. Other mistakes include relying only on timer noise, assuming all browser caches are partitioned identically, creating clearly different unauthorized and nonexistent paths, protecting reads while leaving state-changing requests vulnerable to CSRF, confusing authentication with authorization, applying framing controls without testing distinguishability, and retaining unnecessary opener relationships. Finally, do not promise perfect constant-time web responses or invent cross-origin observability that the browser does not expose.

Interview tip

Structure the answer as secret state, attacker capability, browser boundary, observable side channel, mitigation, and verification. Explicitly say that the same-origin policy can block direct reading while observable behavior can still leak information, and that CORS is not a general XS-Leak defense. Finish with paired tests and residual risk.

Interviewer may ask next
If modern browsers reduce timer precision and partition caches, can we stop worrying about timing and cache-based cross-site leaks?

No. Those browser defenses reduce precision and remove some historically shared state, but they are defense in depth. Large timing differences can survive reduced timer precision, and attackers can repeat measurements and use statistics. Cache partitioning also depends on the browser, cache type, partition key, and resource behavior. The application should still minimize secret-dependent observable differences, configure sensitive caching deliberately, restrict unnecessary cross-site loading, and test the actual supported browsers.

How would you handle an endpoint where an existing but unauthorized private document is naturally slower to process than a nonexistent document?

First keep authorization on the trusted server and never return private content to an unauthorized user. Then remove avoidable differences in the attacker-visible path. For example, use similar error handling, redirect behavior, response shape, and major processing stages for unauthorized and nonexistent identifiers when existence is sensitive. I would not add arbitrary sleep calls and claim constant-time behavior, because scheduling and network variance make that fragile and delays can create denial-of-service cost. Instead I would restructure lookups where practical, remove large deterministic differences, repeatedly measure the attacker-visible distributions, and verify that document existence cannot be classified reliably.

12. Design a browser XHR request interface with headers, errors, and cancellation.API DesignEasyGoogle

Question Details

Design the public contract for request(options) around the browser's XHR capability. Define options for method, URL, request headers, body, response type, credentials, timeout, and AbortSignal; define the fulfilled response record with status, status text, final URL, response headers, and parsed body. Specify behavior for HTTP errors, network failure, timeout, abort, invalid headers, redirects, and a 204 response. State whether the promise fulfills or rejects for non-2xx status and keep that choice consistent. Include one deterministic example call and result shape, cleanup after settlement, and a compatibility boundary for browser features without exposing the underlying XHR object as mutable shared state.

Short Interview Answer (30-60 seconds)

I would expose one Promise-based request(options) function and keep the XMLHttpRequest object private. The caller provides the method, URL, headers, body, response type, credentials mode, timeout, and optional AbortSignal. I validate the options, send the request through XHR, and return a normalized response record for 2xx responses. A 204 response fulfills with data: null. Non-2xx responses reject with HTTPError. Network failure, timeout, abort, invalid setup, and parsing failure use separate errors. I also remove listeners and timers after settlement. Browser CORS, same-origin, credential, and HTTPS rules still apply.

Detailed Explanation

This API gives application code one simple way to make browser requests. The caller does not need to manage an XHR object directly. It passes normal request options and receives one Promise. The helper validates the input, starts the request, watches for cancellation or timeout, and then processes the response. Successful 2xx responses fulfill with one stable response shape. A 204 response has data: null. HTTP failures, network problems, timeouts, aborts, invalid request setup, and invalid JSON reject clearly. The helper also cleans up listeners and timers after finishing.

Useful Questions to Ask the Interviewer
  • Should every non-2xx HTTP status reject the Promise? The approved design says yes.
  • Which browser versions must support AbortController?
  • Should response shape validation be optional or always enabled?
Design a browser XHR request interface with headers, errors, and cancellation. diagram
How to Explain It in an Interview
1. Define the browser contract

I would expose request(options): Promise<ResponseRecord>.

The options are:

  • method: a string such as GET or POST. The default is GET.
  • url: the required request URL.
  • headers: a record of request header names and values.
  • body: supported request data such as a string, Blob, FormData, URLSearchParams, ArrayBuffer, Document, or an object that the wrapper serializes.
  • responseType: json, text, blob, arraybuffer, or document. The default is json.
  • credentials: omit, same-origin, or include. The default is same-origin.
  • timeout: milliseconds. Zero means no timeout.
  • signal: an optional AbortSignal.

The fulfilled response record contains status, statusText, final url, response headers, and parsed data.

The application never receives the mutable XHR instance. The helper owns it internally and exposes only the Promise and normalized response data.

2. Start and control the request

A user action or page lifecycle event starts the call. The application first validates the method, URL, header names, header values, and body. It then normalizes the options and applies defaults.

The helper creates and configures one XHR request. It calls open(method, url, true), applies request headers, sets credential behavior, assigns the requested response type, sets the timeout, and sends the body.

An AbortController can control cancellation. Its signal is passed into request(options). If the caller later calls controller.abort(), the helper aborts the in-flight XHR and rejects with AbortError.

The same cancellation mechanism can stop work that is no longer useful. This also protects the UI from stale responses caused by an obsolete request.

If the configured timeout expires before a response finishes, the request stops and rejects with TimeoutError.

3. Validate the response

When a response arrives, the helper reads the HTTP status, status text, final URL, and response headers.

It then handles the response body according to responseType. For JSON, invalid JSON rejects with ParseError rather than returning broken data.

The design can also run a response shape check when the application provides one. That check happens before parsed data becomes valid application data.

For a normal 2xx response, the helper builds the fulfilled response record. For 204 No Content, the Promise still fulfills, but data is exactly null.

Redirects are followed by normal browser XHR behavior, up to browser limits. The response record uses the final URL exposed by XHR after redirects.

4. Handle success and failure

The Promise fulfills only for 2xx HTTP statuses in this design.

For a successful response, the caller receives { status, statusText, url, headers, data }.

For a non-2xx HTTP response, the Promise rejects with HTTPError. The error can contain the status, status text, response headers, and available parsed response data.

A network-level failure rejects with NetworkError. This represents a failure where the application does not receive a usable HTTP response.

A timeout rejects with TimeoutError. An AbortSignal cancellation rejects with AbortError.

Invalid request options or invalid headers reject with TypeError. Invalid JSON rejects with ParseError.

These outcomes stay separate. The UI can therefore show success, empty, aborted, validation, retryable, or final-error states without treating every result as the same problem.

5. Protect the browser boundary

The remote API is one external boundary. The frontend sends an HTTPS request containing the method, URL, headers, body, and credentials according to the selected policy. The remote API returns an HTTP response containing status, headers, and body.

Browser security rules still control what JavaScript can send and read. The same-origin policy limits cross-origin access. CORS controls whether browser JavaScript may read an allowed cross-origin response. CORS is not authentication.

Credential behavior also matters. Cookies may be sent according to browser rules and the configured credentials option. HTTPS protects the request and response while they travel across the network.

The wrapper does not bypass browser security rules. It also never exposes the underlying XHR object as mutable shared state.

6. Verify the behavior

I would test the public Promise contract instead of exposing XHR internals.

A deterministic example is: request({ method: "GET", url: "/api/items?limit=10", headers: { "Accept": "application/json" }, body: undefined, responseType: "json", credentials: "same-origin", timeout: 10000, signal: controller.signal }).

For the approved success example, the Promise fulfills with status 200, status text OK, final URL https://app.example.com/api/items?limit=10, response headers including content-type: application/json; charset=utf-8 and cache-control: no-store, and parsed data containing two items named Item 1 and Item 2.

I would also verify that a 204 response returns data: null. A non-2xx response rejects with HTTPError. Cancellation rejects with AbortError. Network failure rejects with NetworkError. Timeout rejects with TimeoutError. Invalid setup rejects with TypeError. Invalid JSON rejects with ParseError.

After fulfillment or rejection, the helper removes XHR event listeners, clears timers and cancellation hooks, releases references, and makes sure the Promise settles only once.

Practical Complexity & Trade-offs

The wrapper adds a small amount of JavaScript work around XHR. Request setup grows with the number of headers and the body size. Response parsing mainly depends on response size. JSON parsing also needs memory for the parsed result. Cancellation saves work when an old request is no longer useful. The main maintenance trade-off is simplicity for callers versus more responsibility inside the wrapper. The wrapper must translate several XHR events into clear, stable Promise outcomes. Compatibility is another boundary. The design depends on standard browser XHR and uses AbortController for cancellation. If AbortController is unavailable, signal-based cancellation is not supported by this contract. Browser security rules such as same-origin policy, CORS, credential handling, and HTTPS still remain browser responsibilities.

Why Interviewers Ask This

The interviewer is checking whether I can turn a low-level browser API into a small, predictable public contract. They want clear request and response modeling, correct Promise behavior, HTTP error handling, cancellation, timeout handling, validation, and cleanup. They also want to see whether I understand browser security boundaries such as CORS and credential behavior. The important skill is engineering judgment: hiding mutable implementation details while giving callers useful, consistent results and errors.

Interviewer may ask next
How would you prevent an older XHR response from updating the UI after a newer request has started?

I would keep the existing request(options) contract and cancel the obsolete request when possible. When a new user action makes the old request unnecessary, the application calls AbortController.abort() for the older call. Its AbortSignal is already connected to the XHR wrapper. The wrapper therefore aborts that XHR and rejects its Promise with AbortError. The UI treats this as intentional cancellation instead of a normal failure. The application can also keep a current request identifier as a stale-response guard. A completed response updates the UI only when its identifier still matches the newest request. The XHR execution, response parsing, 2xx fulfillment rule, non-2xx rejection rule, and response record stay unchanged. Browser CORS, credential, same-origin, and HTTPS rules also stay unchanged. This maintains correctness because an obsolete result cannot replace newer data. The main downside is extra lifecycle bookkeeping. The application must cancel or invalidate old work at the right time and avoid showing an expected abort as a user-facing error.

What should happen if AbortController is not available in a supported browser?

I would keep the same XHR request contract, but clearly mark cancellation as unavailable in that browser. The normal request(options) flow can still validate options, create the private XHR instance, send the HTTPS request, process the response, and return the normalized Promise result. The difference is the signal capability. Without AbortController, the caller cannot use this contract to cancel an in-flight request through AbortSignal. I would not expose the underlying XHR object as a workaround because that would break the design boundary and create mutable shared state. The 2xx fulfillment rule, 204 data: null behavior, non-2xx HTTPError, network failure, timeout, parsing behavior, and cleanup stay unchanged. Browser same-origin, CORS, credentials, and HTTPS rules also remain unchanged. This keeps the API predictable and secure. The main downside is reduced functionality on browsers without AbortController. Those browsers can still make requests, but they cannot use the cancellation feature defined by this interface.

13. Design the submission contract for sending page data without a refresh.API DesignEasyGoogle

Question Details

Design a framework-neutral submitForm({url, fields, signal}) contract for a browser page that must send data without navigation. Define the accepted field value schema, encoding choice, request headers, credentials behavior, response record, field-error shape, global error shape, duplicate-submit rule, and cancellation behavior. Distinguish validation failure from transport failure and an HTTP response that rejects the operation. State what happens for an empty form, a 204 success, malformed JSON, navigation during the request, and an already-aborted signal. Include one request and exact success-result example so consumers can render pending, success, and error states without reading hidden XHR state.

Short Interview Answer (30-60 seconds)

I would make submitForm({url, fields, signal}) a small browser contract that validates the form, shows a pending state, and sends one POST request without navigation. JSON-compatible values use application/json; File or Blob values need multipart/form-data. I would allow only one active submission per form and use AbortController to cancel replaced or stale work. HTTP validation and operation errors return explicit ok: false results, while aborts and transport failures reject the Promise. Credentials follow the browser origin rules. Every response is checked before the UI renders success, empty, validation, aborted, or final-error states.

Detailed Explanation

The page needs to send form data without leaving the page. The browser should validate the values, send one request, wait for the result, and show a clear state. The caller should not inspect hidden XHR state. Instead, submitForm gives the caller a defined Promise contract. That contract explains valid field values, body encoding, credentials, duplicate submits, cancellation, success data, and each failure type. The browser talks to one external Remote API. It also checks every HTTP response before changing the visible UI.

Useful Questions to Ask the Interviewer
  • Should this form accept only JSON-compatible values, or must it also accept File or Blob values?
  • For cross-origin requests, are credentialed cookies required and allowed by the API CORS policy?
  • What exact response schema should the browser validate for this operation?
  • When a newer submit replaces a pending submit, should the previous request always be aborted?
Design the submission contract for sending page data without a refresh. diagram
How to Explain It in an Interview
1. Define the browser contract

I would expose submitForm({ url, fields, signal }).

url is a string. It can be an absolute URL or a same-origin path.

fields is a record whose keys are field names. For the JSON path, each value must be JSON-compatible. The diagram allows strings, numbers, booleans, null, arrays of supported values, and nested objects using the same rules. An undefined field is omitted.

If File or Blob values are required, that submission uses multipart/form-data. I would not pass File or Blob objects through JSON.stringify.

The request method is POST.

For the JSON path, the body is JSON.stringify(fields). The request uses Content-Type: application/json; charset=utf-8 and Accept: application/json. The approved contract also shows X-Requested-With: fetch. A CSRF token header is added only when the server requires it.

Credentials follow the browser rules. Same-origin requests use same-origin. A cross-origin request uses credentials: "include" only when the API permits credentialed CORS.

The example request is POST /api/profile with JSON fields such as name, email, and age.

A successful resolved value has this shape: { ok: true, status: number, data: T | null, headers: Record<string, string>, url: string, durationMs: number }.

The exact success example is { "ok": true, "status": 200, "data": { "id": "u_123", "updatedAt": "2025-05-10T12:36:00Z" }, "headers": { "content-type": "application/json" }, "url": "https://app.example.com/api/profile", "durationMs": 312 }.

For a validation rejection from the API, the resolved value is an error result with ok: false, status: 422, type: "validation", fieldErrors, formError, and code.

Each field error has the shape { field: string, code: string, message: string }.

A global operation error uses { ok: false, status: number, type: "error", code: string, message: string, details: unknown, retryable: boolean }.

2. Start and control the request

The flow starts when the user submits the form.

The browser first validates the fields. If local validation fails, no request needs to be sent.

The browser then builds the request and creates or uses an AbortController. The caller may supply an existing signal.

The UI enters the pending state while the active request is running.

The duplicate-submit rule is one active submission at a time per form instance. The normal UI can disable submit while pending. If another ordinary submit arrives while one is active, it can be ignored. If the application intentionally starts a replacement submit, it aborts the previous request first.

The stale-response guard prevents an old or aborted request from changing the current UI.

If the supplied signal is already aborted, no request is sent. The Promise rejects immediately with an AbortError.

If navigation unloads the page during the request, the browser request is treated as aborted. The Promise rejects with AbortError, and the old page must not receive a visible UI update.

3. Send the request through the browser boundary

Fetch sends the HTTPS request toward the one external Remote API.

The browser boundary includes the same-origin policy, CORS, TLS, and credential behavior.

The same-origin policy limits cross-origin access by browser JavaScript.

CORS controls whether JavaScript may read an allowed cross-origin response. CORS is not authentication.

Same-origin credentials can include cookies. Cross-origin cookies are included only with credentials: "include" and when the server permits that credentialed CORS request.

If automatically sent credentials create a CSRF risk, the request includes the server-required CSRF protection. The browser does not invent or enforce trusted server authorization rules.

4. Validate the response

When an HTTP response arrives, the browser first checks its status.

Fetch normally resolves when an HTTP response arrives. A 4xx or 5xx status does not by itself make Fetch reject.

The browser then checks Content-Type.

If JSON is expected and a body exists, it parses the JSON.

After parsing, it validates the response shape before updating the UI.

A malformed JSON body becomes a final parse error. The diagram names this parse_error and marks it as not retryable.

A 204 response has no body. It is still successful. The success result uses status: 204 and data: null, and the UI shows the empty success state.

For an empty form, the JSON path sends {}. The remote API decides whether that operation is valid. If it returns a supported success response, the browser treats it as success.

5. Handle success and failure

The contract keeps HTTP outcomes, transport failures, and cancellation separate.

A 200 or 201 success resolves with ok: true and returned data.

A 204 success resolves with ok: true, status: 204, and data: null.

A validation rejection such as 422 is an HTTP response. It resolves with ok: false, type: "validation", and the field or form errors. The UI can place field messages beside the correct inputs.

An HTTP 4xx or 5xx response that rejects the operation also resolves normally at the Fetch level. The contract converts it into an ok: false, type: "error" global result.

A transport failure is different because the browser did not obtain a usable HTTP response. The diagram includes network errors, timeouts, DNS failures, and CORS failures in this category. The Promise rejects with a GlobalError marked retryable: true when appropriate.

An abort also rejects the Promise, but with AbortError rather than a validation or global HTTP result.

A malformed JSON response becomes a final parse_error with retryable: false.

These explicit outcomes let the consumer render pending, success, empty, validation, aborted, and final-error states without reading hidden XHR state.

6. Verify the behavior

I would test each contract path directly.

For a normal JSON form, I would verify the POST method, headers, credentials mode, and serialized body.

For an empty form, I would verify that {} is sent.

For a 200 response, I would verify the exact success result shape.

For a 204 response, I would verify data: null and the empty success state.

For a 422 response, I would verify that the Promise resolves with type: "validation" and the correct field errors.

For another 4xx or 5xx operation rejection, I would verify that Fetch is not treated as a transport failure and that the caller receives an ok: false, type: "error" result.

For malformed JSON, I would verify a final non-retryable parse_error.

For a transport failure, I would verify that the Promise rejects with the global transport error.

For an already-aborted signal, I would verify immediate AbortError rejection and confirm that no request is sent.

For navigation or an intentional replacement submit, I would verify that AbortController cancels the stale request and that its result never updates the visible UI.

Practical Complexity & Trade-offs

There is one network request for one accepted submission. Encoding and parsing work grow with the size of the submitted data. JSON is simple for normal values, but File or Blob values need multipart form encoding. Allowing only one active submission makes duplicate behavior easier to understand. AbortController also prevents stale work from updating the page. Credentialed cross-origin requests are more complex because CORS and CSRF rules must be correct. The design does not define automatic mutation retries, so the browser should not retry a failed POST by itself. That avoids accidentally performing the same operation twice.

Why Interviewers Ask This

The interviewer is testing whether I can define a clear browser API contract instead of hiding behavior inside event handlers. They want correct request and response modeling, correct Fetch behavior, useful validation errors, and clear cancellation rules. They also want me to separate an HTTP rejection from a transport failure. The browser security boundary checks my understanding of credentials, CORS, same-origin policy, and CSRF. Duplicate and stale-request handling shows whether I can make asynchronous UI behavior predictable and safe.

Interviewer may ask next
What changes if the user submits again while the first request is still pending?

I would keep one active submission per form instance. The affected flow is the submit action, the stale or duplicate guard, AbortController, and the existing POST request. For a normal second click while the first request is pending, the UI can ignore the new submit or keep the button disabled. If the application intentionally treats the newer values as a replacement, it first aborts the older request and then starts the new POST. The older Promise rejects with AbortError, and its stale result is never allowed to change the current UI.

The request method, headers, credentials behavior, response validation, and error shapes stay unchanged. Security also stays the same because the newer request crosses the same browser and Remote API boundary.

The main downside is that canceling the browser request does not prove that the remote operation was never processed. Because this contract defines no idempotency key or safe retry rule, I would not claim replacement submits are automatically safe to repeat after an uncertain network failure.

What changes when the form needs to upload a File or Blob?

I would change only the field schema and request-encoding path. The affected component is request construction inside submitForm. The current JSON path accepts JSON-compatible values and sends application/json. If the form must accept File or Blob values, that request instead builds a FormData body and uses multipart form encoding. I would not send File or Blob objects through JSON.stringify.

The rest of the design stays the same. The user still enters a pending state. AbortController still handles replacement and cancellation. The stale-response guard still blocks obsolete updates. Credentials still follow the same-origin or permitted cross-origin rule. The Remote API remains the single external boundary. The returned response still goes through status checks, content-type checks, parsing when appropriate, schema validation, and the same visible success or error states.

The main downside is extra client complexity. The contract now supports two body formats, so tests must prove that normal JSON values and file uploads choose the correct encoding every time.

14. Design the public API of a webpage analytics SDK.API DesignEasyGoogle

Question Details

Design a small browser SDK contract with methods such as init(config), track(name, properties?), page(view?), identify(user?), and shutdown(). Define event names, allowed property values, timestamp ownership, anonymous and signed-in identity transitions, return values, invalid-input behavior, and whether calls before initialization are rejected or queued. Specify how repeated initialization and multiple instances behave, how consumers opt out, and which data is never collected automatically. Include a short usage sequence that produces a fully defined event record while keeping transport, batching, and persistence behind the SDK boundary.

Short Interview Answer (30-60 seconds)

I would expose a small browser SDK with init(config), track(name, properties?), page(view?), identify(user?: User | null), and shutdown(). init, track, page, and identify return void, while shutdown returns Promise<void>. The SDK validates input, adds a client-owned timestamp, applies anonymous or signed-in identity, and queues events behind the SDK boundary. Calls before init are queued up to 100. Repeated init updates the singleton configuration. The SDK batches JSON and sends it over HTTPS to one remote analytics API. Consumers can opt out, and sensitive page or form data is never collected automatically.

Detailed Explanation

This question asks me to design the public contract for a small browser analytics SDK. Website code should have a few simple methods for page views, custom events, identity, setup, and shutdown. The application should not manage queues, batching, persistence, or network transport itself. Those details stay hidden inside the SDK. The contract must also define valid property values, timestamps, anonymous users, signed-in users, invalid calls, repeated initialization, multiple instances, opt-out behavior, and privacy rules. The goal is a predictable API that is easy for frontend developers to use correctly.

Useful Questions to Ask the Interviewer
  • Should calls before init be rejected or queued?
  • Should repeated init calls fail or update configuration?
  • Should multiple SDK instances share one browser singleton?
  • Which property value types should track accept?
  • Should identify(null) clear the signed-in identity?
  • Which information must never be collected automatically?
Design the public API of a webpage analytics SDK. diagram
How to Explain It in an Interview
1. Define the browser contract

I would expose five public methods.

init(config) starts the SDK. apiKey is the required string. The shown configuration can also include endpoint, appVersion, debug, autoPageView, and optOut. init returns void. The first call initializes the singleton. Later init calls update its configuration and continue using the same instance.

track(name, properties?) records an event. name must be a string. properties is optional. Each property value may be a string, number, boolean, null, or an array of strings, numbers, or booleans. This keeps the public event properties JSON-safe. track returns void.

page(view?) records a page_view event. The view object may contain path, title, and referrer. The diagram requires path when a view object is supplied. page returns void. It may also run automatically during initialization and history changes when autoPageView is enabled.

identify(user?: User | null) changes identity state. A user can contain id, email, name, and traits. Passing a user associates future events with that user. Passing null clears the signed-in identity and returns future events to anonymous mode. identify returns void.

shutdown() flushes the queue and stops SDK timers or listeners. It returns Promise<void>. Calls after shutdown are ignored.

The built-in event names shown are page_view, click, form_submit, signup, login, and purchase. A custom event may also use any valid string name.

2. Create and queue an event

A user action or page lifecycle event starts the flow. Examples include clicks, form submissions, page loads, and route changes.

The web app calls a public SDK method. The SDK validates and sanitizes the input before continuing. It then creates the internal event data.

Calls before init are queued instead of rejected. The pre-init queue is limited to 100 calls. Additional calls are dropped with a warning after the queue reaches that limit.

The SDK owns the event timestamp. It records client time as milliseconds since the Unix epoch. The diagram treats this client timestamp as authoritative for event ordering. A server may also have receive time, but that does not replace the client timestamp in this contract.

3. Apply anonymous and signed-in identity

The visitor starts anonymously. The diagram shows an anonymous device identifier stored through a first-party cookie or browser storage.

When the application calls identify with a user, future events become associated with that signed-in user. The example identifies user_42.

The SDK may preserve the anonymous identifier as a correlation value. This lets the event record retain the anonymous history while also showing the current signed-in user.

When the application calls identify(null), the signed-in identity is cleared. Later events return to anonymous mode.

This identity state belongs inside the SDK. Application code should not rebuild it separately for every track call.

4. Keep transport, batching, and persistence private

Queueing, batching, and persistence are hidden behind the SDK boundary. The public consumer does not control those details.

The diagram shows an internal queue, validation and sanitization, timestamp ownership, identity state, batching, and persistence using memory plus localStorage.

The SDK sends a JSON batch over HTTPS to one remote analytics API. The request crosses the external boundary with the JSON batch and headers.

An AbortController can provide an abort signal for network work that should stop. This cancellation behavior remains inside the SDK rather than changing the public analytics methods.

Keeping these details private allows the SDK implementation to change later without breaking application code.

5. Validate the remote response

The remote analytics API returns a JSON response containing status and results.

The SDK first checks the response status and Content-Type. It then parses the JSON. After parsing, it performs schema validation before trusting the result.

The response handling shown in the diagram keeps several outcomes separate: success, empty, partial, retryable error, authentication error, and fatal error.

These are transport-side states inside the SDK. The website still talks to the small public method contract instead of parsing the analytics transport response itself.

6. Define invalid-input and lifecycle behavior

Invalid API usage should not crash the host application. For example, a non-string event name is invalid. In development or debug behavior, the SDK reports a console warning and drops that event.

An oversized payload may be truncated or dropped with a warning. The diagram states that SDK failures are reported through debug logs instead of being thrown into the host application.

The SDK is a singleton per browser window. Multiple instance requests return the same singleton. This prevents separate queues, configurations, or identity states inside one window.

Repeated init calls update configuration and continue. They do not create another independent instance.

shutdown() flushes pending work, stops timers or listeners, and then completes its Promise. Calls after shutdown are ignored.

7. Protect privacy and the browser boundary

Consumers can opt out through the SDK configuration. When optOut is true, analytics data is not collected or sent. The diagram also allows this state to be changed later with another init call such as init({ optOut: true }).

The SDK never automatically collects page content, form field values, text input, email addresses, exact keystrokes, file contents, precise location, webcam data, microphone data, or third-party cookies.

This does not prevent an application from explicitly passing supported data, such as the email shown in the identify example. The privacy rule is about automatic collection.

The network transport uses HTTPS. The remote analytics API is an external boundary. The diagram also shows CORS enabled at that boundary. CORS controls whether browser JavaScript may read a cross-origin response. It is not authentication.

8. Produce the example event record

A short sequence is:

  1. analytics.init({ apiKey: "pk_live_123", appVersion: "1.2.0" }).
  2. analytics.page({ path: "/home", title: "Home" }).
  3. analytics.track("signup_click", { method: "google" }).
  4. analytics.identify({ id: "user_42", email: "a@example.com", traits: { plan: "pro" } }).
  5. analytics.track("purchase", { value: 49.99, currency: "USD", item_id: "sku_123" }).

The finalized purchase record contains event set to purchase. Its properties contain value 49.99, currency USD, and item_id sku_123. Its user object contains id user_42, the supplied email, and plan trait. Its context contains the current page information and appVersion 1.2.0. The SDK adds the client-owned timestamp. It also preserves anonymous_id as the anonymous correlation identifier that existed before sign-in.

The main design idea is separation. Application code understands the stable public methods and event contract. Validation, queueing, identity state, timestamps, batching, persistence, HTTPS transport, cancellation, response parsing, and response classification remain behind the SDK boundary.

Practical Complexity & Trade-offs

The public API is small, but the SDK hides useful internal work. Validation takes time based on the event size. Queueing and batching use memory based on the number and size of waiting events. The pre-init queue is capped at 100 calls, so it cannot grow forever. Client timestamps are easy to create, but browser clocks can be wrong. A singleton keeps configuration and identity simple, but it prevents independent SDK state inside one window. Memory and localStorage persistence can reduce event loss, but they use device storage. HTTPS protects data while it travels. CORS controls browser access to cross-origin responses, but it does not authenticate a user. Opt-out must stop both collection and sending. The main trade-off is a simple consumer API versus more hidden logic inside the SDK.

Why Interviewers Ask This

The interviewer is testing whether I can design a small and predictable browser API while hiding implementation details. They want clear method contracts, valid data types, lifecycle rules, identity transitions, timestamp ownership, privacy choices, validation, and failure behavior. They also want to see whether I understand browser boundaries such as HTTPS and CORS. The key skill is choosing responsibilities carefully and explaining trade-offs without exposing queueing, batching, persistence, or transport complexity to SDK consumers.

Interviewer may ask next
What would you change if the browser goes offline or the remote analytics API is temporarily unavailable?

I would keep the public API unchanged and change only the hidden queue and transport behavior. track(), page(), and identify() would still validate input, create event data, add identity, and add the client timestamp normally. The affected components are the internal queue, memory or localStorage persistence, batching logic, and remote analytics API flow. The diagram already shows an offline queue and a retryable-error result, so temporarily unsent events can remain inside the existing SDK boundary. The queue must still be bounded so offline use cannot consume unlimited memory or storage. Opt-out must also take priority, so the SDK must not keep collecting new analytics data while opt-out is active. HTTPS, response validation, identity rules, and the public method signatures stay unchanged. The main downside is extra internal complexity. Persisted events can become old, consume storage, or risk duplicate delivery. The SDK therefore needs careful internal bookkeeping while keeping those transport details invisible to the application.

How should the SDK handle a user who signs in and later signs out in the same browser window?

I would change only the active identity state and keep the rest of the SDK contract unchanged. Before sign-in, events use the anonymous identifier shown in the diagram. When the application calls identify({ id: "user_42", ... }), future events become associated with user_42. The SDK may keep anonymous_id as the preserved correlation identifier so earlier anonymous activity can still be related to the later signed-in session. When the user signs out, the application calls identify(null). The SDK immediately clears the signed-in identity, and future events return to anonymous mode. The affected component is the Identity State inside the SDK. Validation, timestamps, queueing, batching, persistence, HTTPS transport, response handling, shutdown, and opt-out behavior do not change. The SDK must not automatically copy sensitive user data into unrelated events. The main downside is identity complexity. A bad transition could attribute one person's later activity to another person, so clearing signed-in state must be immediate and deterministic.

15. Design the public component API for a single-value slider.API DesignEasyGoogle

Question Details

Design a reusable slider contract that can be implemented in plain JavaScript or adapted to a UI framework. Define min, max, step, value, optional initial value, orientation, disabled state, accessible label, value formatter, and onChange versus onCommit callbacks. State controlled and uncontrolled behavior, validation of a value that is not aligned to a step, keyboard and pointer event semantics, and what happens when bounds change while the current value becomes invalid. Include one prop or configuration example and the exact callback sequence for a drag that crosses several intermediate values and ends at a committed value.

Short Interview Answer (30-60 seconds)

I would expose a small, framework-neutral slider contract. The main inputs are min, max, step, value or defaultValue, orientation, disabled, label, valueFormatter, onChange, and onCommit. In controlled mode, the parent owns value. In uncontrolled mode, the slider owns its value after defaultValue. I clamp values to the bounds and align them to the step. Pointer and keyboard changes fire onChange as the value changes. A completed interaction fires onCommit. I would also make the thumb keyboard accessible and expose the correct slider ARIA values.

Detailed Explanation

This question asks us to define a simple contract for one slider value. The API must say which numbers are allowed. It must say who owns the current value. It must also define pointer input, keyboard input, callbacks, disabled behavior, and accessibility. We also need a clear rule for invalid values. The same rule should apply when the bounds or step change later. Finally, we need one concrete configuration and one exact drag example so callers can understand when onChange and onCommit run.

Useful Questions to Ask the Interviewer
  • Should Page Up and Page Down move by ten steps, as shown in the approved design?
  • Should pointer leave followed by pointer up commit the interaction?
  • When a prop change corrects the current value, should that correction fire onChange?
  • Should the accessible label be a string only, or should a visible label relationship also be supported?
Design the public component API for a single-value slider. diagram
How to Explain It in an Interview
1. Define the public component contract

I would keep the public API small and explicit.

  • min: number is the inclusive lower bound.
  • max: number is the inclusive upper bound.
  • step: number is the interval between valid values. It must be greater than zero.
  • value?: number is the current value in controlled mode.
  • defaultValue?: number is the optional initial value in uncontrolled mode.
  • orientation?: 'horizontal' | 'vertical' selects the slider direction. The approved design uses horizontal by default.
  • disabled?: boolean makes the slider non-interactive.
  • label?: string provides an accessible label.
  • valueFormatter?: (value: number) => string creates display text such as 40%.
  • onChange?: (value: number, ctx: ChangeContext) => void reports value changes during interaction.
  • onCommit?: (value: number) => void reports the final committed value.

The approved design also shows a ChangeContext. Its reason can be pointer, keyboard, or programmatic. It also has committing: boolean, which tells the caller whether a commit will follow.

A matching configuration example is:

const slider = new Slider({ min: 0, max: 100, step: 5, defaultValue: 20, orientation: 'horizontal', label: 'Volume', valueFormatter: v => ${v}%, onChange: (v, ctx) => console.log('change', v, ctx), onCommit: v => console.log('commit', v) });

2. Define controlled and uncontrolled behavior

In controlled mode, the caller passes value. The caller owns the source of truth. The slider reports a proposed change through onChange. The caller then decides which value to pass back.

In uncontrolled mode, the caller omits value. It may provide defaultValue as the initial value. After initialization, the slider keeps its own current value.

The approved design does not support switching between controlled and uncontrolled modes. This keeps state ownership clear.

3. Normalize and validate values

The slider first checks the basic rules. step must be greater than zero. min must be less than or equal to max.

A value is first clamped to [min, max]. It is then aligned to the step using the approved rule:

aligned = round((clamp(value, min, max) - min) / step) * step + min

For example, with min = 0, max = 100, and step = 5, an input value of 42 aligns to 40.

The same normalization rule should be used for initial values, interaction values, and later prop changes. That avoids different parts of the component disagreeing about which values are valid.

4. Define pointer and keyboard semantics

For pointer input, pointer down on the thumb starts the interaction. Dragging updates the proposed value. Each new effective value fires onChange. Pointer up commits the interaction. The approved design also treats pointer leave followed by pointer up as a commit.

For keyboard input, Right Arrow and Up Arrow add one step. Left Arrow and Down Arrow subtract one step. Home moves to min. End moves to max. Page Up adds ten steps. Page Down subtracts ten steps. Enter or Space commits the current value.

When disabled is true, the slider does not respond to these interactions and does not fire interaction callbacks.

5. Explain onChange versus onCommit

onChange is for live value changes. It can run several times during one drag. onCommit is for the final value when the interaction finishes.

The approved diagram shows this drag sequence starting from 10 with step = 5:

1. Pointer down starts at 10. No callback is shown. 2. The drag reaches 15. Fire onChange(15). 3. The drag reaches 20. Fire onChange(20). 4. The drag reaches 25. Fire onChange(25). 5. The drag reaches 30. Fire onChange(30). 6. The drag reaches 35. Fire onChange(35). 7. The drag reaches 40. Fire onChange(40). 8. The diagram then labels pointer release as 42 and shows onChange(42) followed by onCommit(42).

There is a conflict inside the approved diagram. Its validation rule says 42 with step = 5 becomes 40, but its final drag row uses 42 as the component value. A technically consistent implementation should apply the stated normalization rule before callbacks. That means a raw pointer position corresponding to 42 would keep the effective value at 40, would not fire another onChange if 40 was already reported, and would commit with onCommit(40). I would confirm this correction with the interviewer before implementation because the two visible diagram rules cannot both be true.

6. Handle bounds and step changes

When min, max, or step changes, the component re-validates the current value.

First, clamp the value to the new [min, max] range. Then align it to the new step. If the effective value does not change, do nothing. If it changes, update to the nearest valid value and report the programmatic change through onChange, as shown in the approved design.

If an interaction is active and then reaches a real commit point, such as pointer up, onCommit reports the final effective value.

Controlled mode keeps the same ownership rule. The component reports the corrected value, but the parent still owns the value prop. Uncontrolled mode can update its internal state directly.

7. Keep the slider accessible

The focusable thumb uses role="slider". It exposes aria-valuemin, aria-valuemax, and aria-valuenow. It also exposes aria-orientation and aria-disabled when relevant. The label supplies the accessible name.

The thumb must be reachable by Tab. Focus must remain visible. Keyboard users must be able to perform the same important value changes as pointer users. Screen readers should receive the current value as it changes.

The value formatter controls friendly display text. It does not change the underlying numeric value or validation rules.

8. Verify the component contract

I would test controlled and uncontrolled behavior separately. I would test both bounds and several step sizes. I would test a non-step value such as 42 with step = 5. I would test every keyboard action shown in the design. I would test a drag across several values and verify the exact callback order. I would test disabled behavior and bounds changes. I would also verify focus behavior, role="slider", the ARIA value attributes, orientation, disabled state, and the accessible label.

Practical Complexity & Trade-offs

Each slider update is simple constant-time work. The component clamps one number, aligns it to the step, updates the displayed position, and possibly calls a callback. The main trade-off is API clarity versus flexibility. Controlled mode gives the parent full control, but it requires the parent to update value correctly. Uncontrolled mode is easier for simple uses, but the component owns more state. A value formatter improves display flexibility without changing numeric rules. Accessibility adds some implementation and testing work, but it is required for a reusable slider. The most important reliability rule is using one normalization rule everywhere so pointer, keyboard, initialization, and prop changes cannot disagree.

Why Interviewers Ask This

Interviewers use this question to test whether I can turn UI behavior into a clear public contract. They want to see precise state ownership, validation rules, and callback semantics. They also check whether pointer and keyboard behavior stay consistent and whether accessibility is part of the design from the start. The important skill is engineering judgment: choosing simple rules, defining edge cases, and noticing conflicts before they become bugs for component users.

Interviewer may ask next
What should happen if min, max, or step changes and the current slider value becomes invalid?

I would re-run the same normalization rule used everywhere else. The affected flow is the slider's prop-change path. First, clamp the current value to the new [min, max] range. Then align it to the new step. If the effective value is unchanged, the component does nothing. If it changes, the approved design reports the adjusted value through onChange with a programmatic reason. In controlled mode, the parent still owns value, so the slider reports the corrected value and waits for the parent to pass a new value back. In uncontrolled mode, the slider can update its own internal value. Accessibility stays correct because aria-valuemin, aria-valuemax, and aria-valuenow are updated to match the effective state. The main downside is that automatic correction may surprise callers. The normalization rule must therefore be documented clearly and used consistently for initialization, user interaction, and later prop changes.

How would you make the same slider work correctly for keyboard and screen-reader users?

I would keep the same value model and add complete keyboard and slider accessibility semantics to the focusable thumb. Right Arrow and Up Arrow add one step. Left Arrow and Down Arrow subtract one step. Home moves to min, and End moves to max. Page Up adds ten steps, while Page Down subtracts ten steps. The approved design uses Enter or Space as a commit action. Every effective value change follows the same clamp and step-alignment rule and fires onChange. A commit action fires onCommit with the final effective value. The thumb uses role="slider" with aria-valuemin, aria-valuemax, aria-valuenow, aria-orientation, aria-disabled, and an accessible label. It is reachable by Tab, and focus remains visible. A formatter can create friendly display text such as 40% without changing the numeric value. The downside is more interaction and accessibility testing, but the public value contract stays unchanged.

16. Design batching and flush contracts for a browser analytics SDK.API DesignMediumGoogle

Question Details

Extend the reported analytics SDK with a transport contract. Define batch envelope version, event IDs, maximum events and bytes, compression indication, send reason, acknowledgement, partial rejection, retryable versus permanent errors, Retry-After, and a flush({reason, deadlineMs}) result. Specify behavior on page hide, offline transition, consent withdrawal, duplicated batches, an uncertain network outcome, and an event too large to send. Include an exact batch request and response in which one event is accepted and one is permanently rejected. The client contract must bound retries, preserve privacy choices, and remain testable with a deterministic clock and transport adapter.

Short Interview Answer (30-60 seconds)

I would batch validated analytics events inside the browser SDK and send each batch with browser fetch over HTTPS to one external analytics API. Every event gets a UUID, and every batch has envelope version 1.0, a batchId, compression metadata, size limits, and a send reason. AbortController and deadlineMs bound each flush. The response can accept some events and permanently reject others. Retryable failures use bounded backoff and Retry-After when present. Privacy always wins, so events blocked by current consent are never sent. The trade-off is fewer requests versus more memory and delivery delay.

Detailed Explanation

The browser analytics SDK collects events and sends them in batches. This reduces network requests. The client still needs a clear result for every event. It must handle page hiding, offline periods, consent changes, duplicate batches, and uncertain network failures. The design uses one batch request and one acknowledgement response. Retries stop after five attempts or when the flush deadline expires. Privacy choices always override delivery. The remote analytics API remains one external boundary. The browser owns validation, batching, request cancellation, retry decisions, and local queue state.

Useful Questions to Ask the Interviewer
  • Should queued events survive a browser restart, or only the current page session?
  • What retention period should the client use for its batch deduplication cache?
  • Which event fields must be treated as disallowed PII under the consent policy?
  • Which supported browsers must provide the page-hide keepalive behavior?
Design batching and flush contracts for a browser analytics SDK. diagram
How to Explain It in an Interview
1. Define the browser contract

The SDK receives user actions, page lifecycle events, network changes, privacy changes, and manual flush() calls.

The first step is Ingest & Validate. The client enriches each event with values such as timestamp and page information. It validates the event shape. It checks the current consent state. It also rejects an event that exceeds the maximum event size.

The next step is Queue & Batch. Each accepted event receives an eventId using a UUID. The client groups events using configured limits. The diagram shows example limits of 50 events, 256 KB per batch, and 32 KB per event.

Before sending, the SDK builds envelope version 1.0. It serializes the batch as JSON and marks compression as gzip. It also selects the send reason.

The supported send reasons are explicit_flush, page_hide, offline, interval, queue_full, and consent_withdrawn.

The browser uses fetch() with AbortController. The request crosses the browser security boundary and goes over HTTPS to POST /v1/batch.

The exact request shown in the diagram is:

{ "envelope": { "version": "1.0", "batchId": "b9f9c1e4-2c0a-4d8e-8e7d-9a1f3c7b6e11", "sentAt": "2025-05-21T12:34:56.789Z", "sdk": { "name": "web", "version": "2.4.1" }, "page": { "url": "https://example.com/page", "referrer": "https://google.com" }, "consent": { "ad_storage": false, "analytics_storage": true }, "compression": { "format": "gzip" }, "eventCount": 2, "eventBytes": 512, "maxEventBytes": 32768, "sendReason": "explicit_flush", "deadlineMs": 5000 }, "events": [ { "eventId": "e1a8b6f2-3c0d-4b11-8e6a-7f2a9d0c1234", "name": "checkout_start", "ts": "2025-05-21T12:34:56.123Z", "props": { "value": 129.99, "currency": "USD" } }, { "eventId": "e2b9c7d3-4d1e-4c22-9f1b-8a3d2e1f5678", "name": "pii_leak_attempt", "ts": "2025-05-21T12:34:56.456Z", "props": { "email": "user@example.com" } } ] }

2. Start and control the flush

The SDK exposes flush({reason, deadlineMs}).

A flush groups eligible queued events into a batch. It then starts the network attempt through the transport adapter.

The Send with AbortController component owns cancellation. A new flush can supersede an older one. Page-hide work can use a short timeout. Consent withdrawal can also stop work that is no longer allowed.

Each network attempt has a timeout. The full operation must also respect deadlineMs.

The client keeps queued events, in-flight batches, retry schedule state, the current consent snapshot, a deduplication cache, and last-successful-send information.

The returned Promise<FlushResult> contains:

  • ok: whether all events were accepted.
  • sent: the accepted event count.
  • rejected: the permanent rejection count.
  • retrying: the number of events still scheduled for retry.
  • reason: the original flush reason.
  • nextAttemptAt: the next retry time as an epoch-millisecond number when retrying.
3. Validate and process the response

The external analytics API returns a JSON acknowledgement to the browser client.

When an HTTP response arrives, fetch() normally resolves even when the status is not successful. The client therefore checks the supported response status before trusting the body. It then parses the JSON and validates the expected response schema before changing queue state.

The exact response shown in the diagram is a 200 OK partial acknowledgement:

{ "envelope": { "version": "1.0", "batchId": "b9f9c1e4-2c0a-4d8e-8e7d-9a1f3c7b6e11", "receivedAt": "2025-05-21T12:34:56.999Z", "status": "partial", "acceptedCount": 1, "rejectedCount": 1 }, "results": [ { "eventId": "e1a8b6f2-3c0d-4b11-8e6a-7f2a9d0c1234", "status": "accepted" }, { "eventId": "e2b9c7d3-4d1e-4c22-9f1b-8a3d2e1f5678", "status": "rejected", "error": { "code": "privacy_violation", "message": "Event contains disallowed PII", "retryable": false } } ], "retry": { "retryable": false, "retryAfterMs": 0 } }

One event is accepted. One event is permanently rejected.

The accepted event leaves the queue. The rejected event also leaves the queue because retryable is false. A retryable rejection would instead stay eligible for the bounded retry path.

4. Handle retries and special cases

Retryable failures use exponential backoff with jitter. The client allows at most five attempts. Total retry time is also bounded by deadlineMs.

If the response provides the HTTP Retry-After value, the client waits for it before another attempt. The value may be expressed as seconds or an HTTP date.

An uncertain network outcome needs special care. The browser may lose the acknowledgement after the server received the request. The client therefore retries the same batchId instead of creating a new identity. The server can deduplicate the repeated batch by that batchId.

The uncertain-outcome path continues only until deadlineMs or maxAttempts. If another attempt remains scheduled, FlushResult exposes that through retrying and nextAttemptAt.

On page hide, the SDK triggers flush({reason: "page_hide", deadlineMs: 2000}). It performs a best-effort send using keepalive behavior and remains bounded by the short deadline.

On an offline transition, the SDK keeps events queued and makes no send attempts. When connectivity returns, it flushes again. The associated send reason is offline.

On consent withdrawal, the SDK removes queued events that are no longer allowed. It does not send them. The reason is consent_withdrawn. An affected in-flight request may also be aborted by the client.

For a duplicate batch, the server deduplicates by batchId. The client also keeps a deduplication cache for a limited time to avoid unnecessary re-sending.

If one event exceeds the configured maximum event size, the SDK drops that event and records a client error. It continues processing other valid events.

5. Protect the browser boundary

The transport uses HTTPS only. HTTPS protects event data while it crosses the network.

CORS is enabled at the browser boundary. CORS controls whether browser JavaScript may read a cross-origin response. It is not authentication.

The design does not place credentials inside the analytics request body.

Privacy is checked before every send. The current consent state is attached to the batch and also controls which queued events remain eligible.

This matters during retries. An event may have been allowed when collected but become disallowed later. The newest consent choice wins, so that event is purged instead of retried.

6. Verify the behavior

The SDK uses a deterministic clock for tests. A deterministic clock means tests control time instead of waiting for real time.

The SDK also uses a transport adapter. Tests can make that adapter return accepted, partial, retryable, permanent, timeout, or network-failure outcomes without a real network.

Unit tests should verify event IDs, batch limits, envelope version, gzip metadata, send reasons, and oversized-event handling.

Retry tests should advance the deterministic clock. They should verify exponential backoff, jitter behavior, Retry-After, the five-attempt limit, and deadlineMs.

Privacy tests should queue an event, withdraw consent, and confirm that the event never crosses the transport boundary.

Partial-response tests should use the exact two-event request and response. They should verify that the accepted event is removed and the permanent rejection is never retried.

Uncertain-outcome tests should resend the same batchId. They should verify that retries stop when either the attempt limit or deadline is reached.

Practical Complexity & Trade-offs

Batching reduces request count, but events may wait longer before delivery. Larger batches use more browser memory. Smaller batches send sooner but create more requests. Compression reduces network bytes but adds browser CPU work. Retry logic improves delivery, but uncertain outcomes can repeat requests. Reusing the same batchId makes those retries safe with server deduplication. Retries stop after five attempts or when deadlineMs expires. Retry-After prevents the browser from retrying too early. AbortController stops obsolete work. Privacy can intentionally discard queued events, which reduces analytics completeness. A deterministic clock and transport adapter add some design work, but they make timing and failure behavior much easier to test.

Why Interviewers Ask This

This question tests whether a candidate can design a reliable browser-to-API contract instead of only calling fetch. The interviewer is looking for clear batch modeling, partial acknowledgements, bounded retries, idempotency, lifecycle handling, and privacy judgment. It also checks whether the candidate understands uncertain network outcomes and permanent versus retryable failures. A strong answer keeps client responsibilities clear, protects consent choices, and makes time-dependent behavior deterministic enough to test.

Interviewer may ask next
What changes if the network often drops after the server receives a batch but before the browser receives the acknowledgement?

I would keep POST /v1/batch and retry the same batchId. The affected flow is Send with AbortController through the external analytics API and back to Handle Response. A network failure does not prove that the server missed the request. The first request may already have succeeded. Creating a new batch identity could therefore duplicate analytics events. The client keeps the in-flight batch and retries that same identity with exponential backoff and jitter. The server deduplicates repeated deliveries using batchId. The retry still stops after five attempts or when deadlineMs expires. If Retry-After is present, the client waits for it. Before another send, the browser checks current consent again. Events no longer allowed by privacy choices are not retried. The downside is that the client must keep in-flight state longer, and the flush may take longer to finish. The batch envelope, response contract, partial rejection handling, and transport boundary remain unchanged.

What changes if the user withdraws analytics consent while a batch is queued or retrying?

I would make the newest consent state override delivery. The affected components are the consent snapshot in SDK State, Queue & Batch, Send with AbortController, and the retry path. Queued events that are no longer allowed are removed immediately. They are not sent later just because they were valid when first collected. Before every retry crosses the HTTPS boundary, the SDK checks consent again. Consent withdrawal can also abort affected in-flight work through AbortController. The SDK uses consent_withdrawn as the reason for this flow. An acknowledgement that already arrived is still processed normally, but disallowed unsent events are purged instead of retried. This keeps privacy correct during offline periods and retry backoff. The downside is intentional analytics loss because privacy is more important than delivery completeness. The endpoint, batchId deduplication, maximum retry count, deadline handling, partial acknowledgement contract, and deterministic test setup all stay unchanged.

17. Design an order-preserving card-reorder mutation API.API DesignHardGoogle

Question Details

Design the mutation contract for moving a card within or between project-board columns without rewriting every later card. Define stable card and column IDs, the ordering token or neighbor references supplied by the client, board revision, idempotency key, authorization context, success result, and conflict response. Specify insert into an empty column, move to the beginning or end, missing neighbors, concurrent deletion, duplicate request delivery, and ordering-token exhaustion or compaction. Include one exact move request and response. The browser must be able to apply the move optimistically, match the acknowledgement, and recover from conflict without guessing the authoritative order.

Short Interview Answer (30-60 seconds)

I would use one idempotent reorder mutation. The browser validates stable card and column IDs, the current boardRevision, neighbor references, an idempotencyKey, and a clientRequestId. It sends POST /v1/cards/{cardId}/reorder over HTTPS and moves the card optimistically. AbortController can cancel obsolete browser work, while clientRequestId prevents stale responses from updating newer UI state. A successful response returns the new boardRevision and authoritative neighbors. A 409 conflict makes the browser roll back or reconcile from authoritative order instead of guessing. The trusted server still owns authorization.

Detailed Explanation

The goal is to move one card without rewriting every later card. The browser sends stable IDs and the card's intended neighbor position. It also sends the board revision it last saw. This lets the API detect an outdated move. An idempotency key makes duplicate delivery safe. The browser can move the card immediately so the UI feels fast. When the response arrives, it either confirms that position or tells the browser that its view was stale. On conflict, the browser must use authoritative order and never invent the final position.

Useful Questions to Ask the Interviewer
  • Should neighbor references be the public ordering contract? The approved design uses prevCardId and nextCardId as stable neighbor references.
  • Is boardRevision required on every reorder? The approved design requires the client to send its latest known revision.
  • Can the same idempotencyKey be replayed after uncertain delivery? The approved design says duplicate delivery with the same key returns the same result.
  • Which supported authentication context should the browser use: a bearer token or an HttpOnly cookie?
  • When a 409 conflict occurs, how is the authoritative order supplied to the browser for reconciliation?
Design an order-preserving card-reorder mutation API. diagram
How to Explain It in an Interview
1. Define the browser contract

The user drags a card within a column or between columns. The browser keeps cardId and columnId stable. It does not send a complete rewritten column.

The request goes over HTTPS to POST /v1/cards/{cardId}/reorder. The JSON body carries the card being moved, source and destination information, the expected boardRevision, an idempotencyKey, and a clientRequestId.

The exact move request shown in the approved diagram is:

{"cardId":"card_123","from":{"columnId":"col_A","prevCardId":"card_88"},"to":{"columnId":"col_B","prevCardId":"card_45"},"boardRevision":1287,"idempotencyKey":"550e8400-e29b-41d4-a716-446655440000","clientRequestId":"req_01J2Y0X3A8ZQ"}

The ordering model uses stable neighbor references. A card can be described relative to prevCardId and nextCardId. This avoids rewriting every later card.

For an empty destination column, to.prevCardId is null. Moving to the beginning also uses to.prevCardId as null. Moving to the end uses the current tail card ID as to.prevCardId.

The browser also sends supported authentication context. The diagram shows an Authorization bearer token as one option. It also allows an HttpOnly cookie approach.

2. Start and control the request

The browser validates IDs, boardRevision, neighbor references, and request metadata before sending anything. It then builds the JSON body.

The UI can move the card optimistically. This means the user sees the new position immediately.

The browser creates an AbortController for obsolete work. If the user moves again or navigates away, the earlier fetch can be aborted. This saves browser work, but aborting does not prove that remote processing stopped.

The browser also tracks clientRequestId. A late response for an older request must not overwrite a newer card position.

The request is sent with fetch(). Fetch is Promise-based asynchronous browser work. It is not a background thread.

3. Validate the response

When an HTTP response arrives, the browser first checks the status and content type. Fetch normally resolves when an HTTP response arrives, including many HTTP error statuses. Therefore, the browser must inspect the response before treating it as success.

For a successful JSON response, the browser parses the body and validates its expected shape before changing UI state.

The exact 200 OK response shown in the diagram is:

{"movedCard":"card_123","from":{"columnId":"col_A","prevCardId":"card_88"},"to":{"columnId":"col_B","prevCardId":"card_45"},"boardRevision":1288,"position":{"columnId":"col_B","prevCardId":"card_45","nextCardId":"card_78"},"clientRequestId":"req_01J2Y0X3A8ZQ"}

The important values are the new boardRevision and the authoritative position. The response gives prevCardId and nextCardId for the moved card. The echoed clientRequestId lets the browser match the acknowledgement to the request that caused it.

4. Handle success and failure

On success, the browser confirms the optimistic move using the authoritative response.

A 409 conflict means the client's assumptions are stale or invalid. The approved diagram names REVISION_MISMATCH, CARD_NOT_FOUND, COLUMN_NOT_FOUND, and NEIGHBOR_MISMATCH as conflict cases.

REVISION_MISMATCH means boardRevision is stale. NEIGHBOR_MISMATCH means the supplied neighbor references no longer match current order. CARD_NOT_FOUND can represent a concurrently deleted card. COLUMN_NOT_FOUND means the destination column no longer exists.

The browser must not guess after a conflict. It rolls back or reconciles using authoritative order from the supported recovery path. The latest boardRevision must be used before another move is attempted.

Duplicate request delivery is safe because the same logical mutation reuses the same idempotencyKey. Replaying the same key is a no-op and returns the same result instead of applying the move twice.

The diagram also defines 400 for invalid input, 401 for missing authentication, 403 for insufficient permission, 429 for too many requests, and 5xx for server errors. A 429 should be retried later. A 5xx may be retried with backoff. For the same logical mutation, retries must keep the same idempotencyKey.

Ordering-token exhaustion or compaction is hidden from the browser. The server may rebalance its internal ordering representation. The client contract stays stable because it continues using card IDs, column IDs, neighbor references, and the latest boardRevision.

5. Protect the browser boundary

The request uses HTTPS. CORS allows only trusted origins to read supported cross-origin responses. CORS is not authentication.

The same-origin policy limits cross-origin browser access. If cookies are sent automatically, CSRF protection is needed. The diagram shows SameSite cookie protection or a CSRF token header for state-changing requests.

If bearer tokens are used, they should not be stored in localStorage. The diagram prefers HttpOnly cookies or in-memory storage because that reduces token exposure.

Authentication and authorization are different. Authentication identifies the user. Authorization decides whether that user may move the card. The trusted server must enforce authorization for the board or column. Hiding a browser control is not a security boundary.

6. Verify the behavior

I would test request construction first. Tests should cover stable IDs, boardRevision, neighbor references, idempotencyKey, and clientRequestId.

I would test empty-column insertion, moving to the beginning, and moving to the end. I would also test missing neighbors and concurrent deletion.

Component tests should verify optimistic movement, successful confirmation, rollback, and conflict reconciliation.

Network tests should cover 200, 400, 401, 403, 409, 429, and 5xx responses. They should also cover malformed JSON, wrong content type, aborts, and network failure.

I would deliver the same mutation twice with the same idempotencyKey. The card must move only once, and the duplicate must return the same result.

Finally, I would test stale responses. If a newer drag happens first, an older response with an earlier clientRequestId must not replace the newer UI state.

Practical Complexity & Trade-offs

The request stays small because one move sends IDs, neighbor references, one boardRevision, and request metadata instead of a whole reordered column. Browser request building and response parsing are constant-size work for one mutation. Optimistic UI makes the board feel fast, but it adds rollback and reconciliation logic. boardRevision protects against lost updates. idempotencyKey protects against duplicate delivery. clientRequestId protects the UI from stale acknowledgements. The main trade-off is conflict handling. Another user can change the board before this request finishes. The browser must then accept the authoritative order instead of guessing. AbortController reduces obsolete browser work, but it cannot guarantee that remote processing stopped. Security also has trade-offs. HttpOnly cookies reduce JavaScript token exposure but need CSRF protection when sent automatically. Bearer tokens avoid automatic cookie sending but must be protected from script exposure. Internal ordering compaction can happen without changing the browser contract.

Why Interviewers Ask This

Interviewers use this question to test API judgment under concurrent updates. They want to see clear request and response modeling, stable ordering references, revisions, idempotency, optimistic UI, and conflict recovery. They also check whether you understand Fetch behavior, stale-response protection, authentication, authorization, and browser security boundaries. A strong answer keeps user experience separate from correctness and explains trade-offs without inventing unnecessary server internals.

Interviewer may ask next
What changes when two users reorder cards at almost the same time?

I would keep the same endpoint and browser flow. Each client still sends POST /v1/cards/{cardId}/reorder with its known boardRevision, neighbor references, idempotencyKey, and clientRequestId. Suppose the first move succeeds. The board revision then changes. The second client's revision or neighbor references may now be stale, so that request can receive 409 Conflict. The affected browser must not guess a merged order. It should roll back or reconcile from the authoritative order available through the supported conflict-recovery path, then use the latest boardRevision for any next move. Optimistic UI can remain, so each user still sees immediate feedback. Authorization, HTTPS, CORS, and CSRF behavior do not change. Idempotency also stays unchanged because it protects duplicate delivery of one logical move, not two different users making competing moves. The main downside is visible reconciliation. A card can briefly appear in the optimistic position and then move to the authoritative position after a conflict.

How should the browser retry after a network failure without moving the card twice?

I would retry the same logical mutation with the same idempotencyKey. The endpoint remains POST /v1/cards/{cardId}/reorder. The request keeps the same logical move data and the same idempotency key. A network failure is uncertain because the browser may not know whether the remote API already processed the first delivery. Sending the retry with a new idempotency key could allow the same logical action to be applied again. Reusing the original key lets duplicate delivery return the same result as the first request. The browser still uses clientRequestId and stale-response checks so an old acknowledgement cannot overwrite a newer drag. AbortController remains useful for obsolete browser work, but it is not the duplicate-safety mechanism. Authorization and browser security boundaries remain unchanged. Retry behavior follows the approved error contract: 429 waits and retries later, while 5xx may use backoff. The main downside is extra client state. The browser must keep the original mutation details and idempotency key until the final outcome is known.

18. Design the frontend architecture of a browser-based code playground.System DesignEasyGoogle

Question Details

Design the reported browser code playground so a user can edit HTML, CSS, and JavaScript and see a result without leaving the page. Define editor, preview, and persistence boundaries; how code reaches an isolated execution environment; how console output, runtime errors, and infinite loops are surfaced; and how unsaved work survives navigation. Cover loading, empty, running, success, and failure states, keyboard use, accessibility of the preview controls, responsive layout, and browser compatibility. Explain which work belongs on the main thread, what must be sandboxed, and how a minimal first version can later add sharing and version history without coupling every editor pane to network logic.

Short Interview Answer (30-60 seconds)

At a high level, I would build the playground as a client-rendered single page application. The user edits HTML, CSS, and JavaScript, then runs that code inside a sandboxed iframe instead of the playground page itself. Shared client state holds open files and unsaved changes, while IndexedDB stores projects and snapshots. Console messages and errors return through postMessage. CDN caching and a service worker improve loading and offline use. The trade-off is extra complexity around isolation, persistence, and updates.

Detailed Explanation

The goal is to let a developer edit HTML, CSS, and JavaScript and immediately see the result on the same page. The hardest frontend problem is separating trusted playground code from untrusted user code. I would use a client-rendered single page application, or CSR app, because this tool is highly interactive and does not depend on SEO. The browser application owns routing, editors, layout, state, and persistence. A sandboxed iframe owns user-code execution. Browser storage protects unsaved work, while remote services stay behind separate external boundaries.

Useful Questions to Ask the Interviewer
  • Which browsers and device sizes must we support?
  • Must the playground work after the network goes offline?
  • Is sharing required in the first version or a later version?
  • Should users sign in before saving projects remotely?
  • What behavior is expected when user code never finishes?
Design the frontend architecture of a browser-based code playground. diagram
How to Explain It in an Interview
1. Build one client-side application shell

I would use a single page application with client-side rendering. React Router handles routes such as /, /play/:id, /explore, /settings, and /about.

The app shell owns the responsive layout. It contains resizable panes and accessibility controls. The main editing view contains HTML, CSS, and JavaScript editors, a preview, and a console panel.

The browser receives static JavaScript, CSS, fonts, and other assets through the CDN. Long-lived immutable assets use browser caching. A service worker can cache the application shell and important assets for later offline use.

2. Separate editing from execution

The editor panes handle editing work such as syntax highlighting, autocomplete, IntelliSense, and linting. Normal application UI work stays on the browser main thread.

Pressing Run sends the current HTML, CSS, and JavaScript to the runner using the execution path shown in the diagram. The runner lives inside a sandboxed iframe. Its sandbox allows scripts but does not grant same-origin access. It also blocks top-level navigation.

The runner injects the HTML and CSS and executes the JavaScript. The rendered result appears inside the preview iframe. This keeps user code behind a browser security boundary instead of executing directly inside the application page.

3. Return console output and failures safely

The iframe runner captures console output and runtime errors. It sends logs, errors, and execution status to the parent through postMessage. The Console and Runtime Panel then shows normal logs, warnings, errors, and stack information when available.

The diagram also includes an infinite-loop guard. A stuck execution should be treated as a failed run and the isolated preview should be restarted when possible. An iframe sandbox is a security boundary, not a guaranteed CPU-isolation mechanism, so the design should not claim that every synchronous infinite loop can always be interrupted cleanly.

4. Give each kind of state one owner

Ephemeral UI state stores the active tab, cursor, selection, pane sizes, and theme. URL state stores values such as project id, file path, and view mode. Shared client state stores open files, editor models, unsaved changes, and run status.

IndexedDB stores projects and automatic snapshots. localStorage keeps small UI preferences such as theme, layout, and font size. This local persistence lets unsaved work survive route changes and browser restarts.

The editor panes should not call remote services directly. Shared state and persistence code provide a stable boundary between editing and network features.

5. Handle normal and failure states clearly

The interface explicitly supports loading, empty, running, success, failure, and offline states. A loading state can show the application shell or lazy chunks. An empty state can offer a starter template. Running shows execution progress. Failure keeps the source code visible and sends runtime details to the console.

Keyboard users need predictable shortcuts and visible focus. Preview controls need accessible names. The layout must respond to smaller screens. Interface strings can use internationalization without changing user code.

6. Add remote features without redesigning the editors

The first version can work mainly with browser state and IndexedDB. Later, the Project API can support project CRUD, sharing, and history. The Auth Service remains a separate identity-provider boundary. Static templates or images can come from the external assets CDN.

Client error reporting, Web Vitals, telemetry, feature flags, gradual rollout, and rollback are cross-cutting concerns. They help measure problems and release changes safely without becoming part of the code-execution path.

Engineering Considerations / Design Trade-offs

The benefit is that editing remains fast because most work stays inside the browser. The sandboxed iframe also separates user code from the main application. The downside is that message passing and iframe lifecycle handling add complexity. IndexedDB protects local work, but syncing that work with a future Project API creates conflict cases. The service worker improves offline use, but cached versions must be updated carefully. Shared client state keeps editors independent from network logic, but too much shared state can become difficult to understand and maintain.

Why Interviewers Ask This

The interviewer wants to see whether you can divide a browser application into clear responsibilities. They are checking if you understand unsafe code execution, state ownership, persistence, failures, accessibility, caching, and offline behavior. They also want to see whether you can start with a small design and later add sharing and history without tightly coupling every editor to remote services.

Interviewer may ask next
How would you change this design if users must share projects and keep version history across devices?

I would keep the same editor, preview, sandbox, and shared-state boundaries. The main change would be adding remote synchronization through the Project API already shown in the diagram.

Editors would still update shared client state first. They would never call the Project API directly. The persistence layer would keep automatic snapshots in IndexedDB and send saved project changes to the Project API when the user is online. The Auth Service would provide the user's identity for remote operations.

After a successful save, the client can mark that version as synchronized. If the request fails, the IndexedDB copy remains available and the interface can show that remote synchronization is pending. Version history remains a Project API capability rather than becoming editor logic.

This preserves the existing architecture and keeps typing responsive on slow networks. The main downside is conflict handling. If two devices change the same project before synchronization finishes, the product needs a clear rule for choosing, merging, or presenting those versions.

What happens if the JavaScript entered by the user contains an infinite loop?

I would keep the application state outside the execution environment and treat the preview run as disposable. The affected parts are the sandboxed iframe, runner script, preview, and Console and Runtime Panel.

When Run starts, shared client state records that execution is running. The runner executes the user's code inside the sandbox and reports normal logs, errors, and status through postMessage. The diagram's infinite-loop guard should detect a run that does not complete and treat it as failed when the browser still allows the parent application to respond. The preview can then be recreated and the user's editor contents remain untouched.

I would also be clear about one limitation. A sandboxed iframe provides security isolation, but it does not guarantee a separate CPU thread or process. A fully synchronous endless loop may still hurt page responsiveness before recovery is possible.

The benefit is that source code and saved state remain separate from preview failures. The downside is that reliable hard CPU limits need stronger execution isolation than the diagram's basic iframe design provides.

19. Design a browser analytics SDK used by many webpages.System DesignEasyGoogle

Question Details

Design the reported webpage analytics SDK as a frontend library rather than a backend pipeline. Define initialization, event collection, page-view tracking, batching, transport choice, retry limits, page lifecycle handling, and cleanup. Cover consent and disabled states, multiple SDK instances, single-page navigation, offline periods, duplicate events, schema evolution, and observability for dropped data. Explain how the SDK avoids blocking interaction, leaking page data unintentionally, or keeping a page alive during unload. Include the public-module boundaries, configuration ownership, compatibility strategy, and test seams needed for several applications to adopt the same SDK safely.

Short Interview Answer (30-60 seconds)

At a high level, I would build a framework-neutral browser SDK that lets webpages collect analytics without slowing user interaction or leaking data. Each integration creates an isolated instance with its own validated configuration and consent state. Events pass through validation, enrichment, deduplication, batching, and browser storage. Active batches use fetch. Page-exit sends use sendBeacon or fetch with keepalive. Observable failures retry with limits. The trade-off is better resilience versus extra browser storage and lifecycle complexity.

Detailed Explanation

A browser analytics SDK must collect useful events without hurting the webpage that uses it. The main challenge is the browser lifecycle. Networks fail, consent changes, users navigate inside single-page applications, and JavaScript may stop quickly when a page closes. I would design this as a framework-neutral browser library rather than a backend pipeline. The host application supplies configuration. Each SDK instance keeps its own validated runtime state. Events then move through consent, validation, enrichment, batching, storage, transport, retry, and observability modules.

Useful Questions to Ask the Interviewer
  • Which browsers must the SDK support?
  • Can applications disable analytics completely?
  • Should queued events survive reloads and offline periods?
  • What information may be collected before consent is granted?
  • How much duplicate delivery is acceptable?
  • What batch-size and retry limits should applications configure?
Design a browser analytics SDK used by many webpages. diagram
How to Explain It in an Interview
1. Initialize safely and isolate each instance

The host page calls init(config). The host application owns the configuration it supplies. Config & Validation checks required values, defaults, and schema settings. Each SDK instance then owns its validated runtime configuration.

The Consent Manager checks consent, GPC, and Do Not Track. It also applies masking and allowlist rules for sensitive data. A denied or disabled instance should not send normal analytics events.

Multiple instances remain isolated by write key. They have separate queues and configuration, so one integration cannot silently change another.

2. Collect page views and custom events

The public API exposes track, identify, page, and shutdown. Event Normalizer validates the schema, coerces supported values, and adds metadata. Context Enricher adds allowed values such as URL, referrer, UTM data, viewport, device, session, and page title.

Deduplicator uses event IDs and page-view checks to reduce duplicates. For a single-page application, route changes can trigger page() automatically.

3. Batch and persist without blocking interaction

Events first enter an in-memory queue. Batcher groups them by size or time. This reduces network calls and keeps analytics away from the page's interaction path.

Queue & Storage can persist queued events and metadata in IndexedDB. LocalStorage holds small configuration, flags, and consent state. SessionStorage holds temporary session information. Memory holds the in-flight queue and counters.

When offline, the SDK pauses sending and keeps eligible queued events when possible. On a later load or when the browser returns online, it restores or resumes the queue.

4. Choose transport based on page lifecycle

During active use, batches use fetch. This lets the SDK observe transport failures and apply its retry policy.

During pagehide or unload, it uses navigator.sendBeacon or a small fetch request with keepalive. It never relies on synchronous XHR during unload. This avoids keeping the page alive just to finish analytics work.

Observable failures retry up to retryLimit with exponential backoff and jitter. A successfully queued sendBeacon call does not provide server acknowledgement, so the SDK must not claim confirmed delivery.

5. Drop safely and expose observability

If an observable failure keeps happening after the retry limit, the SDK drops that event and increments its dropped-event metric. The host page can observe events queued, events sent, events dropped, send errors, and queue size through SDK statistics or callbacks.

Delivery is best effort. Bounded retries improve reliability, but duplicate delivery is still possible. The SDK therefore does not promise exactly-once delivery.

6. Evolve, clean up, and test safely

Every event carries a schema version. Additive changes should stay backward compatible. Unknown fields can be ignored, while deprecated fields produce warnings before removal.

shutdown() removes instance-owned listeners and performs cleanup. Compatibility stays framework-neutral, with no required framework dependency and support for the modern browsers targeted by the SDK build.

The main test seams are a mockable transport adapter, clock and ID generator, and storage adapter. These boundaries let several applications test failures, retries, storage, consent, and lifecycle behavior without using a real analytics endpoint.

Engineering Considerations / Design Trade-offs

The benefit is that batching keeps analytics work away from normal user interaction. Browser storage also helps events survive reloads and offline periods. The downside is more code for storage, cleanup, and browser lifecycle cases. Active fetch requests provide useful failure information. Page-exit sends are harder because the browser may stop work quickly. sendBeacon and fetch keepalive help, but they cannot prove server delivery. Limited retries improve reliability without retrying forever. Deduplication reduces repeats, but duplicate delivery can still happen.

Why Interviewers Ask This

The interviewer wants to see whether you understand browser limits instead of designing a large backend system. They want to see how you separate collection, consent, storage, transport, retries, and lifecycle handling. They also test whether you can protect user interaction and privacy while handling failures. A strong answer explains clear trade-offs, safe cleanup, multiple instances, compatibility, and test boundaries.

Interviewer may ask next
What would you change if the SDK must keep collecting events while the user stays offline for several hours?

I would keep the same architecture, but I would rely more on Queue & Storage. Events would still enter the in-memory queue first. While offline, eligible events would be persisted in IndexedDB instead of repeatedly trying the network.

I would enforce a maximum queue size and an event age limit. When either limit is reached, the SDK should drop older or lower-priority events and increase the dropped-event metric. This keeps browser storage bounded.

When the browser becomes online again, the existing Transport path resumes sending batches with fetch. Observable failures still follow the same bounded Retry Strategy. Consent rules continue to apply before data is stored or sent.

The main downside is extra storage management. Long offline periods can also create a large burst when connectivity returns, so the Batcher must continue respecting its normal size and timing limits.

How would the design handle several independent applications on one webpage creating separate SDK instances?

I would keep the Multiple Instances design already shown. Each initialized instance would own its configuration, consent state, queue, retry state, and cleanup behavior. The instances would be isolated by write key, with no global configuration collisions.

Persisted queue data should also be namespaced for the correct instance. That prevents one integration from restoring another integration's events. Event IDs and page-view deduplication should remain inside the proper instance unless the product explicitly defines shared behavior.

The instances may all observe browser lifecycle events such as pagehide or online changes. Each instance should handle those signals through its own registered callbacks and remove its own listeners during shutdown().

The public API does not need to change. The main downside is extra memory, storage, and listener cost when many instances run on the same page.

20. Design a frontend notification experience for millions of real-time updates.System DesignEasyGoogle

Question Details

Design the reported large-scale notification frontend. The browser receives new items while the user navigates, shows an unread count and a list, and lets users mark items read. Define application-shell and feature boundaries, normalized state, snapshot loading, incremental updates, ordering, deduplication, reconnection, and pagination of history. Cover initial loading, disconnected, stale, permission-denied, empty, and partial-error states; focus and announcement behavior; background-tab throttling; memory limits; and how multiple tabs reconcile read state. Keep backend internals outside scope except for contracts the browser needs, and explain how you would measure latency from event arrival to usable UI.

Short Interview Answer (30-60 seconds)

At a high level, I would build a notification page that stays useful while new items arrive in real time. The main challenge is keeping unread counts, ordered lists, and read state consistent across navigation and multiple tabs. I would use an SSR plus CSR hybrid, then hydrate the page for interaction. A snapshot loads first, WebSocket or SSE adds updates, and normalized client state drives the UI. The trade-off is fresher updates versus browser memory, battery, and background-tab work.

Detailed Explanation

The goal is to show notifications quickly while the user keeps navigating. The browser must show an unread badge, a notification list, and actions for marking items read. It must also handle bursts, weak networks, multiple tabs, and old cached data. I would divide the frontend into delivery, application boundaries, state, remote data, failure states, accessibility, and measurement. Server internals stay outside this design. The browser only depends on the API, identity, push, feature-flag, and error-reporting contracts shown in the diagram.

Useful Questions to Ask the Interviewer
  • How quickly should a new notification become visible?
  • How much notification history should the browser keep?
  • Must read state update immediately across multiple tabs?
  • What offline behavior is required?
  • Which browsers, devices, locales, and accessibility targets matter?
Design a frontend notification experience for millions of real-time updates. diagram
How to Explain It in an Interview
1. Deliver and start the browser application

The user reaches the application through DNS and HTTPS. Static JavaScript, CSS, images, and fonts come through the CDN and browser cache. The design uses an SSR plus CSR hybrid. SSR gives useful initial HTML. CSR handles later navigation and live updates. Hydration means attaching JavaScript behavior to that initial HTML.

Code splitting keeps route bundles smaller. Lazy loading delays heavy components until needed. Prefetching can prepare likely next routes. The service worker can cache shell assets and selected API responses.

2. Separate routes, components, and state

The application has routes such as /home, /inbox, /settings, /profile, and /help. The notification feature contains the Top Bar unread badge, Notification Center, Item, Toast or In-App Alert, Empty State, and Settings.

Small UI details remain local, such as panel state and focus. Shared notification data uses normalized state. Each notification is stored once by ID. Lists keep ordered IDs and pagination cursors. URL state keeps values such as the unread filter, page, and thread.

IndexedDB stores normalized events and metadata. LocalStorage keeps preferences, feature flags, and last-read information. Runtime memory uses an LRU cache so older entries can be removed when memory grows.

3. Load a snapshot and apply real-time updates

The first data request loads /v1/notifications?cursor=... as a snapshot. Later history pages use pagination cursors. After the snapshot, WebSocket or SSE delivers incremental updates. A heartbeat checks that the real-time connection is still alive.

The client deduplicates by notification ID. It ignores older sequence numbers. The diagram orders visible items by timestamp and ID in descending order. Read actions use /v1/notifications/{id}/read or the bulk-read endpoint.

BroadcastChannel carries read state, new items, and settings changes between tabs. This lets another open tab reconcile its local view without sharing one JavaScript execution context.

4. Handle browser failure states

The UI has Initial Loading, Empty, Active, Stale, Disconnected, Partial Error, Permission Denied, and Aborted states. Stale means the shown data may be older than the latest remote result. Partial errors keep usable items visible and offer retry where possible.

When the live connection drops, the browser reconnects and refreshes or resumes from its latest known position when the contract allows it. Background Sync can retry queued actions after connectivity returns. Background tabs should reduce rendering work because browsers throttle hidden pages. Updates can be batched until the tab becomes active again.

5. Make the experience accessible and measurable

Keyboard users can navigate the notification UI and close panels with Escape. Focus stays inside an open panel when needed and returns after closing. ARIA live regions announce important new notifications without announcing every background event.

The layout is mobile-first and responsive. Localization covers translated strings, plural forms, relative time, and right-to-left text.

For performance, I would record a client event when a notification arrives and another when its usable UI is rendered. Their difference measures event-to-UI latency. I would track p50 and p90 latency, render time, interaction metrics, reconnects, errors, and connection health. Feature flags support gradual rollout, experiments, a kill switch, and rollback.

Engineering Considerations / Design Trade-offs

The benefit is that SSR gives useful HTML quickly, while CSR handles navigation and live updates. The downside is more browser logic than a simple page. Normalized state avoids keeping many duplicate notification objects, but indexes and cursors add code. WebSocket or SSE keeps data fresh, but it uses network and battery. Caching helps on weak connections, but cached data can become stale. BroadcastChannel keeps tabs closer to the same state, but it adds coordination work. Virtualized lists and an LRU memory limit protect the browser, but older items may need another history request.

Why Interviewers Ask This

The interviewer wants to see whether the candidate can turn a large real-time requirement into clear browser flows. They are testing choices about state ownership, snapshot and live updates, ordering, deduplication, failures, multiple tabs, accessibility, and memory. They also want to see whether the candidate keeps backend internals out of scope, measures user-visible latency, and explains trade-offs clearly.

Interviewer may ask next
What would you change if users must mark notifications read while offline for several hours?

I would keep the same browser architecture, but I would rely more on the persistence layer. IndexedDB would keep the notification data and any pending read actions. The Disconnected state would clearly show that the user is viewing cached information.

When the user marks an item read offline, the shared client state can update immediately. The browser would also save the pending action locally. Background Sync can retry that action when connectivity returns if the browser supports it. Otherwise, the application can retry when it becomes active again.

BroadcastChannel would still send the local read change to other open tabs. After reconnecting, the browser would send queued actions and then refresh the snapshot or resume from its latest known position. The remote API still decides whether the read operation is accepted.

The main downside is conflict handling. A locally queued action may be old when the browser finally reconnects.

How would you handle a short burst of thousands of notifications for one user?

I would keep the same snapshot, real-time connection, and normalized state. The main change would be how often the browser updates the visible UI. I would batch a burst of incoming events instead of rendering every event separately.

The shared store would still deduplicate notifications by ID and ignore older sequence numbers. The visible list would keep the diagram's timestamp-and-ID descending order. A virtualized list would create DOM elements only for rows near the viewport. The runtime LRU cache would also remove older in-memory entries when the configured memory limit is reached.

For a background tab, I would do even less rendering because hidden pages are throttled. The unread count can advance while list work is coalesced until the tab becomes active again.

I would watch event-to-UI latency, render time, errors, and connection health during bursts. The downside is that batching can delay some visible updates slightly.

More questions load as you scroll

Disclaimer: This interview guide is for educational and informational purposes only. It is designed to help readers prepare, but it does not guarantee any interview result, hiring decision, offer, or outcome. Interview questions, hiring criteria, and preferred answers can vary by employer, interviewer, industry, location, and time. The examples and explanations reflect the authors' research and judgment, are provided without warranties of any kind, and should not be treated as the only correct approach. Diagrams are simplified illustrations intended to highlight the main components and their interactions; actual systems and implementations may be more complex. Alternative approaches may be equally valid or better suited to a particular question, context, or interviewer. To the fullest extent permitted by applicable law, the author, contributors, and publisher are not liable for decisions made, actions taken, or losses incurred based on this guide.

Company Notice: This guide is an independent educational resource and is not affiliated with, endorsed by, sponsored by, or approved by the company named in this guide. Company names are used only to identify interview experiences commonly reported by candidates. Interview practices can change without notice, and inclusion of company-specific content does not mean these questions are official, complete, or guaranteed to be asked. To the fullest extent permitted by law, the author, contributors, and publisher are not responsible for outcomes related to use of this material.