cswork / libraryOpen practice workspace

Articles

Deliveroo Frontend Interview: React Coding and UI Architecture Preparation

Prepare for Deliveroo frontend interviews with React coding, save and retry behavior, UI architecture, testing differences, accessibility, and AI rules.

The Deliveroo frontend interview is worth preparing for as three different activities: solving a focused assessment, extending an existing React feature, and explaining how a UI would work in production. Official guidance: Deliveroo describes React debugging and REST API tasks, a supplied project for live coding, and a separate discussion based on a UI image.

The practical challenge is connecting visible behavior with state, requests, and failure handling. A checkbox that changes on screen is easy. A preference editor that saves the intended values, survives failure, and explains what happened to the user requires a clearer contract.

Separate the assessment from the live interviews

Deliveroo's official frontend guide describes a 70-minute HackerRank assessment involving React debugging and REST API work. It says platform checks evaluate submissions, without requiring candidates to write tests there.

For live coding, the guide describes extending a React sample in JavaScript or TypeScript. Open the supplied ZIP and get it running beforehand; the ZIP is preparation for the session, not a take-home assignment to complete in advance. Here, testing should be demonstrated or discussed if time is short. AI-generated submissions and live ChatGPT or Copilot assistance are prohibited.

The architecture discussion starts from a UI image and explores production concerns. Follow your invitation for exact arrangements and allowed tools.

Evidence boundary: we did not establish two independent frontend candidate reports from the same current hiring cycle. The rest of this article is editorial preparation grounded in those official task descriptions and technical documentation, not a list of confirmed questions or a universal round sequence.

Read the feature before adding behavior

In an unfamiliar project, first run the existing application and identify what already works. Locate the component, its inputs, the request helper, and any existing tests. Trace one interaction from the browser event to the state update and rendered result before changing several files.

Ask whether the supplied data is initial state, continuously refreshed server data, or a controlled value owned by a parent. These contracts produce different implementations. Copying a prop into local state once can be sensible for an editor, but it will not automatically follow later prop updates.

Inspect how the project represents loading and errors. Reuse a clear local pattern when it fits the task. Introducing a new state library for two fields creates work you must justify, while scattering duplicate status flags can make impossible combinations representable.

Explain your first implementation target in user terms: “I will let the user change two preferences and explicitly save them, then handle a failed save without discarding their choices.” That gives the interviewer a concrete behavior to evaluate as you work.

Extend a notification-preferences editor

Original practice scenario: an account page already loads two boolean preferences, email and push. Add a Save action, visible progress, and a retry path. The exercise API accepts a PUT request containing both values and returns the accepted values as JSON on success.

Clarify the contract before implementation. For the first version, only one save may be active, editing is temporarily disabled during that save, and a failed request preserves the draft. The component receives its initial values once per account; switching accounts remounts it with a new key. Background refresh and multiple-tab conflicts are later architecture questions.

Keep three kinds of state distinct: the last confirmed values, the user's draft, and the request status. Derive whether the form is dirty by comparing the two booleans. Do not store a separate dirty flag that can disagree with the values after a retry or reset.

User action or responseDraftConfirmed valuesVisible behavior
Initial loadMatches serverInitial valuesSave disabled.
Toggle emailUpdated locallyUnchangedSave enabled.
SubmitCaptured for requestUnchangedSaving message; controls temporarily disabled.
Save failsPreservedUnchangedError and retry action.
Retry succeedsAccepted responseAccepted responseConfirmation; Save disabled again.

This table is the exercise's acceptance contract. It makes the failure path as explicit as the happy path. A candidate can use it to explain their choices before writing a component, and a practice partner can use it to check the result.

For a small component, local state and a ref for an immediate submission guard are sufficient. The state drives what renders; the ref prevents a second submission from entering the handler before a render updates disabled controls. Neither replaces server-side concurrency controls.

Explain the save handler precisely

Here is the core of the original implementation. draft, saved, and status are component state; inFlight is a ref initialized to false. dirty compares the draft with the last confirmed values. savePreferences is the request helper.

async function handleSubmit(event) {
  event.preventDefault();
  if (inFlight.current || !dirty) return;

  inFlight.current = true;
  setStatus('saving');
  const submitted = { ...draft };

  try {
    const accepted = await savePreferences(submitted);
    setSaved(accepted);
    setDraft(accepted);
    setStatus('saved');
  } catch {
    setStatus('error');
  } finally {
    inFlight.current = false;
  }
}

React's state snapshot explanation is relevant here: an event handler sees the values from its render. The captured submitted object makes the request's intended values explicit. It does not magically track later edits.

The first-version contract disables the fieldset during saving, so accepting the response as the new draft does not overwrite edits made during the request. If editing must remain possible, this implementation needs to change. You would preserve later local edits, distinguish the submitted revision, and decide how the UI represents partially saved work.

Put the save in the submit handler rather than an Effect triggered by draft changes. This is an explicit Save interaction. React's guidance on unnecessary Effects helps distinguish a user-triggered action from synchronization that must occur because a component is present.

The request helper must also classify failure correctly. MDN explains that receiving an HTTP error status does not by itself reject the fetch promise. Check response.ok, parse the response, and validate the expected boolean fields before treating it as accepted preferences.

Do not quietly replace an invalid response with the submitted values and announce success. That would hide uncertainty about what the server accepted. In this exercise, malformed success data follows the error path while the draft remains available.

Test the transitions that can lose user work

The local React fixture for this article was exercised using React DOM in jsdom. It checked unchanged-form disabling, a changed checkbox, repeated submissions in the same turn, disabled controls during saving, failure preservation, successful retry, and request-helper response handling. This is component-level evidence, not a live backend or assistive-technology test.

For the most useful failure test, hold the save promise unresolved. Submit twice before resolving it and assert that only one request was started. Reject that request, then check that the changed checkbox remains selected and retry is available. Resolve the retry and verify that the confirmation appears and the form is no longer dirty.

That sequence catches more than a snapshot of the initial page. It tests whether the implementation preserves the user's intent through an asynchronous transition. A test that merely asserts that a Save button exists would miss the problem this exercise is designed to expose.

Add a request-helper test for a non-success HTTP status and another for malformed JSON data. Decide how much diagnostic detail belongs in logs versus the user-facing message. “Save failed; your choices are still here” is useful to the user without presenting raw server internals.

During rehearsal, distinguish completed tests from tests you would add. If you only manually checked a successful save, say so. A clear remaining-test list is stronger than implying that a working demo establishes correctness across retries, navigation, and concurrent updates.

Turn the same UI into an architecture discussion

Imagine receiving an image showing a notification settings card, two controls, a Save button, and a status message. A screenshot establishes visible structure, but it leaves important behavior undefined. Ask whether preferences are account-wide, whether changes must persist across devices, and whether disabling a channel has immediate consequences elsewhere.

A reasonable initial decomposition is a page that loads the account data, a form that owns the draft, and an API helper that handles the request contract. The server owns persisted preferences and authorization. A presentational status component can display the current result without owning a second copy of save state.

Now change one constraint at a time. The table below is an original architecture exercise, not Deliveroo's internal design.

New constraintDecision to explain
Preferences refresh in the backgroundUpdate a clean form, but avoid replacing an unsaved draft silently.
Users edit in two tabsDefine server-side version checks or an explicit last-write policy; local button disabling is insufficient.
Editing remains enabled during saveTrack the submitted revision and preserve newer draft changes after the response.
Save response is lost after the server writesExplain the uncertain outcome and how a read or supported retry reconciles it.
Account changes during a requestIsolate state by account and avoid showing the previous account's completion message.

Do not solve every row with a large framework. Choose one constraint and follow it through the data model, request contract, rendered state, and test. For a version conflict, explain what the user sees: perhaps a message that settings changed elsewhere, with a deliberate refresh or review action. Silently overwriting either version is a product decision, not an implementation default.

Similarly, a disabled button cannot enforce authorization. The server must verify that the authenticated user may change the target account's preferences. Keep secrets out of frontend bundles and discuss request protection in the context of the application's authentication scheme, rather than promising that React handles security automatically.

Make accessibility and performance concrete

Use native labeled checkboxes and a submit button before inventing custom controls. Group related choices with a fieldset and legend. Ensure that the error is visible and programmatically announced, and that a successful save produces understandable feedback. W3C's form notification guidance explains why users need clear success and error information.

Test the actual keyboard path: reach each control, change its value, submit, and retry after failure. Check what happens to focus when controls are disabled and re-enabled. A DOM test can inspect roles and disabled attributes, but it does not establish the experience with a real screen reader.

For two checkboxes, network delay is likely more relevant than rendering a tiny component tree. Measure before adding memoization. If the page later contains hundreds of preference groups, investigate which interactions are slow and whether the delay comes from rendering, layout, or fetching. Optimization should preserve labels, focus, and the save contract.

Practice the gaps your implementation exposes

Choose the one that targets the first weakness you found in the notification editor.

Practice questionFocus for this preparation
Implement and Improve React ComponentsControlled behavior, component boundaries, and tests.
Implement a multi-step form in ReactSubmission state and retaining user work after errors.
Explain JS threading and useState vs useRefRendered status versus an immediate mutable request guard.
Build an Accessible Recursive Expand-and-Collapse Tree in ReactStable identity and keyboard interaction as the UI grows.
Design a reusable React comment componentMutation feedback, duplicate submission, and reusable contracts.

Return to the Frontend Engineer practice collection after the exercise. For your final rehearsal, implement the small behavior first, demonstrate one failure path, and then explain one architecture change without rewriting everything. That sequence makes your reasoning visible at each level of the task.

Sources and Further Reading

Sources checked September 10, 2026. The notification editor and architecture variations are original practice material.