Frontend System Design Interview: Designing an E-commerce Platform Step-by-Step
I have been giving interviews since few months where I encountered this question in one of well-known startups.
Frontend System Design Interview: Designing an E-commerce Platform Step-by-Step
I have been giving interviews since few months where I encountered this question in one of well-known startups.
Photo by Christina @ wocintechchat.com M on Unsplash
A few months ago, sitting in a virtual interview loop with a well-known startup, I was asked a question that stumped me more than any DSA problem ever had: “Design the frontend of an e-commerce platform like Amazon.”
What followed was one of the most intellectually engaging 45 minutes of my interview journey, and it fundamentally changed how I think about building large-scale frontend systems. In this post, I’ll walk you through the exact mental model I’ve developed since question by question, layer by layer
Disclaimer : This is my first system design blog, written entirely from my own interview prep and the questions I kept asking myself along the way. I’ve tried to capture not just what to design, but why each decision exists because that’s what nobody explained to me when I started.
If something is unclear, oversimplified, or just plain wrong I’d genuinely love to know. Drop a comment below. I’m learning too, and this blog will only get better with your input
Step 1: Clarifying Questions
Before writing a single line of design, the first thing I do is ask clarifying questions. This signals to the interviewer that you think like a product engineer, not just a coder.
Here’s what I’d ask:
Scope — What pages are in scope?
- Are we designing the full platform or specific pages? I’d confirm: Product Listing Page (PLP), Product Details Page (PDP), Cart, and Checkout.
Users — Who are we designing for?
- Are users logged in, or do we need to support guest checkout (no login required)?
- What devices — desktop only, or mobile + tablet too?
- Are we targeting international users, or just one region?
Scale & availability — What are the traffic expectations?
- Are we expecting millions of concurrent users (think a flash sale)?
- Should the system be highly available even under heavy load?
SEO — Does discoverability matter?
- Should product pages rank on Google? This has a direct impact on rendering strategy.
Why the clarifying questions are important?
They shape the architectural decision that follows.
Answers would be Yes to Guest checkout , Yes to all devices, Yes to international users, Yes to SEO & Yes to high availability.
Step 2: Requirements
Functional Requirements
Once the scope is clear, I list out what the system must do. For an e-commerce platform, that breaks down into three core flows:
- Browse Products
- Browse and search products (by keyword, category, filters, sorting)
- View Product Listing Page (PLP) — name, image, price, rating
- View Product Details Page (PDP) — name, images, description, price, availability
2. Cart
- Add items to cart, Update quantity of a product, Remove a product from cart
- Cart should persist across sessions and devices, synced with backend (server is source of truth)
3. Purchase / Checkout
- Enter or select a saved delivery address, Enter payment details
- Place the order and see a confirmation
One thing worth calling out explicitly: Guest checkout users should be able to go through the entire flow without creating an account.
Non-Functional Requirements
These are the qualities the system must have. These are just as important as the features themselves, and in my experience, interviewers love it when you bring these up unprompted.
- Performance: Page load under 2 seconds
- SEO-friendly: Organic search traffic is a massive acquisition channel for e-commerce product pages must be indexable by Google
- Multi-device: Desktop, tablet, and mobile support
- International users: Users from the US, Asia, Europe — support local languages (i18n) and region-specific formats (i10n)
- High availability & scalability: The system must stay up during traffic spikes (flash sales, festive season)
- Security: HTTPS everywhere, no sensitive data leaks especially around payments
- Fast, responsive interactions: Filtering, adding to cart, updating quantities should feel instant

Requirements specified on Excalidraw
The key discipline: every element you add to your architecture must satisfy at least one of these requirements. If you can’t name the requirement, the element shouldn’t be there.
Rendering Strategy: SSR vs CSR
This is the first major architectural decision, and it’s driven entirely by your NFRs.
PLP and PDP → Server-Side Rendering (SSR)
- The SEO-friendly NFR forces this.
- Google’s crawler needs to receive fully-rendered HTML. If you serve an empty
<div id="root">and hydrate client-side, Google may never index your product pages. - SSR sends complete HTML from the server so the crawler immediately sees product name, price, and description. As a bonus, this also improves page load the browser can paint content before JavaScript bundles are even parsed.
Cart and Checkout → Client-Side Rendering (CSR)
- These pages have no SEO requirement a user’s cart is private and not Google-indexable.
- They are also highly interactive: quantity changes, coupon codes, address forms.
- Rendering them server-side adds unnecessary latency for every keystroke and update.

Rendering Strategies based on different pages
Step 3 : Architecture: Layer by Layer

High-level architecture diagram
1. Client Layer
Four pages: PLP, PDP, Cart, Checkout. This is what the user sees and interacts with.
2. State / Data Layer
This layer exists entirely for the “fast, responsive interactions” NFR.
When a user filters products by price, you don’t want a server round trip the filter state lives client-side and re-renders immediately.
When a user adds an item to cart, the cart icon updates instantly. That reactivity comes from in-memory state management (Redux or Zustand), not the server.
Components in this layer: Search, Filter, Coupon, Cart state
3. Browser Storage
This is where guest checkout gets solved. Since guest users have no account, cart data can’t live on the server. The browser storage layer has four distinct responsibilities:
- LocalStorage : It holds Guest Cart items persists across sessions guest closes browser, cart is still there
- sessionStorage : It holds Checkout step state which is tab-scoped clears on close, which is intentional for sensitive flows
- API cache : It holds product/search responses which helps to reduce repeated PLP/PDP visits don’t refetch the same data
- Service Worker : It holds static assets + API responses which helps in high availability via serving cached content when network is flaky.
Important distinction: Client state management (Redux/Zustand) is separate from browser storage.
4. CDN
- Directly satisfies two NFRs: page load under 2 seconds and international users. A user in Singapore hitting your Mumbai origin server gets ~200ms of network latency before a single byte arrives.
- A CDN serves JS, CSS, and product images from an edge node geographically close to the user. Mobile users on 4G especially benefit.
5. API Gateway
- Single entry point for all service calls.
- Enforces HTTPS termination, auth token validation, and rate limiting centrally.
- Satisfies the security NFR and enables load balancing across service instances for the scalability NFR.
6. Services Layer
Each service owns a specific domain:
- Auth : identity verification before checkout and payment
- Product : all data for PLP and PDP
- Cart : server-side cart for logged-in users
- Checkout : order creation and payment processing
- Address : separated so address management doesn’t couple into checkout logic
7. Databases (Per Service)
- Each service owns its own datastore.
- This directly satisfies the scalability NFR. A traffic spike on product search shouldn’t degrade cart writes.
The Tradeoffs
Guest Checkout: Where the Cart Actually Lives
This is the most commonly misunderstood part of the architecture. Guest checkout creates two distinct storage concerns:
In-session (user is actively shopping):
- Cart lives in Redux/Zustand — in JS memory.
- This makes add-to-cart feel instant and updates the cart icon reactively across the whole app.
Across sessions (user closes browser, comes back tomorrow):
- Cart is persisted to
localStorage. On app load, Redux reads from localStorage and rehydrates the in-memory store.
User adds item
→ Redux/Zustand (in-memory, instant reactivity)
→ localStorage (persisted, survives refresh)
User returns next day
→ App boots → reads localStorage → rehydrates Redux store
Why not sessionStorage? It clears when the tab closes a guest would lose their cart every time they close the browser, which is terrible UX.
Why not the Cart service on the server? The Cart service requires a user ID. A guest has no account. There’s nothing to associate the server-side cart with.
The cart merge on login: When a guest logs in, you need POST /api/cart/merge to merge the localStorage cart with their server-side cart:
User logs in
→ POST /api/auth/login → get token
→ POST /api/cart/merge (send localStorage cart in body)
→ server returns merged cart
→ update Redux store
→ clear localStorage guest cart
This merge step is what most candidates miss. Without it, users lose their guest cart when they log in.
Why Add to Cart Feels Instant: Optimistic UI
When you click “Add to Cart”, no network call happens before the UI updates. Here’s exactly what runs:
User clicks "Add to Cart"
→ dispatch({ type: 'ADD_TO_CART', item }) ← ~1ms, in JS memory
→ cart icon count updates instantly
→ POST /api/cart/add fires in background
→ localStorage updated for persistence
The UI reacts to the in-memory state change, not the server response. This pattern is called an Optimistic UI Update you assume the server will succeed and update the UI immediately.
If it fails, you rollback:
Server returns error
→ dispatch({ type: 'REMOVE_FROM_CART', item })
→ Show toast: "Couldn't add item, please try again"
If you waited for the server before updating the UI, every add-to-cart click would feel laggy. JS memory update takes ~1ms. A network round trip takes 200–500ms minimum. The difference is very noticeable.
Step 4 : Data Model
Seven core entities drive the entire system.
PK : Primary Key, FK : Foreign key
ProductList : API response shape for PLP, not a DB entity. Contains products[] and pagination metadata. No PK — it's not stored, it's returned.
Product : PK product_id, name, description, unit_price, currency, primary_image, image_urls[].
Cart : PK cart_id, FK user_id (nullable — null if guest), items[], total_price, currency. The nullable user_id is how guest checkout is solved at the data layer.
CartItem : PK cart_item_id, FK cart_id, FK product_id, quantity, price, currency. Two FKs: one back to Cart, one referencing Product.
AddressDetails : PK address_id, FK user_id (nullable), name, street, city, country. Nullable user_id allows guest address capture without an account.
PaymentDetails : PK payment_id, FK order_id, card_number (masked), card_expiry. **card_cvv is NEVER stored.** CVV goes directly to the payment gateway (Stripe) and never touches your database.
Order : PK order_id, FK user_id, FK address_id, status (pending | confirmed | shipped | delivered | failed), total_price, currency, created_at. PaymentDetails.order_id is a FK to it

Step 5 : API Design
Product APIs
GET /api/products
Parameters:
size (number) — results per page
page (number) — page to fetch
country (string) — determines currency
q (string) — search query (optional)
category (string) — filter by category (optional)
min_price / max_price (number) — price range (optional)
sort (string) — "price_asc" | "price_desc" | "rating" | "newest"
response :
{
"pagination": {
"size": 5,
"page": 2,
"total_pages": 4,
"total": 20
},
"products": [
{
"id": 123,
"name": "Cotton T-shirt",
"primary_image": "https://cdn.example.com/img/t-shirt.jpg",
"unit_price": 12,
"currency": "USD",
"rating": 4.3,
"review_count": 128,
"in_stock": true
},
...
]
}
GET /api/products/:product_id
Parameters: productId, country
response :
{
"id": 123,
"name": "Cotton T-shirt",
"description": "100% organic cotton, available in 3 colours. Lightweight and breathable for everyday wear.",
"primary_image": "https://cdn.example.com/img/t-shirt.jpg",
"image_urls": [
"https://cdn.example.com/img/t-shirt-white.jpg",
"https://cdn.example.com/img/t-shirt-black.jpg",
"https://cdn.example.com/img/t-shirt-red.jpg"
],
"unit_price": 12,
"currency": "USD",
"rating": 4.3,
"review_count": 128,
"in_stock": true,
"stock_count": 34,
"variants": [
{ "label": "Size", "options": ["XS", "S", "M", "L", "XL"] },
{ "label": "Colour", "options": ["White", "Black", "Red"] }
]
}
Pagination tradeoff worth mentioning in interviews:
- Offset pagination is chosen for simplicity easy to implement, supports random page jumps, works with SQL LIMIT/OFFSET. The downside at scale:
OFFSET 10000forces the DB to scan and skip 10,000 rows, which is slow on large datasets. - Cursor-based pagination is more performant but more complex to implement. Know which you're choosing and why.
Cart APIs
POST /api/cart/add — add item
PUT /api/cart/items/:cart_item_id — update quantity
DELETE /api/cart/:productId — remove item
GET /api/cart — fetch cart on app load
POST /api/cart/merge — merge guest cart on login
Cart APIs require Authorization: Bearer <token> for logged-in users, or a guest_session_id header for guests.
Order API
POST /order
Body: {
cart_id,
address_details,
payment_token ← from Stripe.js, NOT raw card data
}
Response: {
"id": 456,
"status": "confirmed",
"created_at": "2026-03-26T18:00:00Z",
"total_price": 36,
"currency": "USD",
"items": [...],
"address_details": {
"address_id": 77,
...
},
"payment_details": {
"payment_id": 901,
"card_last_four_digits": "1234",
"status": "success"
}
}
The response must include status (so the client knows the outcome), created_at (for order history), address_id (so the user can reuse the address), and payment_id (for future refunds or receipts).
Step 5 : optimisations
Performance Optimisations
- Code-split JS by route don’t ship the checkout bundle to users browsing PLP
- Prioritise above-the-fold content, lazy load everything else
- Defer non-critical JS (modals, analytics, live chat)
- Prefetch on hover : prefetch PDP data when hovering PLP cards; prefetch checkout page JS while the user is on the cart page
- Optimise images: WebP format, lazy loading, adaptive quality based on network speed
- Prefetch top search results
Image Optimisation
- WebP format — most efficient format available, used by eBay across web and apps
- Host all images on CDN
<img loading="lazy">for below-the-fold images<link rel="preload">for above-the-fold hero images- Adaptive loading — high quality on fast connections, compressed on slow ones
Form Optimisation
- Country-specific address formats : UK uses Postal Code, Japan has prefectures. Services like Stripe Checkout handle this automatically.
- Autofill : specify correct
typeandautocompleteattributes on inputs so browsers fill correctly. - Use
inputmode="numeric"(nottype="number") for card number and CVV fields — shows numeric keyboard on mobile without unwanted increment arrows. - Address autocomplete : user types a street number and selects from suggestions via Google Maps Place Autocomplete. Faster UX, fewer typos.
Accessibility
- Use semantic HTML —
<button>,<input>,<label>,<nav>instead of generic<div>elements - Every
<img>needsalttext, oralt=""if decorative
Internationalisation (i18n)
- Translate pages into supported languages
- Support country-specific address formats (different fields, different labels, different validation)
Closing Thoughts
A well-designed e-commerce frontend isn’t about picking React and calling it done. Every architectural element SSR for PLP/PDP, localStorage for guest cart, Service Worker for offline resilience, API Gateway for security exists because of a specific requirement.
The discipline to ask “which requirement does this satisfy?” before adding anything to your design.
The next time someone asks you to design an e-commerce platform, start with the requirements, trace every decision back to them, and your design will be defensible at every layer.
메타데이터
- post_id
- 1ef17d1e5dfa
- slug
- frontend-system-design-interview-designing-an-e-commerce-platform-step-by-step-1ef17d1e5dfa
- url
- https://medium.com/@pujarini97/frontend-system-design-interview-designing-an-e-commerce-platform-step-by-step-1ef17d1e5dfa
- canonical_url
- https://medium.com/@pujarini97/frontend-system-design-interview-designing-an-e-commerce-platform-step-by-step-1ef17d1e5dfa
- author_url
- https://medium.com/@pujarini97
- status
- ok
- fetched_at
- 2026-06-12 22:02:08