- This lesson is a complete, simulated 45-minute "Design Instagram" interview, minute by minute, with the interviewer's prompts and a strong candidate's answers.
- The time split that works: ~5 min clarifying, ~3 requirements, ~4 estimation, ~4 API, ~5 data model, ~7 high-level design, ~12 deep dives, ~3 bottlenecks and trade-offs, ~2 wrap-up.
- The deep dives interviewers almost always reach are the hybrid feed fan-out, media upload & processing, sharding & Instagram's 64-bit ID scheme, caching, the celebrity / hot-key problem and DMs.
- It ends with 12+ follow-up questions with model answers, a junior / senior / staff rubric, common mistakes, and how the design changes for Twitter/X, TikTok, Facebook, Pinterest and YouTube.
- What gets you hired is not knowing the "right" answer but driving the conversation: state assumptions, quantify, name the trade-offs, and go deep where the interviewer pushes.
1. The problem: why a script for "Design Instagram"?
"Design Instagram" (or its cousins "design a photo-sharing app" and "design a news feed") is one of the most frequently asked system design questions, because it touches almost everything: a read-heavy feed, a write-heavy engagement layer, huge blobs, a social graph, search, real-time messaging and async pipelines. It is also open-ended. Without structure, candidates spend 20 minutes on login screens or draw boxes without numbers. Then the clock runs out before the interesting part, the feed.
Every concept in this walkthrough has its own lesson in this guide. Here we practise assembling them under time pressure, in the order and at the depth an interviewer expects. The framework is the one from How to Approach a System Design Problem; the numbers come from Back-of-the-Envelope Estimation; the request paths come from End-to-End Request Flows.
2. The 45-minute plan
| Minutes | Phase | Goal | Output on the whiteboard |
|---|---|---|---|
| 0β5 | Clarifying questions | Agree on scope and scale | Bullet list of in/out of scope features and user numbers |
| 5β8 | Requirements | Functional + non-functional targets | FRs, NFRs (latency, availability, consistency) |
| 8β12 | Capacity estimation | Size the system; find what's hard | QPS, storage, bandwidth, cache size |
| 12β16 | API design | Define the contract | 5β8 endpoints with key params |
| 16β21 | Data model | Entities, keys, DB choices | Tables with partition and clustering keys |
| 21β28 | High-level design | End-to-end boxes and arrows | The architecture diagram |
| 28β40 | Deep dives | Show depth on the hard parts | Feed fan-out, media, sharding, caching, hot keys, DMs |
| 40β43 | Bottlenecks & trade-offs | Show judgement | SPOFs, failure modes, what you'd change at 10Γ |
| 43β45 | Wrap-up | Summarise; leave a strong last impression | 3β4 sentence summary plus future work |
3. Minutes 0β5: Clarifying questions
Never start designing immediately. Ask a few, high-leverage questions, and propose answers yourself when the interviewer turns the question back to you ("What do you think?"), which they often will.
Interviewer: Let's design Instagram.
Candidate: Great. Instagram is big, so let me scope it first. I'm thinking of the core loop: users upload photos and short videos, follow other users, and see a home feed of posts from the people they follow, with likes and comments. Should I also cover Stories, Reels, Explore, search and Direct Messages, or focus on the core?
Interviewer: Focus on posting and the feed. Include likes and comments. Mention Stories, search and DMs at a high level; we may go deeper on one if there's time.
Candidate: Got it. What scale should I design for? I'll assume something like today's Instagram: around 2 billion monthly users and 500 million daily active users, unless you'd like a different number.
Interviewer: 500 million DAU is fine.
Candidate: Is the feed chronological or ranked? And is it global or do we care about multiple regions?
Interviewer: Ranked, but you don't need to design the ML model. Users are global.
| Question | Why it matters | Answer assumed in this walkthrough |
|---|---|---|
| Which features are in scope? | Prevents designing everything shallowly | Upload photo/video posts, follow, home feed, like, comment. Stories, search and DMs at a high level. |
| How many users? DAU? | Drives every number | ~2B MAU, 500M DAU |
| Read/write ratio? | Read-optimised vs write-optimised design | Heavily read-skewed, ~100:1 views to posts |
| Photos only, or video too? Max size? | Storage, transcoding, streaming | Both. Photos β€ ~10 MB original; video β€ 60β90 s. |
| Feed: chronological or ranked? | Changes the read path (ranking stage) | Ranked (treat the model as a black box) |
| How fresh must the feed be? | Allows eventual consistency | A few seconds of delay is fine |
| Global users? Regions? | CDN, multi-region, data residency | Global. Assume multiple regions plus a CDN. |
| Private accounts? Blocking? | Authorization on every read | Yes. They must be enforced on feed, profile and search. |
| Any celebrities with huge followings? | Hot keys, fan-out explosion | Yes. Top accounts have hundreds of millions of followers. |
4. Minutes 5β8: Requirements
Write these on the board. They are the contract you'll check your design against at the end (see requirements).
β Functional
1) Upload a photo/video post with a caption. 2) Follow / unfollow users. 3) View a ranked home feed of followed accounts' posts. 4) Like and comment on posts. 5) View a user's profile grid. High level: Stories (24 h), search users/hashtags, 1:1 DMs, push notifications.
π Non-functional
High availability (99.99% for feed and read paths). Feed p99 < ~300 ms server time. Media loads fast worldwide (CDN). Durable: never lose an uploaded photo (11 nines object durability). Eventual consistency OK for feeds and counters; read-your-writes for your own posts. Scales horizontally.
π« Out of scope
Ads, payments, the internals of the ML ranking model, Reels recommendation, live video, content-moderation policy details, the account recovery UX.
Candidate: The key trade-off I see is availability and latency over strict consistency. If a follower sees a new post five seconds late, or a like count is off by a few, nobody is harmed. But I won't relax durability of uploads, or privacy checks: a blocked user must never see your content. Does that match your expectations?
Interviewer: Yes, that's reasonable.
This is a direct application of CAP/PACELC. During a partition we choose availability for the feed. In normal operation we choose latency over consistency for everything except auth, privacy and DMs ordering.
5. Minutes 8β12: Capacity estimation
State assumptions, round aggressively, and focus on numbers that change the design. Full derivations are in capacity estimation; here is the summary a candidate would put on the board.
Assumptions: 500M DAU Β· 100M new posts/day Β· each DAU opens feed ~10Γ/day
each DAU likes ~10 posts/day Β· avg original photo 2 MB, renditions +0.5 MB
avg follower count ~200 (heavy skew: a few accounts have 100M+)
Writes (posts): 100M / 86,400 s β 1,200 posts/s (peak ~3β5k/s)
Reads (feed): 500M Γ 10 / 86,400 β 58,000 feed req/s (peak ~150k/s)
Likes: 500M Γ 10 / 86,400 β 58,000 likes/s
Fan-out writes: 1,200 posts/s Γ 200 β 240,000 timeline inserts/s (avg)
Media storage: 100M Γ 2.5 MB = 250 TB/day β 90 PB/year (before replication/EC)
Metadata: 100M Γ ~1 KB = 100 GB/day β 36 TB/year
Timeline cache: 500M users Γ 500 IDs Γ 8 B = 2 TB raw (~5β6 TB with Redis overhead)
β ~100 Redis nodes Γ 64 GB, plus replicas
Media egress: 500M Γ 100 images Γ 200 KB = 10 PB/day β 1 Tbps avg (CDN absorbs 95%+)
| Number | Value | Design consequence |
|---|---|---|
| Read : write | ~50β100 : 1 | Optimise reads: precompute feeds, cache aggressively, CDN |
| Feed QPS | ~60k avg, ~150k peak | Stateless feed service, timeline in memory (Redis) |
| Fan-out writes | ~240k/s average, bursty | Async workers via Kafka; can't be in the request path |
| Media storage | ~90 PB/year | Object storage with tiering and erasure coding; never a DB |
| Egress | ~1 Tbps | CDN is mandatory |
| Metadata | ~36 TB/year, high write rate | Sharded DBs (PostgreSQL for users, Cassandra for posts/likes) |
Interviewer: Good. You don't need more precision than that.
Candidate: The two things these numbers tell me: the feed must be served from memory, not computed from the database on each request, and the fan-out of ~240k writes per second has to be asynchronous. Let me define the API.
6. Minutes 12β16: API design
Keep it to the endpoints that matter, and show you know the details that separate good APIs from naive ones: cursor pagination, idempotency keys, pre-signed uploads and authentication (see API design).
# All calls: HTTPS, Authorization: Bearer <JWT>, JSON. Versioned under /v1.
POST /v1/media/uploads {type, size_bytes, sha256}
β 200 {media_id, upload_url (pre-signed PUT, expires 15 min)}
POST /v1/posts {media_ids[], caption, location_id?, user_tags[]}
Headers: Idempotency-Key: <uuid>
β 201 {post_id, created_at, status: "PROCESSING"}
GET /v1/feed?cursor=<opaque>&limit=20
β 200 {posts:[{post_id, author, caption, media:[{url_640, url_1080, w, h}],
like_count, comment_count, viewer_has_liked}], next_cursor}
GET /v1/users/{user_id}/posts?cursor=...&limit=30 # profile grid
PUT /v1/posts/{post_id}/like β 204 (idempotent)
DELETE /v1/posts/{post_id}/like β 204
POST /v1/posts/{post_id}/comments {text, reply_to?} Idempotency-Key β 201 {comment_id}
GET /v1/posts/{post_id}/comments?cursor=...
POST /v1/users/{user_id}/follow β 200 {state: following|requested}
DELETE /v1/users/{user_id}/follow β 204
GET /v1/search/typeahead?q=sel # users, hashtags, places
GET /v1/stories/tray # high level
WS /v1/realtime # DMs, typing, receipts (WebSocket)
Interviewer: Why a cursor rather than page=3?
Candidate: The feed changes constantly. With offsets, new posts arriving at the top shift everything down, so the user sees duplicates or skips posts. Offsets are also slow on large tables. An opaque cursor encodes the position, for example the last post's ID or score plus a snapshot token, so the next page continues exactly where the last one ended.
Interviewer: And the idempotency key?
Candidate: Mobile networks drop responses. If the app times out on POST /posts and retries, the server sees the same key, finds the stored result and returns the original post_id instead of creating a duplicate post. Likes don't need a key because PUT on a (post, user) pair is naturally idempotent.
Protocol choices: REST/JSON for the public mobile API (simple, cacheable, debuggable), gRPC between internal services (fast binary, deadlines, streaming), and a WebSocket for real-time DMs. The API Gateway handles authentication via JWTs and rate limiting in front of all of them.
7. Minutes 16β21: Data model
Pick the store per dataset based on its access pattern, and say why. A strong candidate names partition keys and explains how each query maps to exactly one partition (see databases, indexing).
| Data | Store | Why |
|---|---|---|
| Users, profiles, settings, auth | PostgreSQL, sharded by user_id, with read replicas | Relational, modest size, needs constraints (unique username) and transactions |
| Posts, comments, likes, stories | Cassandra (wide-column) | Huge write volume, simple key-based access, linear horizontal scale, TTL support |
| Follow graph | Sharded KV/wide-column (both directions) plus a graph cache | Two adjacency lists: followers and following |
| Timelines, counters, hot objects | Redis | Sub-millisecond reads of precomputed data |
| Photos and videos | Object Storage (S3) + CDN | Cheap, durable blobs; never store blobs in a DB |
| Search | Elasticsearch | Inverted index for prefix and full-text search |
| DMs | Cassandra/HBase, partitioned by thread_id | Append-heavy, ordered per conversation |
7.1 User DB (PostgreSQL)
CREATE TABLE users (
user_id BIGINT PRIMARY KEY, -- 64-bit time-sortable ID (see deep dive)
username VARCHAR(30) NOT NULL,
full_name VARCHAR(64),
email VARCHAR(255),
password_hash TEXT NOT NULL, -- argon2/bcrypt
bio TEXT,
avatar_key TEXT, -- object-storage key, not the image
is_private BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Global uniqueness of username needs its own lookup table, sharded by username:
CREATE TABLE usernames (username VARCHAR(30) PRIMARY KEY, user_id BIGINT NOT NULL);
7.2 Posts, likes and comments (Cassandra CQL)
-- One post by ID (for hydration)
CREATE TABLE posts_by_id (
post_id bigint PRIMARY KEY,
user_id bigint,
caption text,
media list<frozen<media_ref>>, -- rendition keys, width, height
location_id bigint,
status text, -- PROCESSING | READY | REMOVED
created_at timestamp
);
-- Profile grid: all posts of a user, newest first (one partition per user, bucketed by month)
CREATE TABLE posts_by_user (
user_id bigint,
bucket int, -- e.g. 202610, keeps partitions bounded
post_id bigint,
PRIMARY KEY ((user_id, bucket), post_id)
) WITH CLUSTERING ORDER BY (post_id DESC);
-- Who liked a post (idempotent: primary key = post + user)
CREATE TABLE likes_by_post (
post_id bigint,
bucket smallint, -- spreads mega-viral posts over N partitions
user_id bigint,
liked_at timestamp,
PRIMARY KEY ((post_id, bucket), user_id)
);
CREATE TABLE comments_by_post (
post_id bigint,
comment_id bigint, -- time-sortable β natural ordering
user_id bigint,
text text,
PRIMARY KEY (post_id, comment_id)
) WITH CLUSTERING ORDER BY (comment_id DESC);
7.3 Social graph and timelines
CREATE TABLE following (follower_id bigint, followee_id bigint, created_at timestamp,
PRIMARY KEY (follower_id, followee_id));
CREATE TABLE followers (followee_id bigint, follower_id bigint, created_at timestamp,
PRIMARY KEY (followee_id, follower_id));
# Redis
timeline:{user_id} ZSET score = created_at (or rank), member = post_id (cap 500)
like_count:{post_id}:{n} STRING counter shards, summed on read
post:{post_id} HASH cached hydrated post object (TTL hours)
Interviewer: Why store the follow edge twice?
Candidate: The two queries are partitioned differently. "Whom do I follow?" is needed when building my feed and is partitioned by follower. "Who follows Bob?" is needed for fan-out and is partitioned by followee. With one table, one of those queries becomes a scatter-gather across all shards. Denormalising into two tables makes both single-partition reads. The cost is keeping them in sync, which I'd do with an idempotent async writer driven by the follow event, plus a repair job.
Interviewer: Why not put posts in PostgreSQL too?
Candidate: We could. Public talks describe Instagram doing exactly that early on, with sharded PostgreSQL. At ~1,200 posts/s, ~60k likes/s and petabyte-scale history, Cassandra's leaderless writes, easy horizontal scaling and built-in TTL are a better fit for append-heavy, key-based data. I keep PostgreSQL where I want relational constraints, like unique usernames and account settings.
8. Minutes 21β28: High-level design
Draw the boxes, then walk two requests through them: a write (post a photo) and a read (load the feed). That proves the diagram works.
Candidate: Let me trace a write. Alice posts a photo. The app asks the Upload Service for a pre-signed URL and uploads the bytes straight to object storage, so the media never touches our app servers. Then it calls POST /posts. The Upload Service writes the post row to Cassandra, publishes post.created to Kafka and returns 201. Asynchronously, media workers generate renditions and run moderation. Fan-out workers look up Alice's followers in the graph store and push the post ID into each follower's Redis timeline. The search indexer indexes hashtags, and the notification service tells anyone she tagged.
Candidate: Now a read. Bob opens the app. DNS sends him to the nearest region, and the LB forwards to the gateway, which validates his JWT and rate-limits. The News Feed Service reads timeline:bob from Redis (~500 post IDs), merges in recent posts from any celebrities he follows (pulled, not pushed; I'll explain in the deep dive), ranks them, hydrates the top 20 from the post cache and returns JSON with CDN URLs. The app then fetches images from the nearest CDN edge.
Interviewer: Where's the single point of failure?
Candidate: There shouldn't be one. The LBs are redundant pairs with a floating VIP or Anycast. The gateway and all services are stateless and horizontally scaled behind the LB. Every data store is replicated: PostgreSQL has a leader with followers and automated failover, Cassandra uses RF=3, Redis has replicas, Kafka uses RF=3. The region itself is a failure domain, so we run multiple regions with GeoDNS failover (see multi-region).
More detailed hop-by-hop walkthroughs of all nine core flows are in End-to-End Request Flows.
9. Minutes 28β40: Deep dives
The interviewer will now pick, or let you pick, two or three areas. Lead with the one that's most central: the feed. Have all six below ready; in a real interview you'll cover two or three in depth and mention the rest.
9.1 Feed generation: fan-out on write, on read, or hybrid
Interviewer: How exactly do posts get into people's feeds?
Candidate: There are two basic models. Fan-out on read (pull): when Bob opens the app, fetch the latest posts of everyone he follows and merge them. Writes are cheap, but each read touches hundreds of partitions, and at 150k feed requests/s that's tens of millions of DB reads per second, so it's too slow and expensive. Fan-out on write (push): when Alice posts, write the post ID into every follower's precomputed timeline. Reads become one Redis lookup, which is ideal for a read-heavy app. But a celebrity with 300M followers would trigger 300M writes per post, taking many minutes and causing massive write amplification. So I'd use a hybrid: push for normal accounts, pull for celebrities, merged at read time.
WRITE PATH (post.ready event) READ PATH (GET /feed for Bob)
author has < ~1M followers? timeline:bob (Redis ZSET) ---------+
YES -> PUSH: page through followers, ~500 post IDs pushed earlier |
ZADD timeline:{f} ts post_id +--> merge
(skip inactive followers) celebrities Bob follows (~0-50) | filter (blocked,
NO -> nothing (celebrity) -> PULL recent posts of each ----+ deleted, seen)
(hot, heavily cached) rank -> top 20
hydrate -> JSON
| Fan-out on write (push) | Fan-out on read (pull) | Hybrid β | |
|---|---|---|---|
| Read cost | 1 Redis read | N partition reads (N = followees) | 1 Redis read + a few cached celebrity reads |
| Write cost | F writes (F = followers) | 1 write | F writes only for normal accounts |
| Celebrity post | Catastrophic (100M+ writes) | Fine | Fine (pulled) |
| Inactive users | Wasted writes | No waste | Skip inactive followers; rebuild on return |
| Freshness | Seconds of lag | Real-time | Seconds of lag for push, real-time for celebrities |
Details a senior candidate adds: the threshold is a tunable (follower count and posting rate). Timelines are capped (~500 IDs), because nobody scrolls further and older content can be pulled. Timelines store IDs only, so edits and deletes don't need fan-out; hydration reads the current post object. Ranking happens at read time over the merged candidates. Unfollows are filtered at read time instead of being purged eagerly. Full treatment: news feed.
9.2 Media upload & processing
Clients API Upload Object Cassandra Kafka Workers
Gateway Service Storage
| | | | | | |
| 1 POST /media/uploads | | | |
|-------->| | | | | |
| | 2 route | | | | |
| |-------->| | | | |
| 3 media_id + pre-signed URL | | | |
|<..................| | | | |
| 4 PUT bytes | | | | |
|---------------------------->| | | |
| 5 POST /posts (via gateway) | | | |
|------------------>| | | | |
| | | 6 INSERT post | | |
| | |------------------->| | |
| | | 7 post.created | | |
| | |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>| |
| 8 201 {post_id} | | | | |
|<..................| | | | |
| | | | | | 9 resize, fan-out, index, notify
| | | | | |~~~~~~~~>|
| | | | | | |
legend: ----> synchronous call ....> response ~~~~> asynchronous event (Kafka) -- local work
- Pre-signed URLs let clients upload directly to object storage. App servers never proxy bytes, and the URL is scoped to one key, size and content type and expires in minutes (see media upload).
- Resumable / multipart uploads for video: 5β8 MB chunks, so a dropped connection costs one chunk instead of the whole file.
- Client-side resize and compression before upload, which saves 80β90% of bytes for photos.
- Async processing via Kafka β media workers: renditions (150/320/640/1080 px, WebP/AVIF), video transcoding to HLS/DASH at several bitrates, thumbnails, EXIF stripping, moderation classifiers and perceptual-hash matching. Workers are idempotent, with a dead-letter queue for poison files (see media processing).
- Content-addressed, versioned keys (
p/{media_id}/{hash}_1080.webp) let the CDN cache withmax-age=1 year, immutable. - Storage tiering: renditions of old, rarely viewed posts move to cheaper storage classes. Erasure coding gives ~1.4Γ overhead instead of 3Γ replication (see object storage). Public papers describe Facebook's Haystack and f4 systems doing exactly this kind of hot/warm split for photos.
9.3 Sharding & Instagram's ID scheme
Interviewer: Your User DB and post tables are sharded. How do you generate IDs, and how do you shard?
Candidate: I need IDs that are unique without a central coordinator, 64-bit so they fit in a BIGINT and index well, and roughly time-sortable, so that sorting by ID equals sorting by time. Then feeds and comment lists don't need a separate timestamp index. Instagram's engineering blog describes exactly this. Each ID packs a timestamp, a logical shard number and a per-shard sequence, and is generated inside PostgreSQL.
bit 63 23 22 10 9 0
+--------------------------------------+-------------------+----------------+
| 41 bits: milliseconds since a | 13 bits: logical | 10 bits: |
| custom epoch (~69 years of range) | shard ID (8,192) | sequence |
+--------------------------------------+-------------------+----------------+
(1,024 IDs per ms per shard)
id = (now_ms - EPOCH) << 23 | shard_id << 10 | (seq % 1024)
example: (500,000,000,000 << 23) | (1341 << 10) | 7 = 4,194,304,000,001,373,191
β created 500 billion ms (~15.8 years) after the epoch, lives on logical shard 1341
-- One schema per logical shard; this function is created in each schema (shard 1341 shown)
CREATE SEQUENCE shard_1341.id_seq;
CREATE OR REPLACE FUNCTION shard_1341.next_id(OUT result bigint) AS $$
DECLARE
our_epoch bigint := 1293840000000; -- 2011-01-01T00:00:00Z in ms
shard_id int := 1341;
seq_id bigint;
now_ms bigint;
BEGIN
SELECT nextval('shard_1341.id_seq') % 1024 INTO seq_id;
now_ms := floor(extract(epoch FROM clock_timestamp()) * 1000);
result := ((now_ms - our_epoch) << 23) | (shard_id << 10) | seq_id;
END;
$$ LANGUAGE plpgsql;
- Logical vs physical shards: create thousands of logical shards (PostgreSQL schemas) up front, and map many of them onto each physical server. To scale, move whole logical shards to new machines. Nothing needs re-hashing, only a mapping-table update (see sharding).
- Shard key =
user_idfor users and their posts. A post's ID embeds its shard, so given any post ID you can find its shard without a lookup. - Hot shards (one shard holding several celebrities) are handled by moving logical shards and by the caching and hot-key techniques below.
- Alternatives: Twitter's Snowflake (timestamp + machine ID + sequence, generated by a service), UUIDv4 (random, 128-bit, not sortable, poor index locality), UUIDv7/ULID (sortable 128-bit), and DB auto-increment (needs a central coordinator). Compared in ID generation.
- Cassandra tables use consistent hashing with virtual nodes on the partition key, so they need no manual shard map.
9.4 Caching strategy
| Layer | What's cached | Pattern | Invalidation / TTL |
|---|---|---|---|
| Client (app) | Last feed, images, profile | Stale-while-revalidate | Refresh on open / pull-to-refresh |
| CDN | Media renditions, video segments, avatars | Pull-through, origin shield | Versioned URLs, long TTL; purge on delete |
| Redis: timelines | Post IDs per user | Write-through by fan-out workers | Capped size; rebuilt on miss; evict inactive users |
| Redis/Memcache: objects | Hydrated posts, user profiles | Cache-aside | Delete on update (via events); TTL hours |
| Redis: counters | Likes, comments, followers | Write-back (flushed periodically) | Reconciled with durable store |
| Graph cache | Following lists, block lists | Read-through | Invalidate on follow/unfollow event |
| Search prefix cache | Typeahead results per prefix | Cache-aside | Short TTL (minutes) |
Three issues to name before the interviewer does. First, cache stampede: when a hot key expires, thousands of requests miss at once. Fix it with request coalescing, a lock or lease so one request refills, and early probabilistic refresh. Second, invalidation races: delete the cache entry rather than updating it, and use versioning or leases (Facebook's Memcache paper describes leases for exactly this). Third, cold start after a cache node dies: warm it gradually and have consistent-hashed clients spread the misses. See caching.
9.5 The celebrity / hot-key problem
Interviewer: A celebrity with 400M followers posts. Walk me through everything that gets hot.
Candidate: Five things. (1) Fan-out: already handled, because celebrities are on the pull path, so their post is a single write. (2) The post object: tens of millions of feed requests hydrate the same post:{id} key, so one Redis shard melts. I'd replicate hot keys across several cache nodes (append a random suffix post:{id}#k and read a random replica) and add a tiny in-process LRU cache on every News Feed Service instance, with a TTL of a few seconds. (3) The like counter: one key with millions of increments per minute, so I'd shard it into N sub-counters, batch increments in the service, and sum on read. Showing "4.1M likes" doesn't need exact accuracy. (4) The likes partition in Cassandra: bucket it by (post_id, bucket). (5) The media: every CDN PoP misses at once, so pre-warm major PoPs after processing, and rely on request collapsing and the origin shield. Plus the notification service must not push 400M notifications at once; post notifications are opt-in and rate-limited.
Hot keys are a recurring theme across caching, likes & counters, sharding and CDN. The general toolkit is replicate, split, batch, cache locally, and smooth over time.
9.6 Direct messages
- Transport: persistent WebSocket (or MQTT) connections terminated on a fleet of connection servers. A presence registry in Redis maps
user_id β connection hostwith a heartbeat TTL. HTTP polling would waste battery and server capacity. See direct messaging. - Ordering: a per-thread monotonically increasing sequence number assigned by the DM service. There's no global ordering, since conversations are independent.
- Storage: a Messages table partitioned by
thread_idand clustered byseq. One conversation is one partition; very long conversations are bucketed by time. - Delivery: persist first, then ack the sender. If the recipient is online, push over their socket; if offline, send an event to the Notification Service and APNs/FCM. On reconnect the client syncs
after_seq = N. Client-generatedclient_msg_ids dedupe retries. - Receipts: sent (server persisted), delivered (recipient device acked), read (recipient opened the thread). These are small, idempotent state updates.
- Group chats: fan out to members on send (small groups) and cap group size. End-to-end encryption changes the server's role to storing ciphertext blobs.
10. Minutes 40β43: Bottlenecks & trade-offs
Interviewer: We have a few minutes left. What would break first, and what trade-offs did you make?
Candidate: Here are the risks I'd monitor, in order:
1) Fan-out backlog during peaks. Kafka absorbs it, and I'd autoscale workers on consumer lag. Feeds get slightly stale but nothing fails.
2) Hot keys in Redis and Cassandra from viral content, mitigated as discussed.
3) Ranking latency: it's the biggest server-side cost, so I'd give it a strict deadline and a chronological fallback.
4) Cross-shard consistency: follow edges and counters are written to multiple shards without distributed transactions. I accepted eventual consistency plus repair jobs instead of two-phase commit, which would hurt availability and latency.
5) Regional failure: active-active regions with GeoDNS failover. User data has a home region for writes, so there's some RPO risk for the last few seconds of asynchronously replicated data.
The big trade-offs were: consistency β availability and latency for the feed and counters; storage and write amplification (fan-out, denormalised tables) β read speed; operational complexity (Kafka, many stores) β independent scaling and failure isolation.
Related lessons: resilience patterns, multi-region & DR, observability, core principles.
11. Minutes 43β45: Wrap-up
Candidate: To summarise: a stateless service tier behind a load balancer and API gateway. Media goes directly to object storage via pre-signed URLs and is served through a CDN. Metadata lives in sharded PostgreSQL and Cassandra, with time-sortable 64-bit IDs. Feeds are precomputed in Redis using hybrid fan-out, with pull for celebrities. Every write publishes an event to Kafka, which drives media processing, fan-out, search indexing, notifications and analytics. It meets the latency goals because reads are mostly cache lookups, and it stays available because every tier is replicated and failures degrade gracefully. With more time I'd go deeper on the ranking pipeline, multi-region data placement, abuse and spam prevention, and cost optimisation for cold media.
Interviewer: Thanks, that was great.
12. How Instagram actually does it (public information)
Public engineering posts and conference talks describe several facts worth citing in an interview, carefully hedged:
- Instagram began as a Python/Django monolith and famously scaled to tens of millions of users with a very small engineering team.
- It used sharded PostgreSQL with thousands of logical shards mapped to fewer physical servers, and the 64-bit ID scheme above (41-bit time, 13-bit shard, 10-bit sequence) generated in PL/pgSQL.
- It adopted Cassandra for write-heavy datasets such as feeds, activity and fraud data, and contributed to Cassandra performance work.
- It relied heavily on Memcache and Redis caches and asynchronous task queues, and after the Facebook acquisition it moved into Meta's data centers and infrastructure (Facebook's papers on TAO, Memcache and Haystack describe the graph cache, cache fleet and photo storage ideas used at that scale).
- The home feed moved from chronological to ML-ranked in 2016.
Present these as "public talks describeβ¦", never as insider detail. The interviewer cares that you can reason about the design, not that you've memorised Instagram's internals.
13. Evaluation rubric: junior vs senior vs staff
Interviewers typically score communication, requirements, high-level design, deep dives, trade-offs and operational thinking. Here is what each level tends to look like on this exact question.
| Dimension | Junior / entry (L3βL4) | Senior (L5) | Staff+ (L6+) |
|---|---|---|---|
| Scoping | Asks a few questions; may try to design every feature | Scopes to the core loop crisply; states NFRs with numbers | Also frames the business goal (engagement, cost) and what would change the scope |
| Estimation | Computes QPS/storage with some help | Fast, rounded numbers that drive decisions ("so the feed must be in memory") | Adds cost (e.g. CDN and storage spend), peak vs average, growth, and capacity headroom |
| High-level design | Reasonable boxes; may route media through app servers or use one DB for everything | Clean separation of sync/async and API/media paths; right store per dataset | Same, plus multi-region topology, failure domains and the evolution path from MVP to scale |
| Feed | Pull or push, without the trade-off | Hybrid fan-out with a celebrity threshold, capped timelines, IDs-only timelines | Also inactive-user optimisation, ranking pipeline stages, feature freshness, experimentation (A/B) |
| Data & sharding | "Shard by user_id" | Partition and clustering keys per query; denormalisation; ID scheme; hot partitions | Resharding strategy (logical shards), cross-shard consistency, migrations without downtime |
| Reliability | Mentions replication | Timeouts, retries with idempotency, DLQs, graceful degradation, outbox | SLOs and error budgets, blast-radius reduction, load shedding, regional evacuation drills |
| Trade-offs | Lists pros and cons when asked | Volunteers trade-offs and justifies choices against requirements | Weighs organisational and operational cost; knows when not to build (buy a CDN, etc.) |
| Communication | Needs prompting to move forward | Drives the session, checks in with the interviewer, manages time | Teaches; structures ambiguity; adapts depth to the interviewer's interest |
14. Trade-offs & common mistakes
β What this design optimises for
- Fast, cheap reads (feed served from memory, media from the CDN)
- Writes that return quickly regardless of follower count
- Independent scaling of each service and store
- Failure isolation: background failures don't break user requests
- Easy extension by adding Kafka consumers
β οΈ What it gives up
- Strong consistency for feeds, counts and search
- Storage efficiency (denormalised tables, per-user timelines)
- Simplicity: many datastores, Kafka, worker fleets to operate
- Atomic cross-shard updates (replaced by idempotent retries and repair)
Common mistakes candidates make
- Jumping into boxes before scoping. You'll design the wrong system, or all of it shallowly.
- Estimation without conclusions. Numbers are only useful if you say what they imply ("~150k QPS β must precompute and cache").
- Media through app servers, or blobs in the database. Use pre-signed uploads, object storage and a CDN.
- Pure fan-out on write with no celebrity handling, or pure pull with no answer for read latency.
- "Just add a cache" without saying what's cached, the pattern, the invalidation and the hot-key behaviour.
- A single SQL database for everything with no sharding plan, or NoSQL for everything without reasons.
- Ignoring failure: no timeouts, retries, idempotency or degradation story.
- Sync calls for async work, like resizing images or notifying followers inside the request.
- Monologuing. Not checking in with the interviewer, missing their hints, running out of time before the deep dive.
- Over-engineering things that weren't asked for, like a custom consensus protocol or blockchain, while the feed is still undesigned.
15. How the design changes for similar products
| Product | What's different | Design changes vs Instagram |
|---|---|---|
| Twitter / X | Text-first, tiny payloads; extreme celebrity skew; real-time trends; retweets | Hybrid fan-out is even more critical (timelines in Redis, celebrity pull). Media is optional, so the CDN matters less. Heavy real-time search and trend counting (stream processing). Snowflake IDs. Retweets fan out references, not copies. |
| TikTok | The For You feed is interest-graph, not follow-graph; video-only | Little need for follow-based fan-out. The feed is a recommendation pipeline (candidate retrieval β ranking β re-ranking) over a global video pool, driven by real-time watch signals. Heavy transcoding, ABR streaming and CDN pre-positioning of trending videos. |
| Facebook News Feed | Mixed content (posts, links, groups, pages), bidirectional friendships, heavy ranking | Historically leans more towards pull at read time with aggressive caching (TAO graph cache, aggregators that gather candidates from many leaf servers), because candidate sets are large and ranking is heavy. Privacy checks per item are complex. |
| Saving and organising images into boards; discovery and visual search | The home feed mixes follows and interest-based recommendations. A pin is shared across many boards (store references). Visual similarity search with image embeddings and vector indexes. Long-lived content, so long CDN TTLs pay off. | |
| YouTube | Long videos, creators, search-first and recommendation-first; uploads are big | Upload and transcoding pipelines dominate (many resolutions and codecs, hours of compute per video). Huge storage tiers. The CDN is critical (ISP-embedded caches). Watch-time-based recommendations. Comments and likes resemble Instagram's. |
16. Real-world technologies
| Component | Typical choices | When to mention |
|---|---|---|
| Edge | GeoDNS (Route 53, NS1), CDN (Akamai, Cloudflare, CloudFront, Fastly), Anycast | Global latency and media egress |
| LB / gateway | Envoy, NGINX, HAProxy, AWS ALB/NLB, Kong | Routing, TLS, auth, rate limiting |
| Services | Python/Django, Go, Java/Kotlin, C++; gRPC/Thrift | Stateless business logic |
| Relational | PostgreSQL, MySQL (Vitess, Citus for sharding) | Users, settings, constraints |
| Wide-column | Cassandra, ScyllaDB, HBase, Bigtable, DynamoDB | Posts, likes, comments, messages, timelines-on-disk |
| Cache | Redis, Memcached, TAO-like graph caches | Timelines, counters, hot objects |
| Blob | S3, GCS, Azure Blob, Ceph, Haystack/f4-style systems | Photos and videos |
| Streaming | Kafka, Pulsar, Kinesis; Flink, Spark Streaming | Events, fan-out, real-time aggregates |
| Search | Elasticsearch/OpenSearch, Solr | Users, hashtags, places |
| Analytics / ML | Spark, Presto/Trino, Hive, data warehouse; PyTorch; feature stores | Ranking, Explore, metrics |
17. Interview corner: follow-up questions
These are the follow-ups interviewers use to probe depth after the main design. Try answering each before opening it.
1. How do you pick the follower threshold for switching a user from push to pull?
Make it a tunable driven by cost, not a magic number. Fan-out cost per post β followers Γ posting rate, and pull cost β number of feed reads that touch that author. Push is cheaper while followers Γ posts/day is below what we'd pay in extra read work. In practice choose something like 100kβ1M followers, adjusted by activity (an account with 2M followers that posts once a month can still be pushed). Apply hysteresis so accounts near the boundary don't flip-flop, and measure Redis write throughput and feed p99 to tune it.
2. A user deletes a post. What has to happen across the system?
Synchronously: mark the post REMOVED (a soft delete) in Cassandra and invalidate the post-object cache, so hydration returns nothing and the post vanishes from every feed immediately, without touching millions of timelines. Asynchronously, via a post.deleted event: remove it from the search index, purge CDN URLs (signed URLs also expire), delete likes and comments in the background, remove it from the analytics serving tables, and after a retention period hard-delete the blobs in object storage for privacy/GDPR compliance. Timeline entries that still hold the ID are filtered out at read time and age out.
3. How do you show the correct "viewer_has_liked" flag for 20 posts efficiently?
Batch it. One multi-get against a per-user "recent likes" structure, such as a Redis set of post IDs liked in the last N days, or a Bloom filter for fast negatives backed by an exact lookup in likes_by_user for positives. That's 20 lookups in one round trip, not 20 queries. Older posts fall back to a Cassandra lookup on (post_id, user_id), which is a single-partition read.
4. How would you implement the Explore page?
It's a recommendation problem, not a follow-graph one. Candidate generation retrieves thousands of posts from many sources (accounts similar to ones you engage with, embedding nearest-neighbour search, trending in your region). A lightweight model filters to hundreds, a heavy model ranks them, and then re-ranking adds diversity and integrity filters. It's served from precomputed candidate pools plus real-time features, and trained offline in Analytics & ML Training on engagement logs (see analytics & ML).
5. How do you enforce privacy (private accounts, blocks) in the feed and search?
Defence in depth. At write time, fan-out only goes to approved followers. At read time, the feed filters candidates against the viewer's block and mute lists (cached sets) and the author's privacy state. Hydration re-checks visibility on the post object. Search filters results by visibility, and profile views go through an authorization check in the service. Media URLs are signed and short-lived so leaked links expire. Privacy checks are the one place where we don't accept stale caches for long: block events invalidate caches immediately.
6. How do you migrate from one database shard layout to another with zero downtime?
Use logical shards so a migration moves whole units. For each logical shard: start replicating it to the new server (snapshot plus change stream), let it catch up, briefly pause writes for that one shard (milliseconds to seconds), flip the shard map entry, then resume. Alternatively, dual-write with backfill and verification before switching reads. Do it shard by shard with feature flags and rollback. Never do a big-bang migration.
7. What happens if the Redis timeline cluster loses a node?
Replicas are promoted (Redis Cluster failover takes seconds). If data is lost anyway, affected users' timelines are rebuilt on demand via fan-out-on-read from the graph and posts_by_user, then written back. To avoid a thundering herd, rebuilds are rate-limited and coalesced per user, and the feed service may serve the most recent posts chronologically while rebuilding. Timelines are a cache of derivable data, so no data is permanently lost.
8. How do you count views on Reels accurately at huge scale?
Clients send view events in batches to an ingestion endpoint that writes them to Kafka. A stream processor (Flink or Spark Streaming) deduplicates per (viewer, reel, time window), aggregates per reel per minute, and updates counters in a fast store. Displayed counts are approximate and slightly delayed. Exact counts for billing or creator analytics come from batch jobs over the warehouse. HyperLogLog gives cheap unique-viewer estimates.
9. How would you design for multiple regions? Where does a user's data live?
Active-active regions behind GeoDNS. Stateless services run everywhere. Each user has a home region for writes to their own data (avoiding multi-leader conflicts), and data replicates asynchronously to other regions for local reads. Cassandra can replicate per keyspace with NetworkTopologyStrategy. Media is in object storage replicated across regions and served via the CDN. On a regional outage, GeoDNS moves traffic away and a standby region takes over home-region duties, accepting a small RPO. Data residency laws may pin some users' data to specific regions (see multi-region).
10. How do you protect the system from abuse such as spam accounts, fake likes and scraping?
Layers. Rate limits at the gateway per user, IP and device, on sensitive actions like follow, like, comment and search. Device and account reputation scores. ML classifiers on the event stream that detect bursts and coordinated behaviour. CAPTCHA and phone verification challenges. Shadow-limiting suspicious actions so they don't count. Signed, short-lived media URLs. WAF and bot detection at the edge. Engagement from flagged accounts is excluded from counts and ranking signals (see security, rate limiting).
11. Why Kafka instead of a simple task queue like RabbitMQ or SQS?
We need one event, many independent consumers (fan-out, search, notifications, analytics, media), each with its own pace and the ability to replay history, for example to rebuild a search index or backfill a new feature. Kafka's durable, partitioned log with consumer groups gives that, plus per-key ordering and very high throughput. A classic queue deletes messages after consumption and suits per-task work distribution. You might still use one inside a component, such as a transcoding job queue (see message queues).
12. How do you know the system is healthy? What would you alert on?
Define SLOs on user-facing outcomes: feed p99 latency and error rate, upload success rate, time from post to visibility for followers, DM delivery latency, push success rate. Alert on burn rates of those error budgets, not on CPU. Supporting metrics include Kafka consumer lag per group, cache hit ratios, DB p99 and replication lag, CDN hit ratio and origin load, and DLQ growth. Distributed tracing finds which hop regressed, and structured logs carrying trace IDs explain why (see observability).
13. The interviewer says "now support 10Γ the users". What changes?
Mostly nothing structural, which is the point of horizontal scaling: add service instances, Cassandra nodes, Redis shards, Kafka partitions and CDN capacity. What needs attention: raise the celebrity threshold logic and fan-out capacity (write amplification grows with average follower count, not just users), add logical-to-physical shard moves, add regions to keep latency down, apply tiered and erasure-coded storage for cost, push more work to the client (resizing, caching), and review hot-key mitigations. Then re-run the estimates to find the new first bottleneck.
14. How do comments with replies work, and how are they ordered?
Store comments in comments_by_post, partitioned by post and clustered by time-sortable comment_id. Replies carry a parent_id and are stored in replies_by_comment so a thread loads with one partition read. Display order is ranked: the author's replies, friends' comments and highly liked comments first, using a cached "top comments" list per post, with chronological pagination for the rest. Comment counts use the same sharded-counter approach as likes, and a comment.created event triggers moderation (spam/abuse classifiers) and notifications.
18. Check your understanding
You have 45 minutes. At minute 25 you haven't drawn the high-level design yet. What do you do?
Stop refining earlier sections and draw the end-to-end design immediately, even if it's rough: clients, LB/gateway, core services, stores, Kafka, CDN. Walk one write and one read through it, then ask the interviewer which area to deep-dive. A complete, simple design beats a perfect API with no architecture.
With 500M DAU each loading the feed 10Γ/day, what's the average feed QPS, and roughly what peak would you design for?
500M Γ 10 = 5B requests/day Γ· 86,400 s β 58k QPS on average. With a 2β3Γ peak factor, design for ~150k QPS, plus headroom for a regional failover that shifts another region's traffic.
Decode this Instagram-style ID: (t << 23) | (s << 10) | q. How many IDs per millisecond can one logical shard generate, and how many logical shards are possible?
The sequence field has 10 bits, giving 2^10 = 1,024 IDs per millisecond per shard (about 1 million per second per shard). The shard field has 13 bits, giving 2^13 = 8,192 logical shards. The 41-bit timestamp covers 2^41 ms β 69.7 years from the custom epoch.
A candidate proposes pure fan-out on write. Give the interviewer's best counter-question and the expected answer.
"What happens when an account with 300M followers posts?" With pure push, that's 300M timeline writes per post. At, say, 1M writes/s of fan-out capacity it takes 5 minutes, delays everyone else's posts and wastes writes on inactive users. The expected fix is the hybrid model: pull celebrities at read time and push for everyone else.
Which of these must be strongly consistent: (a) like count, (b) block list, (c) home feed contents, (d) DM order within a thread, (e) username uniqueness?
(b), (d) and (e). A block must take effect immediately (privacy). Messages in a thread must appear in a consistent order. Two users must never get the same username, which needs a unique constraint or a conditional write. (a) and (c) can be eventually consistent.
Why does a timeline store only post IDs rather than full post objects?
IDs are 8 bytes, while objects are ~1 KB, so the cache is ~100Γ smaller (2 TB vs ~250 TB). Edits, deletes and counter changes don't need to be fanned out to millions of timelines: hydration always reads the current object from one place. It also keeps fan-out writes tiny and fast.
How would the design change if the interviewer said "design TikTok's For You feed" instead?
The follow graph becomes a minor signal. The feed is generated by a recommendation pipeline (retrieval from a global content pool via embeddings and trending sources β ranking β re-ranking) using real-time watch-time signals, so there's no follower fan-out. Video transcoding, ABR streaming and CDN pre-positioning become central, and the feedback loop from events to models must be fast (minutes).
- Follow a time-boxed structure: clarify β requirements β estimates β API β data model β high-level design β deep dives β trade-offs β wrap-up.
- Turn numbers into decisions: read-heavy β precompute and cache; huge egress β CDN; high fan-out β async workers.
- The heart of Instagram is the hybrid feed (push for most, pull for celebrities) over IDs-only timelines in Redis.
- Media goes direct to object storage with pre-signed URLs, is processed asynchronously and served via the CDN.
- Know Instagram's 64-bit ID scheme (41-bit time, 13-bit logical shard, 10-bit sequence) and why logical shards make resharding easy.
- Senior signals: volunteering trade-offs, failure modes and hot-key mitigations, and driving the conversation.
Related lessons
- How to approach a system design problem: the framework this script follows
- Requirements and capacity estimation: minutes 0β12 in depth
- API design: cursors, idempotency keys, protocols
- News feed: the main deep dive
- ID generation and sharding: the ID scheme and logical shards
- Caching and likes & counters: hot keys and counters
- Media upload and media processing: the media deep dive
- Direct messaging: the DM deep dive
- End-to-end request flows: every flow traced hop by hop
- Glossary: every term in one place