Microsoft JavaScript Frontend Developer Interview Questions & Answers

microsoft icon

Questions with Detailed ExplanationsWith Detailed Explanations

(Last Updated: August 28, 2026)

11. Build an old-style multi-tap phone keypad.NEWCodingMediumMicrosoft

Question Details

Implement createMultiTapKeypad(container, { commitDelayMs = 1000 } = {}) in framework-neutral browser JavaScript. Render an output field and native buttons for digits 2 through 9 using the mappings 2=abc, 3=def, 4=ghi, 5=jkl, 6=mno, 7=pqrs, 8=tuv, and 9=wxyz. Pressing the same key again before its pending character is committed cycles that character; after the configured delay, or when a different key is pressed, the pending character is committed and the next press starts a new character. Expose { getValue(), destroy() }; labels and output must be text, all buttons must work by keyboard, one timer at most may be pending, and destroy() must remove listeners and clear it. container is an empty connected Element, commitDelayMs is a positive finite number, and invalid input is outside scope. Example: press 2, press 2 again, wait at least one second, press 2, then press 6; the displayed text must become bam after the pending m is committed.

Short Interview Answer (30-60 seconds)

I would keep committed text separate from one pending character. Each digit maps to its letters. If the same key is pressed before the timer fires, I cycle the pending letter and restart the timer. A different key commits the old letter before starting a new one. The timer also commits after the delay. I keep at most one timer. Each press uses O(1) keypad-state work, and the auxiliary state is O(1), excluding the output text itself.

Detailed Explanation

See the Code while reading this explanation.

This problem asks us to recreate typing on an old mobile-phone keypad. Buttons 2 through 9 each represent a small group of letters. Repeated quick presses on the same button change the current letter. Waiting for the delay accepts that letter. Pressing a different button also accepts the current letter before starting another one. The displayed text must always contain all accepted letters plus the current pending letter. The solution therefore keeps committed text and one pending character as separate state and uses one resettable timer.

Useful Questions to Ask the Interviewer
  1. Should getValue() include the currently pending character? Here, yes. It returns the same current text shown in the output.
  2. Should Enter and Space work when a keypad button has focus? Yes. Native button elements already provide keyboard activation.
  3. Should destroy() remove the UI created by the function as well as listeners and the timer? The approved implementation removes its created UI after cleaning up the listeners and timer.
Build an old-style multi-tap phone keypad. diagram
How to Explain It in an Interview
1. Keep committed and pending state separate

committed stores characters that are already accepted. pendingKey stores the digit whose character is still being selected. index selects a letter inside that digit's mapping. At most one character is pending. The current displayed value is always committed + currentChar().

2. Process each keypad press

If the pressed digit is the same as pendingKey, the press happened before that character was committed. I advance index and wrap it with modulo. For example, repeated presses on 7 cycle p → q → r → s → p.

If the pressed digit is different, I first commit the previous pending character, if one exists. Then I make the new digit the pending key and start at index 0 of its letter group.

3. Keep at most one commit timer

After every press, I restart the commit timer. If an older timer is pending, I clear it first. When the new timer fires, I set timerId to null and commit the pending character. This guarantees that at most one timeout is pending.

4. Walk through the approved example

Initially, committed = "" and there is no pending key.

Press 2. The mapping is abc, so index 0 gives pending a. The displayed value is a.

Press 2 again before the timer fires. It is the same key, so index changes from 0 to 1. The pending character becomes b. The displayed value is b.

Wait at least one second. The timer commits b. Now committed = "b", no character is pending, and the displayed value remains b.

Press 2. A new pending character starts at index 0, so the pending character is a. The displayed value becomes ba.

Press 6. Because this is a different key, the pending a is committed first. Now committed = "ba". Key 6 maps to mno, so pending m starts at index 0. The displayed value becomes bam.

Wait at least one second. The timer commits m. Now committed = "bam", no character is pending, and the displayed value remains bam.

The displayed-value sequence is therefore a → b → b → ba → bam → bam.

5. Explain why it is correct

The invariant is simple: committed contains only accepted characters, while pendingKey and index describe at most one unfinished character. A same-key press only changes that unfinished character. A different-key press commits it before starting another one. Timer expiry also commits it. Because both the output and getValue() use committed + currentChar(), they always represent the current keypad state.

6. Explain the JavaScript implementation

The function creates one text output element and eight native buttons for digits 2 through 9. Button labels use textContent, so they are text. Native buttons provide normal keyboard activation. Each button gets a stored click-handler reference so destroy() can remove exactly the function that was registered. The output also uses textContent. destroy() clears a pending timer, removes all button listeners, and removes the UI created by this function.

7. Explain complexity and edge cases

Each press performs O(1) keypad-state work because every key has only three or four letters and the state updates are constant-sized. Auxiliary state is O(1), excluding the output text itself. Rapid same-key presses restart the timer and cycle correctly. A different key commits the previous character first. Cycling wraps around. The output starts empty. destroy() clears the pending timer and removes every registered listener.

Key Insight / Why This Solution Works

Use a small state machine with committed text plus at most one pending character. committed holds accepted text. pendingKey identifies the active digit, and index identifies its current letter. The central invariant is that there is never more than one pending character and the current value is always committed + currentChar(). A repeated press on the same key advances the pending index. A different key commits the old pending character before starting the new key. One resettable timeout performs the same commit when the user pauses.

Code
function createMultiTapKeypad(container, { commitDelayMs = 1000 } = {}) {
  // Fixed old-style phone keypad mapping.
  const map = {
    2: 'abc',
    3: 'def',
    4: 'ghi',
    5: 'jkl',
    6: 'mno',
    7: 'pqrs',
    8: 'tuv',
    9: 'wxyz',
  };

  // Accepted characters live in `committed`.
  // At most one unfinished character is described by pendingKey + index.
  let committed = '';
  let pendingKey = null;
  let index = 0;
  let timerId = null;

  // Keep each button and the exact listener registered on it.
  const buttonListeners = [];

  // Create a text-only output field.
  const output = document.createElement('div');
  output.textContent = '';
  output.setAttribute('role', 'status');
  output.setAttribute('aria-live', 'polite');
  container.appendChild(output);

  // Hold the native keypad buttons.
  const grid = document.createElement('div');
  container.appendChild(grid);

  // Read the current unfinished character without changing state.
  function currentChar() {
    if (pendingKey === null) {
      return '';
    }
    return map[pendingKey][index];
  }

  // The UI always shows accepted text plus the pending preview.
  function render() {
    output.textContent = committed + currentChar();
  }

  // Accept the current pending character, if one exists.
  function commit() {
    if (pendingKey === null) {
      return;
    }

    committed += currentChar();
    pendingKey = null;
    index = 0;
    render();
  }

  // Replace any older timeout so at most one timer is pending.
  function startTimer() {
    if (timerId !== null) {
      clearTimeout(timerId);
    }

    timerId = setTimeout(() => {
      // The timeout is no longer pending once this callback starts.
      timerId = null;
      commit();
    }, commitDelayMs);
  }

  // Apply one logical keypad press.
  function onPress(key) {
    if (pendingKey === key) {
      // Same key before commit: cycle to the next mapped letter.
      index = (index + 1) % map[key].length;
    } else {
      // Different key: accept the old character before starting a new one.
      commit();
      pendingKey = key;
      index = 0;
    }

    // Show the new pending value immediately and restart its delay.
    render();
    startTimer();
  }

  // Native buttons already support keyboard activation with Enter and Space.
  for (const key of Object.keys(map)) {
    const button = document.createElement('button');
    button.type = 'button';

    // Keep the digit and mapped letters as plain text.
    button.textContent = `${key} ${map[key]}`;

    // Save this exact function object so destroy() can remove it later.
    const handler = () => onPress(key);
    button.addEventListener('click', handler);
    buttonListeners.push({ button, handler });

    grid.appendChild(button);
  }

  // Return exactly the same current value that the output shows.
  function getValue() {
    return committed + currentChar();
  }

  function destroy() {
    // Cancel an automatic commit that has not fired yet.
    if (timerId !== null) {
      clearTimeout(timerId);
      timerId = null;
    }

    // Remove every event listener with its original function reference.
    for (const { button, handler } of buttonListeners) {
      button.removeEventListener('click', handler);
    }
    buttonListeners.length = 0;

    // Remove only the UI nodes created by this keypad.
    grid.remove();
    output.remove();
  }

  render();
  return { getValue, destroy };
}

// Direct runnable example matching the approved diagram.
const demoContainer = document.createElement('div');
document.body.appendChild(demoContainer);

const keypad = createMultiTapKeypad(demoContainer, { commitDelayMs: 1000 });
const demoButtons = [...demoContainer.querySelectorAll('button')];

function press(digit) {
  const button = demoButtons.find((item) => item.textContent.startsWith(`${digit} `));
  button.click();
}

function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

(async () => {
  // Step 1: pending 'a'. Display: "a".
  press('2');

  // Step 2: same key cycles to pending 'b'. Display: "b".
  press('2');

  // Step 3: timeout commits 'b'. Display remains "b".
  await wait(1100);

  // Step 4: start pending 'a'. Display: "ba".
  press('2');

  // Step 5: different key commits 'a' and starts pending 'm'. Display: "bam".
  press('6');

  // Step 6: timeout commits 'm'. Display remains "bam".
  await wait(1100);

  console.log(keypad.getValue()); // "bam"
})();
Time & Space Complexity

Each keypad press uses O(1) state work. Looking up a digit, moving to the next letter, changing the pending state, and starting or clearing one timer are constant-sized operations because there are only eight fixed keys and each mapping contains at most four letters. Auxiliary state is O(1), excluding the output text itself. The text shown to the user grows as the user types. Updating or creating that JavaScript string can depend on the current output length, so the O(1) statement applies specifically to the keypad-state work shown in the diagram.

Where it is used

This state-and-timer pattern is useful when repeated activation of one control changes a pending choice before that choice becomes final. Examples include old-style multi-tap text entry, compact hardware controls, delayed-confirmation interfaces, and other small input devices where several choices share one button.

Why Interviewers Ask This

This question tests whether a candidate can model browser interaction as clear state transitions. It checks timer management, event-listener cleanup, native keyboard accessibility, safe text rendering, and API behavior. It also shows whether the candidate understands pending versus committed state, can guarantee that only one timeout is active, can remove the exact listeners that were registered, and can keep the implementation, example walkthrough, and complexity explanation consistent.

Common interview mistakes

One common mistake is mixing committed text with the pending preview, which makes cycling difficult to reason about. Another is cycling when a different key is pressed instead of committing the old character first. A candidate may accidentally leave multiple timers active instead of clearing the previous one. Another bug is registering an anonymous listener and later trying to remove it with a new function object. getValue() may also be implemented incorrectly if it returns only committed text instead of the current displayed text. Finally, custom Enter or Space handlers are unnecessary when native buttons already provide keyboard activation.

Interview tip

Before writing event code, state the invariant clearly: committed contains finished characters, while pendingKey and index describe at most one unfinished character. Then make every branch preserve that invariant.

Interviewer may ask next
How would you add a Backspace button?

If a pending character exists, Backspace can clear pendingKey, reset index, clear the pending timer, and render. If no character is pending, it can remove the last character from committed and render. The same invariant still holds because there is never more than one pending character. The keypad-state work remains O(1) per press, apart from JavaScript string work on the growing output. Auxiliary state remains O(1), excluding output.

How would the design change if each digit had its own commit delay?

Store both the letters and delay for each digit, such as { letters: "abc", delayMs: 800 }. The state machine does not change. startTimer() simply uses the delay associated with the current pending key. There is still only one pending character and one timeout. The keypad-state work remains O(1) per press, and auxiliary state remains O(1), excluding output. The tradeoff is a slightly larger fixed mapping and more configuration.

12. Design a browser search API for a phone-number directory containing millions of records.API DesignHardMicrosoft

Question Details

A React frontend must search an online directory containing millions of phone-number records without downloading the directory. Design a browser-facing searchPhoneNumbers contract and its HTTP representation for exact and prefix lookup. Define the query's accepted characters and normalization boundary, result-record identity and display fields, cursor pagination, page-size limits, deterministic ordering, cancellation, authentication or tenant scope, and clean empty, invalid, forbidden, rate-limited, and unavailable outcomes. Cover debounced edits, a blank query, stale responses after the query changes, duplicate records, privacy-preserving result limits, caching, request identifiers, logging, and abuse through automated enumeration. Include one exact request and response with a continuation cursor, and keep database and indexing internals outside scope except where the browser contract needs a consistency or freshness guarantee.

Short Interview Answer (30-60 seconds)

I would keep the browser contract small and predictable. The React client debounces edits, validates and normalizes the phone number, then sends GET /v1/phone-numbers/search with q, mode, limit, cursor, and tenant scope. I would cancel obsolete Fetch requests with AbortController and also ignore stale responses. The API returns stable record IDs, display fields, paging data, and a next cursor. The UI handles success, empty, invalid, unauthorized, forbidden, rate-limited, unavailable, and aborted states separately. I would use HTTPS, bearer authentication, tenant authorization, result caps, enumeration protection, and private ETag caching. The main trade-off is freshness versus fewer requests.

Detailed Explanation

A user types part or all of a phone number. The browser must search a huge online directory without downloading it. The client waits briefly while typing stops, checks the input, and converts it into one normalized form. It then sends a small HTTPS request to one remote API. Old requests are canceled when the query changes. Responses are checked before the UI uses them. Results arrive in small pages through opaque cursors. The design also protects tenant data, limits automated enumeration, supports caching, and keeps one consistent result version while a cursor chain is active.

Useful Questions to Ask the Interviewer
  • What minimum normalized prefix length should be required before prefix search begins?
  • Should previous successful results remain visible while a new query is loading?
  • How long is the documented freshness window for a new search?
Design a browser search API for a phone-number directory containing millions of records. diagram
How to Explain It in an Interview
1. Define the browser contract

The browser calls GET /v1/phone-numbers/search.

The query parameters are q, mode, limit, cursor, and the tenant scope shown in the contract.

The user may type digits, one optional leading +, spaces, hyphens, and parentheses. Alphabetic characters, including x and X, are rejected. Normalization happens in the browser before the request. Spaces, hyphens, and parentheses are removed. The normalized q contains digits with an optional leading +.

mode is either exact or prefix. exact means the complete normalized number must match. prefix means matching numbers begin with the normalized prefix.

limit is optional. It is between 1 and 50. The default is 20. cursor is an opaque continuation value. The browser sends it back unchanged and never tries to decode it.

The request carries Authorization: Bearer <access token>, X-Tenant-ID, X-Request-ID, Accept: application/json, and If-None-Match when the browser has an ETag. The diagram also shows the tenant identifier in the query contract. The remote API remains one external HTTPS boundary.

An exact continuation request can be represented as:

GET /v1/phone-numbers/search?q=%2B14255551234&mode=prefix&limit=20&cursor=eyJ2IjoxLCJwYWdlIjoyfQ&tenantId=contoso

with these headers:

Accept: application/json Authorization: Bearer <access token> X-Tenant-ID: contoso X-Request-ID: 01HZX7P2F9V7R5T8NQ1J2K3L4M If-None-Match: W/\"a1b2c3\"

A successful 200 OK response can be:

{"query":"+14255551234","normalizedQuery":"+14255551234","mode":"prefix","items":[{"id":"rec_01J8ZSE6K2Q1X8V9Y0M1N2P3","displayName":"Acme Support","phone":"+1 425-555-1234","location":{"city":"Redmond","state":"WA","country":"US"},"verified":true,"type":"business"}],"paging":{"nextCursor":"eyJ2IjoxLCJwYWdlIjozfQ","hasMore":true,"limit":20},"meta":{"requestId":"01HZX7P2F9V7R5T8NQ1J2K3L4M","asOf":"2025-05-20T12:34:56.789Z"}}.

Each result has a stable opaque id. The browser should use that identity instead of display text when de-duplicating records. Display fields include displayName, a formatted phone, location fields, verified, and type.

The next page uses paging.nextCursor. Ordering stays deterministic across pages: normalized phone number ascending, then stable record ID ascending.

An empty search result is still 200 OK. It returns items: [] and nextCursor: null.

2. Start and control the request

The React client debounces edits for about 250 to 400 milliseconds. Debouncing means waiting briefly before searching. This avoids sending a request for every keystroke.

A blank normalized query sends no API request. The client clears the search results and shows an idle or blank state.

After validation and normalization, the client builds the GET request. Fetch then sends it over HTTPS.

The client creates an AbortController for the in-flight Fetch request. When the query changes, it aborts the previous request.

Cancellation is not enough by itself. The client also keeps a stale-response guard. If an older response arrives after a newer query became current, that old response is ignored.

The GET request is safe and idempotent at the HTTP contract level. Even so, retries must respect rate limits and temporary-service rules instead of creating an uncontrolled retry loop.

3. Validate the response

Fetch normally resolves when an HTTP response arrives, including 400, 401, 403, 429, or 503. It rejects for network-level failures or an abort. The client must therefore inspect the response status before treating the call as successful.

For a normal data response, the client checks that Content-Type is JSON. It then parses the JSON and validates the required fields and types before changing React state.

The stale-response guard is checked before rendering. A structurally valid response can still be obsolete if the user has already changed the query.

A 304 Not Modified is different from a normal JSON response. It has no response body. The browser reuses the previously cached 200 representation.

4. Handle success and failure

200 OK means the request succeeded. The response may contain records or an empty array.

For a non-empty success, the client renders the results. If hasMore is true, it can request the next page using nextCursor.

For an empty success, the UI shows a clean no-results state. Empty data is not an error.

400 Bad Request means the input or request parameters are invalid.

401 Unauthorized means authentication is missing or invalid.

403 Forbidden means the authenticated user does not have access to the requested tenant or scope.

429 Too Many Requests means the request was rate limited. The client respects the HTTP Retry-After header and delays another attempt until that period has passed.

503 Service Unavailable means a temporary service problem. The browser shows a retryable unavailable state and uses backoff. If a Retry-After header is supplied, the client respects it.

An AbortController cancellation is client-side control flow. The UI can mark the request as aborted or silently ignore it. It is not represented as a special HTTP status in this design.

5. Protect the browser boundary

All requests use HTTPS.

The bearer token stays in the Authorization header. It is never placed in the URL or written to logs.

Tenant scope accompanies the request, but browser code is not the authorization authority. The trusted remote API must enforce access for the authenticated user and tenant.

CORS controls which browser origins may read the API response. CORS is not authentication or authorization.

The result set is intentionally small. The page limit is capped at 50. Only the display fields needed by the UI are returned. These limits reduce privacy exposure and make bulk enumeration harder.

Rate limiting applies per IP and token according to the approved design. Enumeration protection can also slow suspicious repeated searches.

Every request carries X-Request-ID, and the response returns requestId. Those identifiers can correlate a browser-visible failure with request logging without putting tokens or sensitive data in logs.

6. Cache repeated GET results

The browser may use its normal HTTP cache for successful GET responses. The response supplies the cache policy, such as private caching and a short or zero freshness lifetime.

An ETag identifies the cached representation. On a later request, the browser can send If-None-Match with that ETag.

If the representation did not change, the remote API may return 304 Not Modified. A 304 has no response body. The browser then reuses its cached 200 representation.

The cache is private to the appropriate browser user or tenant context. Vary behavior must prevent responses for different authorization or tenant contexts from being mixed.

Caching reduces repeated downloads. The trade-off is that cached data can be slightly older than a fresh network response.

7. Preserve pagination consistency and freshness

All pages in one cursor chain use the same result version. This prevents later pages from silently switching to a different snapshot while the user is paging.

The response includes asOf. That value tells the client when the result version was observed.

The browser treats the cursor as opaque and uses it only with the matching query and mode. It must not mix a cursor from one search with another search.

A new search can see directory changes within the documented freshness window. The browser contract does not expose database or indexing internals.

8. Verify the behavior

I would test formatted input containing spaces, hyphens, parentheses, and a leading +. I would test that letters are rejected.

I would test both exact and prefix modes. I would test the blank-query rule and verify that no network request is made.

I would test rapid typing. The previous Fetch request should be aborted, and a stale response must never replace newer results.

I would test cursor paging and verify deterministic ordering by normalized phone number, then stable record ID. I would test duplicate records and confirm de-duplication uses the stable id.

I would test 200, empty 200, 400, 401, 403, 429, and 503 independently.

I would test ETag caching with If-None-Match. A 304 must have no JSON body, and the browser must reuse the cached successful representation.

Finally, I would test that one cursor chain keeps the same result version and that a new search observes changes within the documented freshness guarantee.

Practical Complexity & Trade-offs

The browser never downloads millions of records. It keeps only a small result page and an opaque cursor, so memory and rendering work stay bounded by the page limit. Debouncing reduces request count. AbortController stops obsolete work, while the stale-response guard protects the UI from old results. Cursor paging avoids large client payloads and works with deterministic ordering. ETag caching can reduce repeated downloads, but cached data may be slightly older. Private caching and tenant-aware variation prevent response mixing. Result caps, rate limits, and enumeration protection reduce privacy risk. The downside is more client state and more testing around cancellation, pagination, caching, retries, and freshness.

Why Interviewers Ask This

The interviewer is testing whether I can define a clear browser API boundary without moving server internals into the frontend. They want correct HTTP modeling, input normalization, cursor pagination, stable ordering, and safe asynchronous behavior. They also look for security judgment around bearer tokens, tenant authorization, CORS, privacy, caching, and enumeration abuse. A strong answer separates authentication from authorization, stale responses from failures, and HTTP caching from application state while explaining practical trade-offs.

Interviewer may ask next
What would you change if users type very quickly and the directory API becomes rate limited?

I would keep the same endpoint and make the browser more conservative before sending requests. The affected flow is the React input, debounce step, AbortController, stale-response guard, and GET /v1/phone-numbers/search. I would keep the existing 250 to 400 millisecond debounce. Every new query still aborts the previous Fetch request, and the stale guard still prevents an older response from replacing newer results.

When the API returns 429 Too Many Requests, the client reads the Retry-After header. It must not retry immediately. The search action can be delayed or temporarily disabled until that period ends. The UI should show a clear rate-limited state instead of a generic failure.

Authentication and authorization do not change. The bearer token stays in the authorization header, tenant scope stays attached, and the remote API remains responsible for enforcing access and rate limits.

The main downside is slower feedback during heavy typing or abuse protection. Users may wait longer, but the design avoids creating even more requests while the service is already limiting traffic.

How would you keep pagination correct if directory data changes while the user loads more results?

I would keep the same cursor-based API and preserve one result version for the whole cursor chain. The affected flow is the successful 200 response, nextCursor, the next GET request, deterministic ordering, and the asOf freshness metadata.

The first page returns an opaque nextCursor. The browser sends that cursor unchanged for the next page. Every page in that cursor chain uses the same result version. Ordering remains normalized phone number ascending, then stable record ID ascending. The browser also de-duplicates by the stable record id.

If the directory changes while paging, the current cursor chain does not silently switch versions. A new search can observe newer directory data within the documented freshness window. Authentication, tenant scope, result caps, and privacy protections remain unchanged.

This keeps pagination predictable and avoids missing or repeating records because the underlying directory changed between pages. The main downside is freshness. Someone paging through an older chain may not see the newest update until starting a new search.

13. Design an A/B testing and telemetry contract for a frontend.API DesignMediumMicrosoft

Question Details

Design the browser-facing contract for an experiment with a control and one treatment. Define experiment and variant identifiers, eligibility and assignment input, stable assignment lifetime, exposure semantics, outcome-event schema, event IDs, timestamps, user or anonymous identity transitions, and return values exposed to application code. State the assumptions required for interpreting telemetry, including randomization, sample independence, consistent exposure, metric definition, missing data, multiple devices or tabs, and overlapping experiments. Cover consent, data minimization, offline queues, batching, duplicate delivery, ordering, clock skew, retries after uncertain sends, schema evolution, disabled experiments, and shutdown. Include one exact assignment-to-exposure-to-outcome sequence and explain how the API prevents a component from recording an outcome for a variant the user was never exposed to.

Short Interview Answer (30-60 seconds)

I would separate assignment, exposure, and outcome tracking. assign() checks eligibility and returns a stable control or treatment decision with a TTL. The component calls expose() only when that variant can affect the UI. Then track() accepts an outcome only when the same session already has a matching exposure. Each event has a unique ID and timestamp. The browser validates events, queues them offline, batches them, and sends JSON over HTTPS to one telemetry API. Retries reuse the same event ID, so duplicate delivery can be deduplicated. Consent and data minimization apply before telemetry is collected.

Detailed Explanation

The main problem is simple. Being assigned to a treatment does not mean the user saw it. We need separate contracts for assignment, exposure, and outcomes. The browser also needs stable identity, reliable delivery, privacy rules, and clear experiment assumptions. Events may be delayed, duplicated, or arrive out of order. Browser clocks may also be wrong. The design therefore uses stable decisions, exposure gating, event IDs, timestamps, an offline queue, batching, retries, and deduplication. The remote telemetry API stays one external boundary.

Useful Questions to Ask the Interviewer
  • How long should an assignment remain stable before its TTL expires?
  • Which user attributes may be used for eligibility and bucketing?
  • Which outcome events and metric definitions are approved?
  • What consent signal must exist before telemetry collection starts?
  • How should anonymous and signed-in identity be handled at login?
  • How should overlapping experiments be treated if they affect the same metric?
Design an A/B testing and telemetry contract for a frontend. diagram
How to Explain It in an Interview
1. Define the experiment and assignment contract

I would identify the experiment with an experimentId, such as exp_checkout_v1. The variant is either control or treatment.

The browser calls assign() with the experiment ID, user attributes, context, and a stable bucketingKey. Context may include values such as page, device, and referrer. The SDK also knows its own version.

The assignment is deterministic for the same bucketing key. It remains stable for the assignment TTL. The diagram shows an example TTL of about 30 days. The variant must not change in the middle of that TTL.

assign() returns experimentId, variantId, decisionId, isEligible, reason, and ttl. The assignment is cached until the TTL expires.

If the user is not eligible, the application receives the not-eligible result and does not apply an experiment variant.

2. Keep identity stable

The contract supports a stable user identity or an anonymous identity. The browser can keep that identity using the supported cookie, local storage, or login state shown in the design.

The important rule is stability. The bucketing identity should not change unexpectedly during the assignment lifetime. Otherwise the same user could move between control and treatment.

When an anonymous user later signs in, identity handling must use a defined transition. The current assignment should remain stable for its TTL rather than silently changing the user to another variant.

Multiple tabs and devices may exist. Analysis must therefore define whether the sample unit is a user, anonymous identity, or another bucketing unit.

3. Record exposure only when the variant affects the UI

Assignment does not prove that the user saw the experiment.

The component calls expose() only when the assigned variant is rendered and can affect the user experience. The call carries experimentId, decisionId, and context.

expose() returns an exposureId and a boolean showing whether the exposure was recorded.

Exposure is idempotent per experiment and session in this design. Repeating the same exposure does not create another logical exposure for that session.

This gives exposure one clear meaning: the user had a real chance to experience the assigned variant.

4. Gate outcomes behind exposure

The component calls track() only after a valid exposure exists in the current session.

The outcome event carries the experiment ID, decision ID, exposure ID, event name, and supported metric data such as value, currency, and metadata. One event represents one business outcome. The experiment may support multiple defined metrics.

track() returns an eventId and a queued boolean.

The SDK checks its exposure state before queuing the outcome. If the matching exposure does not exist, the SDK prevents that outcome from being recorded for the experiment.

This directly prevents a component from reporting an outcome for a variant the user was assigned to but never exposed to.

5. Use one reliable browser delivery path

Before delivery, the browser validates and enriches the event with supported fields such as SDK version, session ID, timestamp, page, and referrer.

If delivery cannot happen immediately, the event is persisted in an IndexedDB offline queue. The browser later batches queued events and sends compressed JSON to POST /v1/telemetry.

The request crosses one remote telemetry API boundary over HTTPS. The diagram shows JSON with Content-Type: application/json, bearer-token authentication, CORS support, and HTTPS-only transport.

The remote boundary accepts batches, validates the schema, stores events, deduplicates them, and returns an acknowledgement.

6. Handle failures and uncertain sends safely

A send can fail because the browser is offline, the network fails, a timeout happens, or the remote API returns a retryable server failure.

The browser keeps retryable events in the queue. Retry uses backoff with jitter.

An uncertain send is important. The remote API may have received an event even when the browser did not receive its acknowledgement. The browser therefore retries the same logical event with the same eventId.

The remote side deduplicates by eventId. This makes duplicate delivery safe. The contract does not assume exactly-once network delivery.

The visible client states remain separate. They include loading, success, empty or not eligible, retryable error, final error, and aborted or stale work. A cancelled or superseded request must not overwrite newer application state.

7. Define event identity, time, ordering, and schema rules

Each telemetry event has a unique eventId. The diagram uses UUID v4 for event IDs.

Events also carry timestamps. Client time is best effort because a browser clock can be wrong. Server time is authoritative when telemetry is received and interpreted.

The contract does not guarantee total event ordering. Consumers use timestamps and event meaning instead of assuming every event arrives in creation order.

Schema changes are additive. Events include a schemaVersion. Newer consumers ignore unknown fields when possible, so old and new frontend versions can coexist during deployment.

8. Protect consent and minimize data

The browser must respect the user's consent signal before collecting telemetry.

The client should send only information needed for assignment, exposure, and approved metrics. It should not collect unnecessary personal data.

The diagram also shows bearer-token authentication at the remote API boundary. A token exposed to browser JavaScript must not be treated like a private server secret.

CORS controls whether browser code from another origin may read the response. It is not a replacement for authentication or authorization.

9. Handle disabled experiments and shutdown

A disabled experiment makes the SDK path a no-op for new experiment activity.

During shutdown, queued telemetry is flushed when possible. After shutdown, the SDK starts no new network calls.

This gives the frontend a clear stopping rule. It avoids creating new assignments, exposures, or outcomes after the experiment client is disabled.

10. Exact assignment-to-exposure-to-outcome sequence

The page loads and initializes the SDK.

First, the component calls assign(). It receives treatment as the variant.

Second, the checkout UI renders the treatment. The component now calls expose() because the treatment can affect the user.

Third, the user completes the purchase. The component calls track() for purchase_complete with a value of $49.99.

The browser queues the outcome, batches it, sends it to the telemetry API, and receives an acknowledgement.

If the exposure step never happened, track() fails the SDK exposure gate. The purchase outcome is not queued for that experiment decision.

11. State the assumptions needed to interpret telemetry

Randomization must be uniform and deterministic for the chosen bucketing key.

The chosen sample units should be independent enough for the intended analysis.

Exposure must have one consistent definition. An exposure should always mean that the variant could affect the user.

Metric definitions must be clear and versioned before the results are interpreted.

Missing data is expected. Analysis must account for events that never arrive.

Multiple devices and tabs can produce repeated activity. The analysis needs a clear identity and sample-unit rule.

Overlapping experiments should minimize cross-experiment interference. Their interactions should not be silently ignored when they can change the same metric.

Duplicate delivery is expected and handled by event IDs. Total ordering is not guaranteed. Clock skew must also be considered. Server time is the stronger reference for received telemetry.

Practical Complexity & Trade-offs

Most browser operations are small local state checks and event writes. Stable assignment caching makes repeated assign() calls cheap until the TTL expires. Exposure and outcome calls should not block rendering. The offline IndexedDB queue improves reliability, but it adds cleanup and retry logic. Batching reduces network requests, but events may arrive later. Retrying with the same eventId protects against duplicate counting after uncertain sends. The system does not guarantee total ordering, so timestamps and event meaning matter. Additive schema evolution costs some maintenance, but it allows older and newer frontend versions to work together. Privacy is also a trade-off: collect only what the experiment actually needs.

Why Interviewers Ask This

Interviewers use this question to test whether you understand the difference between assignment, exposure, and outcome. They also evaluate browser API boundaries, stable identity, event contracts, privacy, offline behavior, retries, and duplicate delivery. A strong answer shows good judgment about idempotency, clock skew, schema evolution, and experiment assumptions. The goal is not memorizing telemetry terms. It is showing that you can design data that remains useful when real browser and network failures happen.

Interviewer may ask next
What happens if the browser sends an outcome event, times out, and does not know whether the telemetry API accepted it?

I would retry the same logical event with the same eventId. The affected flow is the browser queue and batch sender to POST /v1/telemetry.

A timeout is uncertain. The remote API may already have accepted the event even though its acknowledgement never reached the browser. If the client creates a new event ID for the retry, the same outcome could be counted twice.

The browser therefore keeps the original queued event. It retries that event with backoff and jitter. The same eventId stays attached to every delivery attempt.

The remote telemetry API deduplicates by eventId. If the first delivery already succeeded, the retry does not create another logical event. If the first delivery never arrived, the retry can deliver it normally.

Assignment, exposure gating, consent, identity, and schema rules do not change.

The main downside is extra queue state and deduplication work. Delivery can also be delayed while the client waits for a retry. The design chooses reliable at-least-once delivery with deduplication instead of pretending the network provides exactly-once delivery.

How should the contract behave when an anonymous user signs in after already receiving an experiment assignment?

I would preserve the existing assignment for its TTL and make the identity transition explicit in the browser state. The affected parts are the stable identity state, the assignment cache, and telemetry enrichment.

Before login, the browser can use the stable anonymous identity shown in the design. That identity provides the bucketing key used for assignment. Exposure and outcome records continue to use the experiment, decision, and exposure identifiers produced by that assignment.

When the user signs in, the browser now has a stable user identity. The client should not silently rebucket the user during the existing assignment lifetime. Otherwise the user could see both control and treatment.

Consent and data-minimization rules stay unchanged. The browser should not add unrelated personal information merely because login occurred.

The main downside is analysis complexity. One person can have anonymous activity, authenticated activity, several tabs, and more than one device. The analysis therefore needs a clear rule for its sample unit and identity transition. All other assignment, exposure, outcome, retry, and deduplication behavior remains unchanged.

14. Design browser-facing API contracts for a ticket-booking frontend.API DesignHardMicrosoft

Question Details

A browser application lets a user find an event, choose a session, inspect seat availability, select seats, confirm the current price, and submit one booking. Design the frontend-facing HTTP contracts and client adapter for those steps. Define stable event, session, seat, availability-snapshot, price, hold or selection, user, and booking identities; request and response shapes; error records; authentication; cacheability; cancellation; pagination where needed; and compatibility/versioning. Specify how the contract handles stale seat maps, another user taking a seat, price changes, expired selections, retries after an uncertain booking response, duplicate submissions, partial failures, and navigation away. Include idempotency and concurrency semantics, one exact end-to-end request/response sequence, and how the UI maps loading, unavailable, conflict, expired, success, and recoverable-error outcomes without treating client state as authoritative.

Short Interview Answer (30-60 seconds)

I would keep the browser contract small and server-authoritative. The user searches, chooses a session, checks seats, creates a short hold, confirms price, and submits one booking. The client validates input, uses Fetch over HTTPS, shows loading, and uses AbortController plus a request ID to cancel or ignore stale work. Read requests may use Cache-Control, ETag, and If-None-Match. Booking uses an Idempotency-Key so retries cannot create duplicates. The browser checks status, content type, JSON, and schema before updating UI state. CORS and authentication stay at the browser boundary. The trade-off is more round trips and UI states for clearer correctness.

Detailed Explanation

The browser must guide one booking without pretending its local state is true. A user searches for an event, chooses a session, checks seats, holds seats, confirms price, and submits one booking. Each step sends one HTTPS request to the same external ticketing API boundary. The client validates input before sending. It can cancel obsolete work and ignore stale responses. After a response arrives, it checks status, content type, JSON, and schema. Only validated server data may change the visible UI.

Useful Questions to Ask the Interviewer
  • How fresh should seat availability appear before the user selects seats? The server-side hold still remains authoritative.
  • What timeout and retry budget should the browser use for safe or idempotent requests? The contract defines backoff but not exact timing.
  • Which supported authentication pattern should this deployment use: a bearer token or an HttpOnly cookie?
  • What browser-support and offline behavior are expected? Those choices can affect cancellation and recoverable-error handling.
Design browser-facing API contracts for a ticket-booking frontend. diagram
How to Explain It in an Interview
1. Define the browser contract

I would use stable opaque IDs. The contract keeps eventId, sessionId, seatId, snapshotId, priceQuoteId, holdId, userId, and bookingId separate. The client never treats any local copy of those values as authority.

The exact happy-path request and response sequence is:

  1. GET /v1/events?query={q}&cursor={cursor}&limit={n} returns events and nextCursor.
  2. GET /v1/events/{eventId}/sessions?cursor={cursor}&limit={n} returns sessions and nextCursor.
  3. GET /v1/sessions/{sessionId}/availability returns the current seat availability tied to snapshotId.
  4. POST /v1/holds with sessionId, seatIds, and snapshotId returns holdId, expiresAt, and priceQuoteId.
  5. GET /v1/holds/{holdId}/price returns priceQuoteId, amount, currency, and validUntil.
  6. POST /v1/bookings with holdId and priceQuoteId, plus an Idempotency-Key, returns bookingId and status.

Each response comes back to the browser. The client checks it before rendering the next state.

The seat availability is only a snapshot. Hold creation is atomic and server-authoritative. If the snapshot is stale, or another user already took a seat, the hold request returns 409 Conflict. The UI then refreshes availability.

If the confirmed price changed or expired, the UI asks the user to confirm again. It never books silently with an old quote.

Reusing the same booking Idempotency-Key returns the original canonical booking result. This prevents duplicate bookings after retries or an uncertain response.

The API uses the /v1 path prefix. Compatible changes are additive and backward-compatible.

2. Start and control the request

A user action starts each request. The frontend first validates required input and builds the URL, headers, query, or JSON body.

The UI then enters a loading state. The client creates an AbortController for work that can become obsolete. A newer search, route change, or navigation can abort the older request.

Abort alone is not enough. A response can still become irrelevant after the UI moves on. The client therefore keeps a request ID or another stale-response guard. It ignores a response that no longer matches the latest request.

The cross-cutting request correlation header is x-request-id. It helps connect a browser request to the error record when troubleshooting.

For reads, the browser may reuse a fresh HTTP cached response. The contract supports Cache-Control, ETag, and conditional If-None-Match requests. This reduces repeat downloads while the server still controls freshness.

3. Validate the response

fetch() does not reject just because the server returned a 4xx or 5xx response. The client must inspect the HTTP status first.

Next, it checks the Content-Type. Normal data uses JSON. Structured failures use application/problem+json.

The client parses JSON only after those checks. It then validates the runtime response shape before changing application state. This protects the UI from malformed or incompatible payloads.

The problem record contains type, title, status, detail, instance, and traceId. A seat conflict can therefore explain that a specific seat is no longer available while keeping one stable error shape.

4. Handle success and failure

The UI keeps outcomes separate. It can show loading, success, empty, unavailable, conflict, expired, recoverable error, or final error.

For stale availability, the client does not trust the old seat map. It fetches fresh availability and lets the user choose again.

For a 409 seat conflict, the hold did not win the race. The UI shows conflict and refreshes the seat state.

For a changed or expired price, the client shows the latest price and requires reconfirmation.

For an expired hold, the UI shows expired state and starts the selection flow again.

For network or retryable 5xx failures, safe or idempotent operations may retry with backoff. The browser must not retry POST /v1/bookings without its Idempotency-Key.

If a booking response is uncertain or only partly completed from the browser's point of view, the UI does not guess success or failure. The browser retries POST /v1/bookings with the same Idempotency-Key and uses the returned canonical booking result.

If the user explicitly cancels a hold, the client sends DELETE /v1/holds/{holdId}. On navigation away, release is best effort only. The server-side hold TTL remains the guaranteed cleanup rule.

5. Protect the browser boundary

All remote calls use HTTPS. The same-origin policy limits cross-origin access by browser JavaScript. CORS is the server policy that can allow this frontend origin to read API responses. CORS is not authentication.

Authentication may use a bearer token in the Authorization header or an HttpOnly cookie. An HttpOnly cookie cannot be read by JavaScript. If credentials are sent automatically with cookies, CSRF protection is needed for state-changing requests.

The browser never owns authorization. The trusted API decides whether the authenticated user may perform an action. Hiding a button is not security.

6. Verify the behavior

I would test the client adapter around the exact contract boundaries. Input validation should block malformed requests. Abort tests should prove canceled work does not update the UI. Stale-response tests should prove an older response is ignored.

Response tests should cover successful JSON, empty data, malformed JSON, wrong content type, schema failure, and application/problem+json. Conflict tests should prove a 409 refreshes seat availability. Expiry tests should prove an expired hold or price moves the UI to expired or reconfirmation state.

For booking, the important test is retry safety. Two attempts with the same Idempotency-Key must map to one canonical booking result. Finally, navigation-away tests should prove the client may attempt hold release without depending on that request for correctness.

Practical Complexity & Trade-offs

The browser uses several small requests instead of one large request. This makes each step clear, but it adds network round trips and more UI states. Cursor pagination keeps long event and session lists small. HTTP caching reduces repeated downloads, but stale data is risky for seats and prices, so the server remains authoritative. AbortController saves work when the user moves on, while a stale-response guard prevents old data from overwriting new data. Idempotency makes booking retries safe, but the client must keep the same key for the same booking attempt. Atomic holds prevent two users from winning the same seat. HTTPS, CORS, authentication, and CSRF rules protect the browser boundary. The main maintenance cost is keeping request shapes, schemas, error handling, and UI states consistent as /v1 grows through backward-compatible additive changes.

Why Interviewers Ask This

This question tests whether the candidate can design a browser contract, not just name endpoints. The interviewer is checking HTTP judgment, request and response modeling, runtime validation, caching, cancellation, authentication boundaries, and browser security. They also want to see concurrency thinking around stale seat maps, atomic holds, price changes, and duplicate booking attempts. A strong answer keeps the server authoritative, explains retry safety clearly, and maps technical failures into understandable UI states.

Interviewer may ask next
What changes if seat availability updates very quickly during a popular ticket sale?

I would keep the same API shape, but make the browser refresh availability more often while still treating the server as authoritative. The affected flow is GET /v1/sessions/{sessionId}/availability followed by POST /v1/holds. The availability response is only a snapshot, so the UI may show recent information without promising that a seat is reserved. When the user selects seats, the atomic hold request still decides the winner. If the snapshot is stale, or another user already took the seat, the API returns 409 and the client refreshes availability.

On the browser side, I would keep AbortController and the request ID guard. A newer availability request cancels or supersedes an older one, so late responses cannot overwrite fresher state. Cache rules can still use ETag and conditional requests when the existing HTTP cache contract allows them.

Correctness does not move into the browser. Authentication and authorization stay unchanged. The main downside is more read traffic and more visible seat changes for users during heavy contention.

How do you handle a timeout after the user clicks Book and the browser does not know whether booking succeeded?

I would retry the same booking request with the same Idempotency-Key. The affected endpoint is POST /v1/bookings with holdId and priceQuoteId. The browser must not create a new key for that retry, because that would represent a new booking attempt. With the same key, the API returns the original canonical booking result for that booking attempt.

The UI stays in a recoverable state while the outcome is uncertain. It must not assume success from local state, and it must not assume failure just because the first response was lost. The client still checks status, content type, JSON, and schema before showing success.

Authentication, HTTPS, CORS, and the trusted authorization boundary do not change. The hold and price rules also stay the same. The main downside is that the client must preserve the Idempotency-Key for the life of that booking attempt and clearly separate a retry from a brand-new booking action.

15. Design a data-driven component and validation API for a PC configuration wizard.API DesignHardMicrosoft

Question Details

A React wizard presents configurable components in steps, and each selection can make later choices compatible, incompatible, required, or unavailable. Design the public component and data-source contracts without hardcoding compatibility rules inside individual step components. Define component, option, step, rule, selected-configuration, validation-result, price-summary, and revision shapes; controlled state and callbacks; synchronous versus asynchronous rule evaluation; cancellation; and error records. Specify initial, loading, valid, invalid, unavailable, revisiting, completed, and stale-rule states; what happens when catalog data changes or a selected option disappears; and how validation messages identify the exact conflicting choices without exposing executable rule text. Include keyboard and accessible error behavior, compatibility/versioning, test fixtures, and one exact selection sequence with the validation events it emits.

Short Interview Answer (30-60 seconds)

I would keep the React steps data-driven and keep compatibility rules outside individual step components. The parent owns the selected configuration, step index, revision, loading state, and validation messages. A selection updates the controlled value, then the browser evaluates already-loaded rules or asks the remote PC configuration API when remote validation is needed. I use AbortController plus requestId and revision checks so obsolete work cannot overwrite newer state. Before updating the UI, I check the HTTP result, content type, JSON parsing, and runtime schema. The UI supports initial, loading, valid, invalid, unavailable, revisiting, completed, and stale-rule states. Conflict messages identify the exact component and option choices without exposing executable rule text. HTTPS and the declared CORS and credential contract protect the browser boundary.

Detailed Explanation

This wizard lets a person choose PC parts one step at a time. An earlier choice can change which later choices are allowed. The main design goal is to keep those compatibility decisions outside each React step. The parent owns the current configuration and visible state. A shared data layer reads catalog and rule data, evaluates simple rules, and requests remote validation when needed. Every result carries revision information so old data can be detected. The browser also validates remote responses before using them. This keeps the UI predictable when requests overlap, catalog data changes, or a selected option disappears.

Useful Questions to Ask the Interviewer
  • Which operations in the data-source contract require the remote API?
  • Which catalog reads may use HTTP caching, revision values, or ETags?
  • What browser authentication mechanism is used, if authentication is required?
  • Can any compatibility rule depend on remote or external data?
  • Which supported browsers must provide AbortController behavior?
Design a data-driven component and validation API for a PC configuration wizard. diagram
How to Explain It in an Interview
1. Define the browser contract

The React wizard is controlled by its parent. The controlled value is value or selectedConfiguration.

The parent receives these callbacks: onChange(nextConfiguration), onValidationResult(result), onComplete(configuration), and onError(errorRecord).

The parent also owns stepIndex, revision, and loading or error state. Individual step components only render data and report user choices. They never hardcode compatibility rules.

The public data shapes match the diagram.

Component contains id: string, name: string, category: string, optional description, optional imageUrl, and version: string.

Option contains id: string, componentId: string, name: string, attributes: object, price: Money, status: 'active' | 'retired', and tags: string[].

Step contains id: string, title: string, componentId: string, order: number, isRequired: boolean, allowMultiple: boolean, and dependsOn: StepRef[].

Rule contains id: string, type: 'incompat' | 'require' | 'limit' | 'unavail', when: RuleClause, then: RuleEffect, messageKey: string, and severity: 'error' | 'warn'.

SelectedConfiguration contains revision: string, selections: { [componentId]: optionId[] }, context: object, and an ISO 8601 updatedAt value.

ValidationResult contains revision: string, isValid: boolean, errors: ErrorRecord[], warnings: ErrorRecord[], unavailable: Unavailable[], and nextSteps: StepState[].

PriceSummary contains currency: string, subtotal: number, discounts: number, tax: number, total: number, and breakdown: LineItem[].

Revision contains id: string as the version identifier, catalogRevision: string, optional compatibleFrom, optional deprecatedAfter, and optional notes.

ErrorRecord contains a code, messageKey, a user-safe message, severity, conflict references, and target information. Each ConflictRef contains the conflicting componentId and optionId, plus selection and label information.

The remote PC configuration API stays one external boundary. The approved design does not define concrete URLs, methods, or status codes. I would keep those details inside the data-source implementation rather than inventing them in the public React contract.

2. Start and control the request

Work starts when the wizard loads, the catalog refreshes, or the user selects or changes an option.

The request builder reads the current selected option ids. It also reads step context, filters, and whether pricing is needed.

The UI enters loading while remote work is pending. When a newer action makes old work useless, the client cancels the old request with AbortController when possible.

Cancellation alone is not enough. An older response may already be returning. Each request therefore also has a requestId and current revision. Only the newest matching request may update controlled state.

This prevents a slow result for an old choice from replacing a newer result.

Simple rules can run synchronously. That means a pure rule is evaluated immediately against catalog and rule data already loaded in the browser.

Remote or external-data validation is asynchronous. It returns a Promise, accepts an AbortSignal, and is protected by the same requestId and revision checks.

The design does not assume polling, WebSockets, server-sent events, or another transport. Those would require an explicit data-source contract.

Repeated user actions are handled by canceling obsolete work and rejecting stale results. I would not claim that every remote operation is safe to retry. A mutation would need an explicit retry or idempotency contract.

3. Validate the response

Fetch sends an HTTPS request across the single remote API boundary. The API later returns an HTTP response and JSON body to the browser client.

Fetch normally resolves when an HTTP response arrives. It does not reject merely because that response represents an application error. The client must inspect the HTTP result first.

The response handler then checks the expected content type. After that, it parses JSON safely.

Parsing is not enough. The client also performs runtime schema validation before trusting the returned object. TypeScript types help developers, but they do not validate network data at runtime.

Validated data moves into the normalize-and-derive stage. That stage builds view models, price summaries, and step availability.

The state updater changes controlled state only after validation succeeds. A response with an obsolete requestId or revision is ignored instead.

4. Handle visible states and failures

The wizard uses explicit states.

initial means the wizard mounted, but no configuration has been validated yet.

loading means validation or data work is still pending.

valid means the current selections are compatible.

invalid means validation found conflicting choices. The UI shows user-safe messages that point to those exact choices.

unavailable means an option cannot currently be selected. The control is unavailable and shows a reason.

revisiting means the user moved back to an earlier step. The configuration is revalidated because an earlier change can affect later choices.

completed means the configuration has passed validation and can be sent to onComplete(configuration).

stale-rule or stale-data means the catalog revision changed, so an earlier validation result can no longer be trusted.

Validation errors identify conflicts through ConflictRef records. Those records name the exact componentId and optionId choices involved. User-visible messages use a safe message or messageKey. They never reveal executable rule expressions or rule source text.

When catalog data changes, the client compares revisions. If the revision changed, it marks affected validation stale and revalidates.

If a previously selected option disappears, the client preserves an error identifying that exact selection. Completion is blocked. The user must choose a replacement or explicitly clear the missing selection before continuing.

Authentication, authorization, validation, retryable, and final-error classifications are used only when the data source actually returns those meanings. The client does not invent unsupported status-code mappings.

5. Protect the browser boundary

Remote communication uses HTTPS.

The browser follows the deployment's same-origin and CORS rules. CORS controls whether browser JavaScript may read certain cross-origin responses. It is not authentication or authorization.

Credentials or tokens are handled according to the declared API contract. Browser JavaScript must never contain a client secret, private key, or other privileged credential.

If credentials are sent automatically by the browser, such as cookies, the authentication design must also define suitable CSRF protection. I would confirm that contract instead of assuming one authentication model.

Authorization remains a trusted-server responsibility. Disabling an option or hiding a button in React improves the interface, but it does not provide authorization security.

6. Handle caching, revisions, and compatibility

The browser may use normal HTTP caching for catalog data when the remote contract allows it. The diagram also shows revision or ETag-based freshness information.

The client respects the remote cache contract. It does not invent its own freshness guarantee. A fresh cached response can be used when the contract permits it.

When a revision changes, rule results based on the old revision become stale. Revalidation then protects correctness.

Version evolution should prefer backward-compatible additive changes. Existing field meanings should remain stable when possible.

Revision metadata can describe compatibility and deprecation. This lets an older frontend understand whether a newer catalog remains usable.

A retired or removed option is treated as data. The step component does not contain special hardcoded logic for that option.

7. Support keyboard and accessible errors

All selectable options must be keyboard focusable.

Arrow keys move within an option group. Enter or Space selects an option.

New validation errors are announced through an aria-live region. This lets a screen-reader user hear the problem without searching the page.

When validation blocks progress, focus moves to the first invalid control. Each error message is programmatically associated with its conflicting control.

Unavailable choices also need a readable reason. The UI must not communicate availability through color alone.

This behavior belongs in shared wizard components. Each PC-part step should not invent its own keyboard or error behavior.

8. Verify the exact behavior

I would build deterministic test fixtures for components, options, steps, rules, selected configurations, validation results, price summaries, revisions, errors, and conflict references.

I would include the exact selection sequence shown in the approved diagram.

1. Select CPU cpu-1. Validation emits VALID after selection 1. 2. Select motherboard mb-a. Validation emits VALID after selection 2. 3. Select memory ram-1. Validation emits VALID after selection 3. 4. Change the CPU to cpu-2, which conflicts with mb-a. Validation emits INVALID after selection 4 and exposes the conflict references for those exact choices.

Unit tests cover pure synchronous rule evaluation.

Component tests cover controlled callbacks, initial and loading states, keyboard selection, focus movement, and accessible errors.

Network integration tests cover HTTP result handling, content-type checking, JSON parsing, runtime schema validation, cancellation, and stale-response rejection.

Revision tests change catalog data during the flow. They verify that results become stale and are revalidated.

A missing-option test removes a selected catalog option. It must produce an error for that exact old selection, block completion, and recover only after replacement or explicit clearing.

Practical Complexity & Trade-offs

The main cost is browser coordination. Each selection may run local rules and may also start remote validation. Local work depends on how many relevant rules need checking. Network cost depends on request count and response size. Runtime schema checks add a small amount of parsing work, but they stop malformed data from reaching the UI. AbortController reduces wasted remote work. The requestId and revision checks protect against old responses even when cancellation is too late. HTTP caching can reduce repeated catalog downloads, but cached information may become old. Revision checks handle that freshness problem. Keeping compatibility rules outside React steps makes each step smaller and easier to test. The trade-off is a more capable shared data and state layer. Accessibility also adds work, but shared keyboard and error behavior prevents every step from reimplementing it. Security remains at the browser boundary: use HTTPS, obey the declared CORS and credential contract, expose no secrets in JavaScript, and leave trusted authorization to the server.

Why Interviewers Ask This

The interviewer is testing whether I can separate UI components from changing business rules. They also want correct controlled-state modeling, request and response handling, cancellation, stale-response protection, and useful error contracts. Browser HTTP behavior and security ownership matter too. Revision handling shows whether the design survives changing catalog data. Accessibility and deterministic fixtures show engineering discipline. The goal is to judge API design choices and trade-offs, not whether I memorized one framework pattern.

Interviewer may ask next
What changes if catalog data updates while the user is halfway through the wizard?

I would keep the architecture and make revision handling the main correctness guard. The affected flow is the catalog response, Revision, SelectedConfiguration, ValidationResult, and the stale-rule UI state. When catalog data arrives with a different revision, the client marks validation based on the previous revision as stale. It does not silently keep showing the old result as valid. The browser revalidates the current selected configuration against the new catalog and rules. If every selected option still exists and remains compatible, the wizard can return to valid. If one selected option disappeared, the client keeps an ErrorRecord that identifies that exact componentId and optionId. Completion remains blocked until the user selects a replacement or explicitly clears the old choice. Older remote responses are still rejected through requestId and revision checks. HTTPS, response schema validation, CORS handling, and the declared credential contract do not change. The main downside is extra validation work and a possible interruption for the user, but that is safer than completing a PC configuration with stale compatibility information.

How would you test cancellation, stale responses, and accessible validation errors?

I would test the existing browser flow with deterministic fixtures and controlled response timing. The affected parts are the React wizard, Abort/Stale Guard, response handler, controlled callbacks, and accessible error UI. First, I would start validation for one selection and immediately change that selection. The test confirms that the obsolete request receives an abort signal when possible. I would also allow an older mocked response to arrive after the newer response. The client must ignore it because its requestId or revision is no longer current. Next, I would run the exact cpu-1, mb-a, ram-1, then cpu-2 fixture sequence. The last event must be INVALID, with conflict references naming the exact CPU and motherboard choices. The message must remain user-safe and must not expose executable rule text. A component test verifies aria-live announcement, focus movement to the first invalid control, and programmatic association between the message and that control. The downside is more detailed test setup, but it catches race conditions and accessibility failures that happy-path tests miss.

16. Design frontend monitoring for application performance.System DesignEasyMicrosoft

Question Details

Design a browser-side performance-monitoring architecture for a multi-route JavaScript application. Define which navigation, rendering, interaction, long-task, resource, API, and JavaScript-error signals are collected; how page, route, release, browser, device, and network context is attached; and which measurements come from platform observers versus explicit application marks. Cover initialization, sampling, consent and privacy, payload size, batching, page lifecycle, offline periods, duplicate events, source-map access, and cleanup. Explain dashboards, alert thresholds, release comparison, investigation from a high-level symptom to a reproducible trace, and how the monitor limits its own CPU, memory, and network overhead. Include test seams and a rollout plan that verifies the monitoring code does not materially worsen the experience it measures.

Short Interview Answer (30-60 seconds)

At a high level, I would measure real user performance inside the browser without making the application noticeably slower. The monitor starts after consent and configuration are ready. It collects navigation, rendering, interaction, long-task, resource, API, and JavaScript-error signals. It adds route, release, browser, device, and network context. Events are sampled, batched, compressed, deduplicated, and sent to an external monitoring backend. The trade-off is getting useful detail while keeping CPU, memory, network, and privacy costs very small.

Detailed Explanation

The goal is to understand how the JavaScript application performs for real users across routes, browsers, devices, and network conditions. The main challenge is that monitoring code also consumes browser resources. I would therefore keep collection small, privacy-aware, measurable, and easy to disable. The browser gathers platform performance entries and explicit application marks. It attaches useful context, prepares a compact telemetry envelope, and sends it to an external Performance Monitoring Backend. Dashboards, alerts, release comparisons, and restricted source-map lookups then help us investigate problems.

Useful Questions to Ask the Interviewer
  • Which browsers and devices matter most?
  • Must consent be granted before any telemetry is collected?
  • How long should offline telemetry remain in browser storage?
  • Which performance thresholds should trigger alerts?
  • What monitoring overhead is considered acceptable?
  • How quickly must a bad monitoring release be rolled back?
Design frontend monitoring for application performance. diagram
How to Explain It in an Interview
1. Initialize the monitor safely

The monitor initializes only after consent and configuration are ready. It then registers performance observers and page lifecycle hooks. The application may use CSR, SSR, SSG, streaming, and hydration. Later route changes are handled by the client router.

Sampling limits how much telemetry is collected. Feature flags control gradual rollout. A kill switch can disable monitoring quickly if overhead becomes too high.

2. Collect browser signals and application marks

Platform observers provide most measurements. Navigation Timing covers navigation and TTFB. Paint and layout observations cover FCP, LCP, and CLS. Event Timing covers supported interaction latency and INP. Long-task entries show main-thread blocking. Resource Timing covers scripts, CSS, images, fonts, XHR, and fetch activity.

For API calls, fetch or XHR resource entries provide timing information. Application code can emit error or custom events when requests fail. JavaScript failures include window error events and unhandled promise rejections.

Explicit application instrumentation is kept separate. performance.mark() and performance.measure() record route or business milestones that the browser cannot infer automatically.

3. Add context and protect privacy

Each telemetry envelope includes event type, timestamps, measurements, page, route, and referrer. Release information includes application version, build, or commit. Browser, device, network, locale, and time-zone context help explain why one user group is slower.

User or session identifiers should remain anonymous where possible. Consent, DNT, and data-minimization rules control collection. Experiment variants and rollout percentage are also attached when useful.

4. Deliver telemetry reliably

The client batches events by size, time, or idle periods. It also enforces a maximum payload size. Compression reduces network use. Deduplication uses an event ID to avoid repeated telemetry.

sendBeacon is preferred for page-exit delivery. fetch with keepalive is another option. When offline, events can wait in an IndexedDB queue and flush after connectivity returns. Lifecycle hooks handle visibilitychange, pagehide, and beforeunload. During teardown, the monitor flushes appropriate pending work and disconnects observers and listeners.

5. Limit monitoring overhead

The browser monitor has CPU, memory, and network budgets. Idle work reduces main-thread pressure. Bounded queues limit memory growth. Backoff and payload limits reduce network pressure.

Self Monitoring measures the monitor itself. During gradual rollout, I compare monitor-on and monitor-off users. I compare CPU, memory, telemetry payload, and user-performance metrics. If the overhead budget is exceeded, the feature flag or kill switch rolls monitoring back.

6. Detect, investigate, and verify problems

The Performance Monitoring Backend is one external boundary. Dashboards show Core Web Vitals, user experience, route performance, cohorts, and release comparisons. Alerts use thresholds or anomaly rules.

Investigation starts from a high-level symptom. I narrow it by route, segment, and time. A trace view shows timing, waterfall, and events. I then reproduce the issue using environment, route, and user actions. For JavaScript stack traces, a restricted diagnostic lookup goes to the external Source Map Service for symbolication. After the fix, I compare the new release with the previous release.

The design also has test seams. Observer, clock, lifecycle, sampler, and transport behavior can be injected. Synthetic entries let tests verify collection, batching, cleanup, sampling, and delivery without depending on real browser timing.

Engineering Considerations / Design Trade-offs

The benefit is that we can see real performance problems across routes, releases, browsers, devices, and networks. The downside is that monitoring also uses CPU, memory, storage, and network traffic. Sampling, batching, compression, bounded queues, and payload limits keep that cost small. Offline buffering improves reliability, but it adds browser storage and cleanup work. More context makes debugging easier, but too much context creates privacy risk. Gradual rollout is safer because we compare monitor-on and monitor-off users, but it takes longer before monitoring reaches everyone.

Why Interviewers Ask This

The interviewer wants to see whether you can measure frontend performance without making the application slower. They also want to see whether you can separate browser measurements from application marks, handle privacy and offline failures, control telemetry cost, and investigate regressions. A strong answer shows practical judgment about browser limits, safe rollout, debugging, and user experience rather than simply naming performance APIs.

Interviewer may ask next
What would you change if many users stay offline for several hours before reconnecting?

I would keep the same architecture, but I would put more responsibility on the existing Offline Queue. Telemetry would continue to be stored in IndexedDB while the browser has no network. The queue would stay bounded so it cannot grow forever. Old or lower-value events could be removed when the storage budget is reached.

When connectivity returns, the client would flush small batches instead of sending everything at once. Compression, sampling, payload-size limits, deduplication, and backoff would still apply. This prevents a large reconnect burst from adding heavy network or CPU work.

Lifecycle cleanup would remain separate from stored telemetry. Observers and listeners should still be disconnected during teardown. Consent and privacy rules must also be checked before queued events are sent.

The main downside is extra browser-storage logic. Long offline periods can also make old telemetry less useful, so the queue needs a clear retention limit.

How would you prove that the monitoring code does not make Core Web Vitals worse?

I would use the existing Feature Flags, Kill Switch, and Self Monitoring controls for a gradual experiment. One comparable group would run with monitoring enabled. Another would run without it. I would compare CPU use, memory use, telemetry payload size, and user-performance metrics such as LCP, CLS, and INP.

The client already has CPU, memory, and network budgets. Those budgets become rollout guardrails. If the monitor-on group becomes meaningfully slower, I stop increasing the rollout percentage. If the overhead budget is exceeded, I use the kill switch to disable monitoring.

Before production, the existing test seams also help. I can inject fake observers, clocks, lifecycle events, samplers, and transports. Synthetic performance entries verify collection, batching, cleanup, sampling, and delivery.

The main downside is rollout speed. This approach takes longer than enabling monitoring for every user immediately, but it gives much safer evidence.

17. Design a way to monitor frontend project size as features are added.System DesignEasyMicrosoft

Question Details

A frontend gains routes, components, and dependencies every release, and the team needs to detect harmful size growth before it reaches users. Design a size-governance system that measures compressed entry and route chunks, lazy chunks, CSS, fonts, images, duplicate modules, and dependency contribution from reproducible production builds. Define baselines, budgets, historical trends, ownership, pull-request and release checks, exceptions, and how dynamic imports and shared chunks are attributed without double counting. Cover source-map and build-manifest handling, cache effects, a deliberately large feature, false alarms caused by tooling changes, and rollback. Explain how build measurements connect to actual bytes loaded on representative user journeys so a smaller aggregate bundle does not hide a worse critical path.

Short Interview Answer (30-60 seconds)

At a high level, I would make frontend size a release quality check. The main constraint is that total bundle size can hide a slower important route. This design is independent of CSR or SSR because it measures the production files we actually ship. A reproducible build feeds a size analyzer, which checks compressed chunks, assets, dependencies, budgets, and history. Pull requests and releases can warn or fail. I also compare representative browser journeys, including cold and warm cache behavior, so actual user bytes remain the final check.

Detailed Explanation

The goal is to catch harmful frontend growth before users receive it. The hard part is that one total bundle number can be misleading. A release may become smaller overall while making an important route much heavier. I would therefore measure reproducible production builds and also measure the bytes loaded by representative browser journeys. This approach does not require a special CSR, SSR, or SSG choice. It measures the output produced by the application's existing production build and connects those measurements to what browsers actually load.

Useful Questions to Ask the Interviewer
  • Which routes are most important to users?
  • Which devices and network speeds should we represent?
  • Should a budget breach block pull requests, releases, or both?
  • How long may an approved size exception remain active?
  • Who owns shared chunks and third-party dependencies?
Design a way to monitor frontend project size as features are added. diagram
How to Explain It in an Interview
1. Create a reproducible production build

A code change goes through the Git provider into the CI/CD pipeline. The pipeline performs a reproducible install and a deterministic production build. It collects JavaScript, CSS, fonts, images, WASM, and other media.

The build also produces a build manifest, bundle statistics, source maps, and hashed assets. These artifacts let the analyzer map generated files back to modules and dependencies.

2. Measure size without double counting

The size analyzer records gzip or Brotli compressed sizes. It measures entry chunks, route chunks, lazy chunks, CSS, fonts, images, duplicate modules, and dependency contribution.

Shared code is counted once in the shared total. Shared chunks are allocated by usage, such as initial versus lazy loading. A dynamic import is attributed to the route that introduces it. Shared CSS is also counted once. Dependencies are attributed to the package that introduces them.

3. Apply budgets, ownership, and checks

The size data store keeps per-build measurements, baselines, budgets, historical trends, ownership mappings, exceptions, and annotations. Governance can define entry, route, global, component, and dependency budgets.

CODEOWNERS, route ownership, and dependency ownership identify the responsible team. Pull-request checks can warn or fail. Releases have a separate gate, and nightly checks can detect slower regressions.

A deliberately large feature can use a time-limited exception. It needs a reason, review, and automatic expiry.

4. Connect build size to real user bytes

Representative journeys load the application on realistic devices and networks. They test cold and warm cache states and can include service-worker behavior when the application uses one.

The browser records transferred bytes per resource, requests, TTFB, FCP, LCP, INP, CLS, errors, and offline success where relevant. The system maps those resources back to build chunks. This prevents a smaller aggregate bundle from hiding a worse critical route.

5. Handle false alarms and rollback

Tool changes can alter chunk names or measurements without changing user experience. I would lock tooling versions, keep builds reproducible, detect tooling-only changes separately, and annotate major upgrades.

Dashboards show route growth, dependency contribution, trends, budget status, and release tracking. If a harmful change reaches release, the team can revert it, pin a dependency version, or restore the previous baseline. The main tradeoff is stricter control versus extra build work and occasional approved exceptions.

Engineering Considerations / Design Trade-offs

The benefit is that the team finds size problems before users see them. Route budgets show where growth happened, while ownership shows who should review it. Real browser journeys are important because build totals alone can hide a worse critical path. The downside is extra build and measurement work. Strict budgets can also block a feature that is intentionally large. Time-limited exceptions help, but they need review. Tool upgrades can create false alarms, so build versions should be locked and major tooling changes should be measured separately.

Why Interviewers Ask This

The interviewer wants to see whether you can measure the right user impact instead of watching one bundle number. They also want to see how you handle shared code, budgets, ownership, caching, tooling changes, exceptions, and rollback. A strong answer shows practical judgment and connects build measurements with the bytes users actually load.

Interviewer may ask next
What would you change if an important new feature must exceed its route budget for two releases?

I would keep the same architecture and use the Exceptions part of Size Governance & Policy. The feature owner would request a time-limited exception with a clear reason. The record would identify the affected route, expected increase, owner, and expiry date.

The normal size analyzer would still measure every build. Pull-request and release checks would still show that the route is above budget. The approved exception would only stop that known increase from blocking the release. Other unexpected growth would still warn or fail normally.

I would also keep measuring the representative user journey. If the larger route makes transferred bytes or important performance metrics much worse than expected, the team can still stop or roll back the release.

The exception should expire automatically. After two releases, the team must reduce the size or review the budget again. The downside is that too many exceptions can weaken the governance system.

How would you handle a build-tool upgrade that suddenly changes many size measurements?

I would treat the tooling upgrade as a measurement change first, not immediately as a product regression. The Noise Reduction & Accuracy part of the diagram already locks tooling versions and separates tooling impact.

I would build the same source revision with the old and new tool versions. Then I would compare compressed sizes, bundle structure, dependency contribution, and the representative browser journeys. If the build numbers move but transferred user bytes and performance remain similar, the change is probably caused by the tool.

I would annotate the release and major upgrade in the historical data. I would not silently overwrite the old baseline. After validating the new measurements, I would establish a reviewed baseline for the new tooling version.

Normal pull-request and release checks would continue during the migration. Real user-journey regressions would still be treated as failures. The downside is extra comparison work during major tooling upgrades.

18. Design a modular Copilot frontend that can plug into multiple host applications.System DesignHardMicrosoft

Question Details

Design the frontend architecture for one Copilot experience that must be embedded in several independently released web applications. Define a host-neutral core, rendering and lifecycle boundary, and versioned adapters for authentication, user and document context, commands, navigation, theming, localization, telemetry, and feature policy. Compare build-time packaging with runtime loading where relevant; address shared dependencies, framework differences, CSS and DOM isolation, host and feature state ownership, permissions, accessibility, performance budgets, and compatibility across host versions. Cover initialization, unavailable capabilities, partial loading, feature failure, sign-out, route changes, cleanup, observability, testing with contract fixtures, staged rollout, and rollback. Explain how host applications can extend supported behavior without reaching into private feature internals or forking the core.

Short Interview Answer (30-60 seconds)

At a high level, I would build one host-neutral Copilot frontend that several independently released web applications can embed. The main challenge is keeping Copilot stable while each host has its own framework, routes, theme, permissions, and release cycle. I would use a client-rendered Copilot Core, a framework-neutral mount and unmount boundary, versioned host adapters, and lazy-loaded feature modules. Host capabilities enter through adapters, while the core owns Copilot UI state. Strong contracts add integration work, but they prevent private host coupling and forks.

Detailed Explanation

The goal is to give users one consistent Copilot experience inside several independently released web applications. The difficult part is keeping the Copilot Core independent from each host framework, route system, authentication model, theme, permissions, and release schedule. I would make the Copilot surface client-rendered inside the browser and expose a framework-neutral mount and unmount boundary. Versioned host adapters provide host capabilities. Lazy-loaded feature modules keep the initial shell small. Clear state ownership, capability checks, isolation, observability, contract tests, and safe rollout controls keep the integration reliable.

Useful Questions to Ask the Interviewer
  • Which browsers and devices must we support?
  • Can the host applications use different frontend frameworks?
  • Which host capabilities are required and which are optional?
  • Must Copilot work when document context or commands are unavailable?
  • What accessibility and localization targets are required?
  • How independently must Copilot and host applications release?
  • How much offline behavior is expected?
Design a modular Copilot frontend that can plug into multiple host applications. diagram
How to Explain It in an Interview
1. Define the host and lifecycle boundary

Each Host App embeds the same Copilot Core. The Copilot surface is client-rendered in the browser. The diagram does not require SSR, SSG, or hydration, so I would not make the core depend on those host choices.

The Public Surface is a stable contract. It provides a framework-neutral mount and unmount lifecycle API. This lets different host frameworks integrate without reaching into private Copilot components.

The core also isolates its DOM and CSS. Shadow DOM is one option. Strictly scoped styles are another. The important rule is that Copilot must not depend on private host DOM.

2. Use versioned adapters for host-owned capabilities

The Host Adapter SPI is the versioned interface between Copilot and each host. It covers Authentication, User & Profile, Document Context, Commands, Navigation, Theming, Localization, Telemetry, and Feature Policy.

Each host implements its own adapter version. The host therefore owns authentication state, user and document context, navigation, theme, locale, and policy. The Copilot Core consumes those values through public contracts.

Feature Policy contains entitlements and capabilities. The frontend may hide or disable unsupported actions for a clear user experience. Trusted remote systems must still enforce real authorization.

3. Keep the Copilot Core modular and own feature state

The UI Shell contains Chat Panel, Prompt Composer, Suggestions & Actions, References & Citations, Settings & Preferences, and State Indicators. A shared Design System supplies themes, tokens, icons, typography, and motion.

Feature Modules such as Chat, Summarize, Explain, Generate, and Rewrite are lazy loaded. Lazy loading means downloading a feature only when it is needed. Extensions use the Public Plugin API for tools, extensions, and commands. They must not access private core internals or fork the core.

The Copilot Core owns Local UI State and feature state. URL State stores deep links and query parameters. Shared Client State holds cached query data and normalization. Persisted State uses localStorage or IndexedDB for user preferences and drafts when appropriate.

4. Explain initialization, data flow, and route changes

Initialization starts with capabilities and configuration. The core authenticates through the adapter, loads the minimal shell, detects features and policy, lazy loads allowed features, and then becomes interactive.

Remote Data is fetched through the external boundaries shown in the diagram: Identity Provider, Content / Data APIs, AI / Copilot Service, and optional Third Party Services. Their internal server architecture stays outside this frontend design.

Route changes notify the core. Host events also provide context updates. This keeps the embedded experience synchronized without reading private host state directly.

5. Handle partial loading, failures, sign-out, and cleanup

The UI has explicit Loading, Empty, Partial / Degraded, Error, Stale, Offline, and Unavailable capability states. Stale means the displayed data may be older than the latest remote result.

If a capability is unavailable, the core hides or disables the affected feature and gives a clear reason. If one lazy-loaded feature fails, the Error & Status Manager can keep the rest of the shell usable instead of failing the whole experience.

On route changes, active work should be cancelled when it is no longer useful. On unmount or sign-out, the Lifecycle Manager removes listeners, clears sensitive feature state, and cleans active work. Offline Support may queue suitable actions and perform background synchronization where browser rules allow it.

6. Deliver, measure, test, and release safely

The delivery path uses Source Code, Build, Code Splitting, static Assets, CDN delivery, Browser Cache, and a Service Worker. The service worker supports caching, updates, and selected offline behavior. Performance budgets track measures such as LCP, INP, CLS, and TTI.

A build-time package is simpler because dependencies are resolved with the host build, but releases are coupled to that host. A runtime-loaded module supports independent rollout, but it needs strict version and compatibility contracts. Shared dependencies need explicit compatible versions. The core should avoid leaking or requiring the host framework, and singleton dependencies should be used only when the contract makes that safe.

Operational Excellence includes error reporting, logs, traces, performance measurement, feature flags, staged rollout, compatibility checks, and safe rollback. Adapter contract tests use host-version fixtures. A feature flag or kill switch can disable a bad feature without forcing hosts to fork or modify private Copilot code.

Engineering Considerations / Design Trade-offs

The benefit is that one Copilot Core can work across many host applications. Versioned adapters keep host-specific behavior outside the core. The downside is more contract and compatibility work. Build-time packaging is simpler, but Copilot releases become tied to each host release. Runtime loading allows faster independent rollout, but version checks and shared dependency rules become more important. DOM and CSS isolation protect Copilot from host styles, but theming needs a clear public contract. Lazy loading reduces the first JavaScript download, but a feature can take longer the first time a user opens it.

Why Interviewers Ask This

The interviewer wants to see whether you can separate shared frontend code from host-specific behavior. They also want to see how you handle state ownership, framework differences, permissions, failures, accessibility, performance, compatibility, and independent releases. The key skill is making stable boundaries and explaining tradeoffs. A strong answer shows how several teams can extend one frontend safely without private coupling or forks.

Interviewer may ask next
What would you change if one host cannot provide several capabilities that Copilot normally uses?

I would keep the same architecture and rely on the existing Capability Registry and Feature Policy contracts. During initialization, the Copilot Core already discovers which capabilities the host adapter supports. If a host cannot provide Document Context, Commands, or another optional capability, the core should not guess or read private host state.

Instead, the missing capability is recorded as unavailable. The related feature can be hidden or disabled. If the user needs an explanation, the UI uses the Unavailable capability state and gives a clear reason. Features that do not depend on that capability can still load normally, so the experience becomes partial or degraded instead of completely broken.

This stays correct because each host still uses the same versioned Host Adapter SPI. Contract tests should include fixtures for hosts with missing capabilities. Real permissions still need enforcement by trusted remote systems.

The main downside is that users may get different Copilot features in different hosts.

How would the design change if Copilot must be released independently from every host application?

I would keep the same Copilot Core and Host Adapter SPI, but I would favor the runtime-loaded module option shown in Packaging & Delivery. The host would load a compatible Copilot version at runtime instead of bundling that exact version into every host build.

The biggest change is stronger compatibility control. The runtime module and the host adapter must agree on supported contract versions before mounting. Shared dependencies need explicit compatible versions. I would avoid requiring the host framework inside the Copilot Core. A singleton dependency should be shared only when its contract is safe across versions.

Adapter contract tests with host-version fixtures would run before rollout. Feature flags would support staged release. Error reporting and performance monitoring would show whether the new version behaves correctly. If problems appear, a kill switch or rollback path can disable the affected feature.

The downside is more runtime loading, version negotiation, monitoring, and failure-handling complexity.

19. Design a ChatGPT-style streaming frontend.System DesignHardMicrosoft

Question Details

Design the browser architecture for a conversational AI interface whose responses arrive incrementally through Server-Sent Events. Define application-shell, conversation-list, thread, composer, streaming-message, transport-adapter, storage, and authentication boundaries; normalize conversation and message identity; and describe how partial text, completion, cancellation, retry, and errors move through state. Cover initial history, very long threads with virtual scrolling, browser-storage limits and migration, reconnection, duplicated or out-of-order events, a new prompt while another stream is active, route changes, sign-out, and stale completions. Include safe rendering, keyboard and screen-reader behavior during streaming, main-thread and memory budgets, API-contract versioning, observability, and test seams. Keep model execution and unrelated backend storage internals outside scope except for contracts the browser requires.

Short Interview Answer (30-60 seconds)

At a high level, I would build a browser app that keeps conversations responsive while assistant text arrives in small streaming updates. The main challenge is handling partial data without showing duplicate, stale, or out-of-order text. I would use an Application Shell with a virtualized Thread, Composer, shared Client State, and a fetch-based SSE Transport Adapter. IndexedDB keeps larger local data. ARIA live regions support accessibility. The trade-off is more browser logic for better streaming, offline use, and long-thread performance.

Detailed Explanation

The goal is to make a conversational AI page feel fast and predictable. A user should open old conversations, send a prompt, watch the answer arrive, stop it, retry failures, and keep working on a weak connection. The hardest frontend problem is streaming state. Events can arrive twice, arrive late, or belong to an older request. I would separate the application shell, conversation UI, state layers, streaming transport, persistence, and external services. The browser owns presentation and client state. Remote systems stay outside the browser boundary.

Useful Questions to Ask the Interviewer
  • Which browsers and device sizes must we support?
  • How important is offline reading or queued sending?
  • Can a user start a new prompt while another stream is active?
  • What accessibility, localization, and storage limits matter?
Design a ChatGPT-style streaming frontend. diagram
How to Explain It in an Interview
1. Start with the browser application

The app is a client-side browser application delivered through the CDN path. The Application Shell owns the top bar and main conversation experience.

The main UI has the Conversation List, Thread, and Composer. The Thread is virtualized. That means only nearby messages stay in the DOM. This keeps very long conversations responsive and limits memory use.

The layout is responsive and localized. Safe Rendering turns supported message content into sanitized HTML. This blocks generated text from becoming executable page content.

2. Separate each kind of state

URL State keeps the conversation id and selected message. UI State keeps input text, focus, panel sizes, and modals.

Client State keeps normalized conversations and messages by id. It also tracks the active requestId and stream status. Remote Data represents the server source of truth from REST and SSE. Persisted State uses IndexedDB plus localStorage with versioned migration.

IndexedDB stores larger conversation and message data. localStorage stores small preferences, theme, and tokens. Eviction and compaction manage browser storage limits.

3. Stream one answer safely

The Transport Adapter sends a POST with fetch and parses text/event-stream. Each versioned event includes eventId, seq, conversationId, messageId, and requestId.

The adapter validates the version, removes duplicate eventIds, and orders events by seq. Partial deltas update the current message. A done event completes it. An error shows retry behavior.

If the connection drops, the adapter reconnects with backoff and lastEventId. Replayed events are safe because duplicates are ignored.

4. Handle cancellation and stale work

Stopping a stream marks it aborted. Starting a new prompt cancels the old stream first.

A route change preserves useful state and lazy-loads the next conversation. Sign-out clears local state and revokes tokens. A late completion is ignored unless its requestId matches the active stream. This prevents stale work from changing the current UI.

5. Keep the experience usable and measurable

Keyboard users send with Enter and stop with Escape. Focus stays predictable. ARIA live regions support screen readers during streaming.

The virtualized Thread limits DOM size. Token updates are batched to reduce long main-thread work. I would monitor INP, long tasks, errors, sessions, analytics, and feature usage.

The Service Worker caches the app shell and static assets. It supports cached offline reading and queued background work. Feature Flags support gradual rollout and rollback.

Engineering Considerations / Design Trade-offs

The benefit is a responsive chat experience. Users see text as soon as it arrives instead of waiting for the full answer. Virtual scrolling also keeps long conversations fast. The downside is extra browser logic. We must track requestId, eventId, sequence order, retries, cancellation, and stale streams. Offline support adds more work because IndexedDB and Service Worker data need migration, eviction, and cleanup. Batching token updates protects the main thread, but it can add a tiny display delay. Gradual rollout reduces release risk, but it adds feature-flag and testing work.

Why Interviewers Ask This

The interviewer wants to see whether the candidate can break a complex browser experience into clear responsibilities. They want good judgment around streaming state, identity, cancellation, long lists, offline data, accessibility, performance, and failures. They also want to see whether the candidate keeps frontend work separate from external services and can explain important trade-offs clearly.

Interviewer may ask next
What would you change if conversations could contain hundreds of thousands of messages?

I would keep the same architecture, but I would make the Thread and persistence rules more aggressive. The virtualized Thread already avoids rendering the whole conversation. I would also load older messages in pages instead of keeping every message object in active Client State.

IndexedDB would still hold larger local conversation data, but eviction and compaction would remove old ranges as storage pressure grows. Remote Data from the REST API remains the server source of truth for history that is no longer local.

Client State would keep the active message window, normalized identities, and the current stream status. URL State would still identify the conversation and selected message. Streaming messages would continue through the same Transport Adapter.

Correctness stays the same because conversationId and messageId remain stable identities. The main downside is more paging and cache-management logic. Jumping to an old message may also need a short loading state.

How would the design handle a network that disconnects several times during one streamed answer?

I would keep the same Transport Adapter, but I would rely more heavily on its reconnect state. When the network drops, the partial message stays visible. The adapter waits with backoff, then reconnects using lastEventId.

The remote stream may replay events after reconnection. That is safe because the browser removes duplicate eventIds and orders accepted events by seq. The requestId must also still match the active stream. If the user starts another prompt, cancels the stream, changes route state, or signs out, a stale completion cannot update the current UI.

The Streaming Message state can show a connection or retry state while preserving partial text. Observability records reconnects and errors for debugging.

The main downside is more transport-state complexity. A long outage may eventually stop automatic retries and show a clear retry action instead.

20. When would you choose client-side, server-side, or hybrid rendering for a web application?System DesignMediumMicrosoft

Question Details

A production frontend contains public landing pages, authenticated application routes, personalized data, and interactive forms. Design a route-by-route rendering strategy rather than selecting one mode for the entire product. Compare client-side rendering, server-side rendering, and a hybrid approach across first useful content, discoverability, personalization, cacheability, browser work, hydration or client startup, navigation, deployment complexity, and failure recovery. Define component and state boundaries, what data is present in the first response, how authenticated content avoids shared-cache leaks, and how loading, error, JavaScript-disabled, and hydration-mismatch cases behave. Include accessibility and browser-compatibility expectations, measurable success criteria, rollout and rollback, and the trade-offs that would make you change the choice for a particular route.

Short Interview Answer (30-60 seconds)

The goal is to make every route fast, useful, and safe. I would choose rendering route by route instead of using one mode everywhere. Public landing pages use SSG, the authenticated dashboard uses SSR, interactive deep app routes use CSR, and search or feed pages use hybrid rendering with streaming. The browser owns routing and client state after startup. Private data must avoid shared caches. The tradeoff is better route performance but more delivery, testing, and rollback complexity.

Detailed Explanation

A production frontend has different needs on different routes. Public pages need fast first content and good search visibility. Authenticated pages need safe personalized data. Interactive tools need rich browser behavior. Search and feed pages may benefit from progressive content. I would therefore choose rendering per route instead of forcing one mode across the product. I would then define what each first response contains, where client state lives, how later navigation works, how failures recover, and how performance and rollout are measured.

Useful Questions to Ask the Interviewer
  • Which routes must be discoverable by search engines?
  • Which pages contain authenticated or personalized data?
  • Which browsers, devices, and network conditions must we support?
  • How important are offline behavior and stale cached content?
  • Which Web Vitals or other performance targets matter most?
  • How quickly must we roll back a bad rendering change?
When would you choose client-side, server-side, or hybrid rendering for a web application? diagram
How to Explain It in an Interview
1. Choose rendering route by route

For / landing, marketing, docs, and blog pages, I would use SSG. SSG creates HTML at build time. The first response contains full HTML, critical CSS, and public content. These pages cache well at the CDN and browser, and they are strong for SEO and discovery.

For /app/dashboard, I would use SSR. SSR creates HTML for each request. The first response contains the HTML shell, critical CSS, safe user data, and preloaded data. Because this content is personalized, shared caches must not reuse one user's response for another user.

For highly interactive /app/* deep routes, I would use CSR. The first response is a minimal HTML shell plus JavaScript bundles. Sensitive data is not placed in that shell. The browser fetches remote data after startup.

For /search and /feed, I would use hybrid rendering with streaming. The first response contains shell HTML. Later HTML or JSON chunks arrive progressively through streaming boundaries. This improves early paint on slower networks and reduces the effect of slow data on TTFB.

2. Start the browser and handle navigation

SSR, SSG, and hybrid pages can hydrate after HTML arrives. Hydration means attaching JavaScript behavior to existing server-rendered HTML. Client routing then handles later navigation without requiring every page transition to behave like a fresh document load.

If JavaScript is disabled, useful SSR or SSG HTML should remain readable. If server and client output disagree during hydration, the app should recover, render again, and report the mismatch.

3. Keep component, state, and data boundaries clear

The component tree follows the shared design system. Local UI state stays near the component that owns it. The diagram shows useState or useReducer as examples. Shared client state may use Context or a shared store such as Zustand. URL state holds query parameters and hashes. Remote data comes from external APIs.

Persisted browser state may use localStorage, IndexedDB, session storage, or appropriate cookies. The first response contains only what that route needs. Safe data may be dehydrated for client reuse. Secrets never belong in HTML or JavaScript. Authenticated data uses secure cookie settings such as HttpOnly, Secure, and SameSite, with CSRF protection.

4. Handle delivery, failures, accessibility, and offline use

The CDN delivers HTML and static assets. Assets can use compression, browser caching, route-level code splitting, dynamic imports, tree shaking, optimized images, optimized fonts, and cache busting. Code splitting means loading only the JavaScript needed for the current route.

Loading uses skeletons or progress indicators. Empty results show helpful next actions. Partial or stale content can stay visible while fresh data loads. Errors show a friendly message and retry option. Navigation cancels work that is no longer needed. Offline mode can show cached content, and the service worker can support caching, synchronization, and queued actions where appropriate.

The UI uses semantic HTML, ARIA where needed, keyboard navigation, focus management, responsive mobile-first layouts, RTL support, and locale-aware date and number formatting.

5. Measure, release, and change the choice safely

I would measure LCP, INP, CLS, FCP, and TTFB, plus custom metrics, traces, JavaScript and API errors, and real-user performance. Performance budgets and alerts help catch regressions.

Feature flags can control rendering per route or per user. A/B tests, canary releases, gradual rollout, and instant rollback reduce release risk. If SEO is poor, I would move a route toward SSR or SSG. If TTFB is high, I would add safe caching or streaming. If browser JavaScript work becomes too large, I would use SSR, SSG, or stronger code splitting. If shared caching risks leaking personalized data, I would vary safely by user or move that work to CSR. The downside of this hybrid strategy is greater deployment and team complexity.

Engineering Considerations / Design Trade-offs

The benefit is that each route gets the rendering style that fits its job. SSG gives very fast public pages and strong caching. SSR gives useful personalized HTML early, but shared caching must be handled carefully. CSR gives rich interaction, but the browser downloads and runs more JavaScript. Streaming shows useful content sooner when some data is slow. Code splitting reduces browser work. Offline support improves recovery, but service workers add more cases to test. The biggest downside is complexity because the team must build, measure, release, and roll back several rendering paths safely.

Why Interviewers Ask This

The interviewer wants to see whether you can choose rendering based on user needs instead of memorizing one favorite approach. They also want to test whether you understand first-page speed, SEO, personalization, caching, browser work, failure handling, accessibility, and safe releases. A strong answer shows that you can compare tradeoffs clearly and change the design when a route's requirements change.

Interviewer may ask next
What would you change if the authenticated dashboard had very high TTFB?

I would keep the same route-by-route design, but I would reduce how much work blocks the first /app/dashboard response. The route can still use SSR when personalized first content matters. I would return the useful HTML shell, critical CSS, and only the data needed immediately. Slower sections can move to streaming or later browser requests.

I would measure TTFB first to confirm the problem. Safe public assets can remain cached at the CDN and browser. Personalized dashboard HTML must still avoid unsafe shared caching.

If some dashboard areas do not need server rendering, I could move those parts toward hybrid or CSR behavior. I would also strengthen route-level code splitting so hydration and client startup require less JavaScript.

Feature flags and canary rollout would let us test the change gradually. The main downside is more loading boundaries and more partial states that must be tested.

What would you change if users needed the application to work during temporary network loss?

I would keep the same rendering choices, but I would expand the service-worker and offline behavior already shown in the design. The service worker can cache selected application assets and safe content needed during temporary network loss.

When the browser is offline, the UI should clearly show cached content instead of pretending it is current. Stale data means the displayed value may be older than the latest remote result. Supported actions can be queued and synchronized after connectivity returns when replaying those actions is safe.

The existing loading, error, partial, stale, and offline states would remain visible and understandable. Sensitive authenticated data must still follow the same browser and cookie safety rules. Secrets must never be stored in HTML or JavaScript bundles.

I would monitor offline failures and recovery with error reporting and real-user measurements. The main downside is extra service-worker, cache-version, synchronization, and testing complexity.

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.