TL;DR

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.

🧠 Analogy A system design interview is like an architect's first meeting with a client who says "build me a house". A good architect doesn't start drawing bricks. They ask how many people will live there, the budget and the climate, sketch a floor plan, and then go deep on whatever the client cares about most (the kitchen, the stairs, the solar panels). The interviewer is that client. The first ten minutes decide whether you build the right house.

2. The 45-minute plan

Clarify Reqs Estimate API Data model High-level design Deep dives (2–3 topics) Risks Wrap 05812162128404345 Minutes β†’ Roughly the first third clarifies and scopes the problem, the middle third builds the design, and the last third is deep dives and trade-offs.
A time budget for a 45-minute interview (about 40 minutes of design after introductions). Adjust it live: if the interviewer jumps to a deep dive early, follow them.
MinutesPhaseGoalOutput on the whiteboard
0–5Clarifying questionsAgree on scope and scaleBullet list of in/out of scope features and user numbers
5–8RequirementsFunctional + non-functional targetsFRs, NFRs (latency, availability, consistency)
8–12Capacity estimationSize the system; find what's hardQPS, storage, bandwidth, cache size
12–16API designDefine the contract5–8 endpoints with key params
16–21Data modelEntities, keys, DB choicesTables with partition and clustering keys
21–28High-level designEnd-to-end boxes and arrowsThe architecture diagram
28–40Deep divesShow depth on the hard partsFeed fan-out, media, sharding, caching, hot keys, DMs
40–43Bottlenecks & trade-offsShow judgementSPOFs, failure modes, what you'd change at 10Γ—
43–45Wrap-upSummarise; leave a strong last impression3–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.

🎀 Dialogue: opening

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.

QuestionWhy it mattersAnswer assumed in this walkthrough
Which features are in scope?Prevents designing everything shallowlyUpload 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 designHeavily read-skewed, ~100:1 views to posts
Photos only, or video too? Max size?Storage, transcoding, streamingBoth. 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 consistencyA few seconds of delay is fine
Global users? Regions?CDN, multi-region, data residencyGlobal. Assume multiple regions plus a CDN.
Private accounts? Blocking?Authorization on every readYes. They must be enforced on feed, profile and search.
Any celebrities with huge followings?Hot keys, fan-out explosionYes. Top accounts have hundreds of millions of followers.
πŸ’‘ Tip Spend no more than ~5 minutes here. Asking 30 questions looks like stalling. Ask the 5–8 that change the design, and state your own sensible assumptions for the rest out loud.

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.

🎀 Dialogue: priorities

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%+)
NumberValueDesign consequence
Read : write~50–100 : 1Optimise reads: precompute feeds, cache aggressively, CDN
Feed QPS~60k avg, ~150k peakStateless feed service, timeline in memory (Redis)
Fan-out writes~240k/s average, burstyAsync workers via Kafka; can't be in the request path
Media storage~90 PB/yearObject storage with tiering and erasure coding; never a DB
Egress~1 TbpsCDN is mandatory
Metadata~36 TB/year, high write rateSharded DBs (PostgreSQL for users, Cassandra for posts/likes)
🎀 Dialogue: keep it moving

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)
🎀 Dialogue: API probes

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).

DataStoreWhy
Users, profiles, settings, authPostgreSQL, sharded by user_id, with read replicasRelational, modest size, needs constraints (unique username) and transactions
Posts, comments, likes, storiesCassandra (wide-column)Huge write volume, simple key-based access, linear horizontal scale, TTL support
Follow graphSharded KV/wide-column (both directions) plus a graph cacheTwo adjacency lists: followers and following
Timelines, counters, hot objectsRedisSub-millisecond reads of precomputed data
Photos and videosObject Storage (S3) + CDNCheap, durable blobs; never store blobs in a DB
SearchElasticsearchInverted index for prefix and full-text search
DMsCassandra/HBase, partitioned by thread_idAppend-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)
🎀 Dialogue: data model probes

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.

CDN DNS πŸ“± ClientsMobile Β· Web Load Balancer API GatewayJWT Β· rate limit Upload Service Stories Service Like & Comment Follow/Graph News Feed Service Search Service Direct Messaging (WS) User/Auth Service Object Storage (S3) Post/Comment/Story (Cassandra) Social Graph store Feed Cache (Redis) Search Index (Elasticsearch) Messages Store (Cassandra/HBase) User DB (PostgreSQL, sharded) Kafka (event bus) Search Indexer Fan-out Workers Media Workers Notification Svc Analytics & ML APNs / FCM origin pull
The high-level design to draw on the whiteboard: a synchronous path (Clients β†’ LB β†’ API Gateway β†’ stateless services β†’ data stores), an async path (services β†’ Kafka β†’ workers), and a media path (Object Storage β†’ CDN β†’ Clients).
🎀 Dialogue: walking the diagram

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

🎀 Dialogue

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 cost1 Redis readN partition reads (N = followees)1 Redis read + a few cached celebrity reads
Write costF writes (F = followers)1 writeF writes only for normal accounts
Celebrity postCatastrophic (100M+ writes)FineFine (pulled)
Inactive usersWasted writesNo wasteSkip inactive followers; rebuild on return
FreshnessSeconds of lagReal-timeSeconds 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

9.3 Sharding & Instagram's ID scheme

🎀 Dialogue

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;

9.4 Caching strategy

LayerWhat's cachedPatternInvalidation / TTL
Client (app)Last feed, images, profileStale-while-revalidateRefresh on open / pull-to-refresh
CDNMedia renditions, video segments, avatarsPull-through, origin shieldVersioned URLs, long TTL; purge on delete
Redis: timelinesPost IDs per userWrite-through by fan-out workersCapped size; rebuilt on miss; evict inactive users
Redis/Memcache: objectsHydrated posts, user profilesCache-asideDelete on update (via events); TTL hours
Redis: countersLikes, comments, followersWrite-back (flushed periodically)Reconciled with durable store
Graph cacheFollowing lists, block listsRead-throughInvalidate on follow/unfollow event
Search prefix cacheTypeahead results per prefixCache-asideShort 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

🎀 Dialogue

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

10. Minutes 40–43: Bottlenecks & trade-offs

🎀 Dialogue

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

🎀 Dialogue: closing summary

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)

πŸ“Έ In Instagram

Public engineering posts and conference talks describe several facts worth citing in an interview, carefully hedged:

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.

DimensionJunior / entry (L3–L4)Senior (L5)Staff+ (L6+)
ScopingAsks a few questions; may try to design every featureScopes to the core loop crisply; states NFRs with numbersAlso frames the business goal (engagement, cost) and what would change the scope
EstimationComputes QPS/storage with some helpFast, 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 designReasonable boxes; may route media through app servers or use one DB for everythingClean separation of sync/async and API/media paths; right store per datasetSame, plus multi-region topology, failure domains and the evolution path from MVP to scale
FeedPull or push, without the trade-offHybrid fan-out with a celebrity threshold, capped timelines, IDs-only timelinesAlso 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 partitionsResharding strategy (logical shards), cross-shard consistency, migrations without downtime
ReliabilityMentions replicationTimeouts, retries with idempotency, DLQs, graceful degradation, outboxSLOs and error budgets, blast-radius reduction, load shedding, regional evacuation drills
Trade-offsLists pros and cons when askedVolunteers trade-offs and justifies choices against requirementsWeighs organisational and operational cost; knows when not to build (buy a CDN, etc.)
CommunicationNeeds prompting to move forwardDrives the session, checks in with the interviewer, manages timeTeaches; 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

15. How the design changes for similar products

ProductWhat's differentDesign changes vs Instagram
Twitter / XText-first, tiny payloads; extreme celebrity skew; real-time trends; retweetsHybrid 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.
TikTokThe For You feed is interest-graph, not follow-graph; video-onlyLittle 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 FeedMixed content (posts, links, groups, pages), bidirectional friendships, heavy rankingHistorically 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.
PinterestSaving and organising images into boards; discovery and visual searchThe 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.
YouTubeLong videos, creators, search-first and recommendation-first; uploads are bigUpload 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

ComponentTypical choicesWhen to mention
EdgeGeoDNS (Route 53, NS1), CDN (Akamai, Cloudflare, CloudFront, Fastly), AnycastGlobal latency and media egress
LB / gatewayEnvoy, NGINX, HAProxy, AWS ALB/NLB, KongRouting, TLS, auth, rate limiting
ServicesPython/Django, Go, Java/Kotlin, C++; gRPC/ThriftStateless business logic
RelationalPostgreSQL, MySQL (Vitess, Citus for sharding)Users, settings, constraints
Wide-columnCassandra, ScyllaDB, HBase, Bigtable, DynamoDBPosts, likes, comments, messages, timelines-on-disk
CacheRedis, Memcached, TAO-like graph cachesTimelines, counters, hot objects
BlobS3, GCS, Azure Blob, Ceph, Haystack/f4-style systemsPhotos and videos
StreamingKafka, Pulsar, Kinesis; Flink, Spark StreamingEvents, fan-out, real-time aggregates
SearchElasticsearch/OpenSearch, SolrUsers, hashtags, places
Analytics / MLSpark, Presto/Trino, Hive, data warehouse; PyTorch; feature storesRanking, 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).

🎯 Key takeaways

Related lessons