0) Problem Restatement
Netflix asked: the homepage shows many rows ("Continue Watching", "Trending Now", "Because you watched X", ...), each a horizontal list of titles. Design how it renders:
- Viewport-based fetching: only load the rows (and titles within rows) that are visible or about to become visible, as the user scrolls down or sideways, especially on slow TVs.
- Deduplication: the same show shouldn't appear in several rows on the screen at the same time (it wastes space and looks broken), even though different row algorithms may independently pick it.
1) Requirements
- The first screen appears fast (under ~1 s), even on low-end TVs.
- Load more rows on vertical scroll, and more titles on horizontal scroll.
- No duplicate titles across rows within a homepage session (or at least within the visible area).
- Personalized rows. Consistent while the user scrolls (rows don't reshuffle).
2) Architecture
Architecture Diagram
flowchart LR
TV["Client - TV / phone / web"] -->|"page 1: rows 1-4"| HP["Homepage service (BFF)"]
HP --> ROWS["Row generators - personalized candidates"]
HP --> SESS[("Page session - shown title set, row plan")]
HP --> CACHE[("Candidate cache per user")]
TV -->|"next rows / more titles in row"| HP- A Homepage service (backend-for-frontend) assembles pages for a page session (one visit to the homepage).
- Row generators (ranking services) produce candidate lists per row type, and are cached per user for a short time.
- A page session stores the row plan (order of rows) and the set of titles already shown.
3) Viewport Fetching
- First request: return the first ~4 rows × the first ~10 titles each (what fits on screen plus a little extra), plus a
session_idand cursors. - Vertical scroll: when the user nears the bottom, request
GET /home/rows?session=...&after_row=4&count=3. - Horizontal scroll:
GET /home/row/{row_id}/items?session=...&cursor=...to extend one row. - Images are lazy-loaded (only visible artwork is fetched), with low-res placeholders first.
- Payloads are small, and TVs get pre-sized images.
4) Deduplication
Where: on the server, per page session, because the server sees all rows and can apply a consistent rule. The client may also do a final safety check. How:- When building a page of rows, go row by row in priority order (e.g., "Continue Watching" first, it owns its titles).
- For each row, walk its ranked candidates and skip titles already in the session's shown set, taking the next best until the row has enough items.
- Add the chosen titles to the shown set and save it to the session.
- Later pages (more rows, or more items within a row) use the same set, so new rows never repeat earlier ones.
5) Performance and Caching
- Precompute or cache candidate lists per user (they don't need to be real-time), so page assembly is fast.
- The session state is small (the plan + a set of title IDs) and stored in a fast KV store with a TTL (e.g., 1 hour).
- On TVs, keep the client light: the server returns ready-to-render rows, and the client just draws and prefetches the next set.
6) Wrap-Up
A homepage BFF creates a page session with a row plan and returns only the rows and titles needed for the first viewport, with cursors for vertical row paging and horizontal in-row paging, while images load lazily. Deduplication happens on the server per session: rows are filled in priority order from cached, personalized candidate lists, skipping titles already shown and recording new ones, so later pages stay duplicate-free and the page stays stable while the user scrolls.