The system design question
Design the 'customers who bought this also bought' and 'recommended for you' sections. They should reflect recent activity and load quickly on every product page.
Agree on the scope (5 minutes)
Say up front what you're covering.
| We'll design | We'll skip |
|---|---|
| 'Also bought' lists for each product | The details of the machine learning model |
| 'Recommended for you' lists for each shopper | The product page itself |
| Mixing in recent activity | Ads and sponsored items |
| Serving both fast on page load | Email recommendations |
Then agree on what "good" means:
- Fast: adds less than about 50 ms to a product page.
- Recent: what you viewed a minute ago should change what you're shown.
- Always shows something: if anything fails, show popular items instead of an empty box.
- Good enough: we'll measure quality by clicks and purchases, not get it perfect up front.
Rough numbers (5 minutes)
Assumptions, but reasonable: 300 million shoppers, 500 million products, 5 billion product page views a day, 50 million orders a day, and we keep the top 50 recommendations per product and 100 per shopper.
- ~60K
- product page views per second
- ~300 GB
- of 'also bought' lists
- ~360 GB
- of 'for you' lists
- 50M
- orders a day to learn from
| Question | Rough math | What it means |
|---|---|---|
| How many lookups? | 5 billion views ÷ 86,400, and two lists per page | About 120K lookups a second, so each one has to be a simple read |
| How big are 'also bought' lists? | 500 million products × 50 × about 12 bytes | About 300 GB. Fits in a split-up key-value store |
| How big are 'for you' lists? | 300 million shoppers × 100 × about 12 bytes | About 360 GB, similar |
| Can we compute it live? | Comparing a shopper against millions of others per page view | No. The heavy work has to happen ahead of time |
The overall design (10 minutes)
- Every view, add to cart and order goes onto an event stream.
- A nightly batch job reads months of orders and builds an 'also bought' list for every product and a 'for you' list for every shopper. It saves them in a key-value store.
- A streaming job reads the same events as they happen and keeps each shopper's last few dozen actions, plus fresh co-purchase counts for new or trending products.
- When a product page loads, the rec service reads the saved lists, adjusts them with recent activity, removes things you already bought, and returns about 20 product IDs.
- The page fetches product details for those IDs from the catalog cache.
What the apps need to do
| Action | Who does it | What's sent |
|---|---|---|
| Also bought for a product | Product page | Product ID and how many items |
| Recommended for a shopper | Product and home pages | Shopper ID, the page they're on and how many items |
| Record activity | Site and apps | Shopper ID, product ID, action type and time |
What we store and where
| Data | Where it goes | Why |
|---|---|---|
| Activity events | Event stream, then cheap long-term storage | Raw input for every job |
| 'Also bought' lists | Key-value store, split by product ID | One read per page load |
| 'For you' lists | Key-value store, split by shopper ID | One read per page load |
| Recent activity | In-memory store, per shopper | Last few dozen actions, kept for a day or so |
| Popular items by category | Cache | Fallback for new shoppers and failures |
Step 1: Building 'also bought' lists (10 minutes)
The basic idea: for every order, every pair of products in it gets a point. Also count pairs bought by the same shopper within a few weeks, since lots of related things aren't in the same order. After a pass over a few months of orders, product A's list is the products with the most points next to A.
Raw counts favor things everyone buys, like batteries. So divide by how popular each product is overall. That turns 'often bought with A' into 'bought with A much more than you'd expect'.
Counting every pair is a big job, so it runs as a nightly batch across many machines, split by product. Huge orders, like someone buying 200 things for an office, would make tens of thousands of pairs, so cap how many items per order we count.
The result goes into a key-value store split by product ID, like any other sharding setup. Swap in the new lists all at once so pages never see half old, half new.
Step 2: Building 'recommended for you' (12 minutes)
This one works in two steps. First gather a few hundred candidates: 'also bought' items for things you bought or viewed, popular items in categories you browse, and items similar shoppers bought. Then score each candidate and keep the best 100.
Scoring is where a machine learning model goes. It takes things like how you've reacted to similar items, price range and how recently you looked at the category, and predicts how likely you are to click or buy. We said we'd skip the model's details, so treat it as a box that returns a score.
This also runs nightly for every active shopper. Shoppers who haven't visited in months don't get a list. If they come back, they get popular items until the next run.
Why not compute it on page load?
Gathering candidates means dozens of reads, and scoring hundreds of items takes time. Doing that 60K times a second would be slow and expensive. Precomputing means a page load is one read. The cost is staleness, which the next section fixes.
Step 3: Reacting to recent activity (8 minutes)
A nightly list doesn't know you started shopping for a tent ten minutes ago. So a stream processing job keeps your last few dozen actions in memory, updated within seconds.
At page load, the rec service reads your nightly list, then mixes in the 'also bought' items for what you just viewed and drops anything you just bought. That's a few extra reads from fast stores, still well under our 50 ms. It's a small, quick adjustment, not a full rerun.
The same streaming job keeps short-term co-purchase counts for the last day or so. That helps brand-new products and sudden trends show up before the nightly job sees them.
All the lists are read through a cache, since popular products get the same 'also bought' list requested thousands of times a second.
Choices you should be able to defend
| We chose | Instead of | Because | We'd reconsider if |
|---|---|---|---|
| Precomputed lists, adjusted at page load | Computing everything live | Page loads stay fast and cheap | We had few products and shoppers |
| Nightly batch plus a light streaming layer | Everything in streaming | Batch is simpler and easier to rerun when there's a bug | Freshness mattered more than cost and simplicity |
| Popularity-adjusted counts | Raw co-purchase counts | Stops every list being full of batteries | Never, raw counts are almost always worse |
| Lists for active shoppers only | Lists for everyone | Saves storage and compute on people who aren't visiting | Inactive shoppers came back often enough to notice |
What could go wrong (5 minutes)
| What happens | What we do about it |
|---|---|
| The nightly job fails | Keep serving yesterday's lists. They're a day staler but still decent |
| The rec service is slow | The page gives up after 50 ms and shows popular items, or hides the section |
| The streaming job falls behind | Recent activity is late, so recommendations act like the nightly ones until it catches up |
| A bad model ships | Watch click rates per version and switch back to the previous lists |
| A new product has no data | Fill its list from similar products in the same category until it has orders |
Follow-up questions (5 minutes)
How do you know it's working?
Run A/B tests: show a slice of shoppers the new version and compare clicks and purchases.
How do you avoid showing the same items everywhere?
Track what's been shown on this visit and push repeats down the list.
What about a shopper with no history?
Start with popular items in the category they're looking at, and use their first few clicks right away through the streaming layer.
How the 60 minutes add up
| Minutes | What you're doing |
|---|---|
| 0–5 | Agree on what you're building |
| 5–10 | Rough numbers |
| 10–20 | Draw the overall design |
| 20–30 | Building 'also bought' lists |
| 30–42 | Building 'recommended for you' |
| 42–50 | Reacting to recent activity |
| 50–55 | What could go wrong |
| 55–60 | Follow-up questions |
Key concepts used here
- Stream processing and windowingCount things in data that never stops coming in.
- Message queues and event streamsKeep services from depending on each other, and handle traffic spikes.
- Caching strategiesMake reads faster, and figure out what to do when data changes.
- Sharding and consistent hashingSplit data across machines and keep it balanced.
Further reading
We wrote this sample answer ourselves.
Practice Loop is independent and isn't affiliated with or endorsed by Amazon.