· SWE Editorial · System Design  · 4 min read

Design a News Feed: Data Model and APIs

Schema and API design for the news feed system design interview: feed table, friendship graph, REST vs GraphQL, and cursor pagination vs offset, updated July 2026.

Schema and API design for the news feed system design interview: feed table, friendship graph, REST vs GraphQL, and cursor pagination vs offset, updated July 2026.

Once the architecture is on the whiteboard, interviewers will push into the data model and API surface. This is where candidates lose points for hand-waving — a vague “we’ll store posts in a database” answer is not sufficient at mid-level and above. This article covers the core tables, the friendship/follow graph, API design choices, and pagination mechanics.

Core Tables

Users table

FieldTypeNotes
user_idbigint (PK)Snowflake or UUID
usernamevarcharUnique, indexed
created_attimestamp
profile_metadataJSON/blobAvatar, bio, etc.

Posts table

FieldTypeNotes
post_idbigint (PK)Snowflake ID — encodes timestamp for natural sort
author_idbigint (FK)Indexed for author-timeline lookups
contenttextOr reference to blob storage for large content
media_urlsarray/JSONPointers to CDN-hosted assets
created_attimestamp
visibilityenumpublic, followers-only, private

Feed table (precomputed timeline)

FieldTypeNotes
user_idbigint (PK, partition key)The viewer whose feed this is
post_idbigint (sort key)Sorted by timestamp descending
scorefloatRanking score, updated by re-ranking job
inserted_attimestampFor TTL-based eviction

The Feed table is deliberately denormalized — it stores (user_id, post_id) pairs rather than joining against the Posts table at read time. This is the entire point of fanout-on-write: pay the storage and write cost up front so reads are a single indexed lookup, not a join across the follow graph and post store.

Friendship / Follow Graph

Model follows as a directed edge, since “A follows B” is not symmetric (unlike mutual friendship on some platforms):

FieldTypeNotes
follower_idbigintIndexed
followee_idbigintIndexed
created_attimestamp

Store this table twice, logically: one index optimized for “who does user X follow” (queried when building X’s feed) and one optimized for “who follows user Y” (queried by the fanout service when Y posts). In practice this means two indexes on the same underlying edge table, or two denormalized tables if write amplification on a single table becomes a bottleneck — call this tradeoff out explicitly, since it’s a natural follow-up question.

For celebrity accounts, this follow table can have tens of millions of rows pointing to a single followee_id. Mention that this is precisely why the fanout service special-cases high-follower accounts — a naive “fetch all followers, write to each cache” loop would fail to complete in reasonable time for a single post from a top-tier celebrity.

REST vs GraphQL for the Feed API

DimensionRESTGraphQL
Client over-fetchingCommon (fixed response shape)Avoided (client specifies fields)
CachingSimple (HTTP cache, CDN-friendly)Harder (single endpoint, POST-based)
VersioningExplicit (/v2/feed)Implicit via schema evolution
Mobile client fitRequires multiple endpoints for varying screen needsOne query adapts to screen/data needs
Interview defaultSafer, more universally understoodSignals awareness of client flexibility tradeoffs

For a feed endpoint specifically, REST with a well-designed cursor-based contract is the safer default to propose first — it’s cacheable at the CDN edge for anonymous or semi-personalized views and easier to reason about under load. Mentioning GraphQL as a viable alternative for clients that need to vary field selection by device (e.g., omitting media URLs on a low-bandwidth mobile view) shows range without over-committing the whiteboard to a heavier stack.

Example REST contract:

GET /v1/feed?cursor={opaque_cursor}&limit=20
Response:
{
  "posts": [ { "post_id": ..., "author_id": ..., "content": ..., "score": ... }, ... ],
  "next_cursor": "eyJ0cyI6MTc..."
}

Cursor Pagination vs Offset Pagination

This deserves its own comparison because it’s a frequent point of confusion and a common follow-up question.

AspectOffset pagination (OFFSET/LIMIT)Cursor pagination
Behavior under insertsItems shift, causing duplicates/skipsStable — cursor anchors to a specific item
Query cost at large offsetsIncreases linearly (DB must scan/skip rows)Constant — indexed seek from cursor
Implementation complexityTrivialRequires encoding/decoding opaque tokens
Supports “jump to page N”YesNo (sequential access only)
Fit for infinite-scroll feedPoorStrong — this is the standard choice

The cursor itself is typically a base64-encoded combination of (timestamp, post_id) — the tuple guarantees a total order even when two posts share the same timestamp to the second. The server decodes the cursor, performs an indexed range query (WHERE (created_at, post_id) < (cursor_ts, cursor_id) ORDER BY created_at DESC LIMIT 20), and encodes the last row’s tuple as the next_cursor for the client’s subsequent request.

API Surface Beyond the Feed Read

A complete answer should also sketch:

  • POST /v1/posts — create a post (triggers the async fanout pipeline described in the architecture deep dive).
  • POST /v1/follow/{user_id} — create a follow edge (may trigger a backfill of the new followee’s recent posts into the follower’s feed cache).
  • DELETE /v1/posts/{post_id} — soft-delete, which must also invalidate the post from every follower’s feed cache — a detail worth naming since naive fanout systems forget the deletion path entirely.

For a structured reference covering data modeling and API design patterns across every major system design question, not just feed, The 0-to-1 SWE Interview Playbook (Amazon: https://www.amazon.com/dp/B0H256Z1MF?tag=sirjohnnymai-20) includes worked schema examples you can adapt live in an interview.

Summary

A strong data model answer denormalizes the feed table for read performance, models follows as a directed graph with dual indexing, defaults to REST with cursor pagination for the read-heavy feed endpoint, and explicitly handles the deletion/invalidation edge case that most candidates forget.

Back to Blog

Related Posts

View All Posts »