Loading...
Loading...
Senior · 45 min
Also asked as: Amazon product listing, Shopify storefront, Airbnb search results, Zalando catalog, Instacart storefront.
Read it once, then start the timer and work without scrolling.
Design the frontend for an e-commerce storefront: a filterable product listing page, product detail, and a cart that survives across devices and sessions.
Everything below is a model answer. You will get far more out of this drill by attempting it cold first — set the timer, talk through it out loud, and only then compare.
A 45-minute phase plan you can practise against a clock.
Interactive · Drill rehearsal timer
6:00
Time remaining in Clarify and scope.
What you should establish before drawing a single box.
Architecture, data, interfaces, and optimisations — the RADIO body.
The half of the answer most candidates skip entirely.
Naming how the system breaks, and what the user sees when it does, is the cheapest way to signal production experience. Work through these before the interviewer has to ask.
The interviewer will challenge you. Rehearse the concession, not just the defence.
Interviewer
Why not just build this as a client-side SPA? It is simpler, the interactions are snappier, and Google renders JavaScript now.
Model response
Google does execute JavaScript, so the crawling objection is weaker than it used to be, and for an authenticated dashboard I would happily go client-only. The reason commerce is different is not really crawlability, it is time to first product. On a client SPA the sequence is HTML, then bundle, then hydrate, then fetch, then render — four serial round trips before a shopper sees a single item, and on a mid-range Android over 4G that is comfortably past three seconds. Every study on commerce puts real revenue on that interval. Server rendering collapses it to one round trip with products already in the markup. There is also a subtler crawl issue: JavaScript rendering goes into a deferred queue, so freshly listed products can take substantially longer to index, which matters when the catalog turns over. So I take SSR for the catalog surfaces and keep the SPA-like feel through client-side navigation and prefetching after the first paint.
Interviewer
You are doing two requests per page — catalog and pricing. That is a waterfall, and it will make the price flicker in after the products render.
Model response
Both criticisms are correct as stated. It is an extra round trip, and if I do it naively the user sees products with blank price slots for a few hundred milliseconds, which looks broken and shifts layout. What I would not do is fix it by merging them, because merging makes the entire catalog payload personalized and therefore uncacheable, and that trade is far worse — we would go from one origin request per minute per category to one per user. The fixes that keep the cache are: issue both requests in parallel from the server render rather than chaining them, reserve the price box so nothing shifts when it lands, and stream the pricing fragment so it arrives within the same response. If pricing turns out to be genuinely slow, I would cache it per price-group rather than per user, since most users fall into a handful of pricing tiers. If a business truly has fully individualized pricing for every customer, then this whole architecture is wrong and I would say so.
Interviewer
Putting filter state in the URL means every filter change is a navigation. State in a store would be far more responsive.
Model response
A store would be more responsive for the immediate interaction, and if this were an internal tool I would use one. The reason the URL wins here is that filtered listings are a shared artifact — people paste them into chats, bookmark them, and land on them from ads and from search. If the state is in a store, all of those produce the unfiltered page and the user has to redo their work, and the back button after five filter changes drops them out of the page entirely rather than undoing one filter. Those are not edge cases in commerce, they are core flows. The responsiveness objection is also solvable without giving up the URL: a shallow route update does not remount the tree, I can render results optimistically from the client cache while the new data loads, and I can debounce continuous controls like a price slider so a drag produces one entry rather than forty. So I get the addressability and pay only a small amount of plumbing.
Interviewer
The server-authoritative cart means every quantity change is a network round trip. That will feel sluggish compared to just keeping the cart in local state.
Model response
It does add latency to each mutation, and the local-state version genuinely feels better in the happy path. The reason I will not take it is that a cart is not display state, it is a financial claim, and the client cannot be trusted to compute it. Promotions stack in ways that depend on the whole basket, tax depends on address and jurisdiction, and stock can change under you — a client-computed total that disagrees with checkout is a support ticket at best and a pricing dispute at worst. I also cannot let the client be authoritative about price for the obvious reason that the client is user-controlled. What I do to recover the responsiveness is optimistic UI on the parts that are cheap and safe: the badge count and the line quantity update instantly, the authoritative subtotal is marked as recalculating rather than showing a stale number, and mutations are debounced so holding the increment button sends one request. That way the interaction feels local while the number the user acts on is always the server's.
Specific signals an interviewer is listening for on this prompt.
Turn one attempt into retained skill.