Home › High-Level Design › Design Instagram
D

Design Instagram

A photo and short-video app where people post, follow each other, and scroll an endless feed. It looks like four small features and then four of the hardest distributed-systems problems hide inside them at once.

High-Level DesignHard14 min read

The candidates who pass hold all four in their head at the same time and keep the trade-offs straight. That is the whole game.

If you remember five things

The whole answer in one glance. Everything below is the defense of these five calls.

  1. Read-optimized by design. The feed is 50:1 read-heavy, so you pay the expensive work once at write time (fan-out) and let 50 cheap reads ride on top.
  2. Hybrid fan-out. Push posts to followers for normal accounts, pull for the handful of celebrities, and merge the two at read time. This one call is driven entirely by that 50:1 ratio.
  3. Media never touches app servers. Signed-URL uploads straight to blob storage, CDN for delivery, metadata only in the database. That is what keeps 60 PB a year from eating the company.
  4. Hot keys get sharded and replicated. A viral post is one key read millions of times and liked millions of times, so you split the counter and replicate the content, and only when it actually goes hot.
  5. Eventual consistency with a read-time filter. Push the easy case eagerly, then clean up unfollows, deletes, and blocks with one cheap filter on read instead of chasing every cached feed.

1Clarifying the requirements#

Pin down scope before drawing a single box. The "right" Instagram for a 45-minute interview is a tiny slice of the real thing, and the fastest way to fail is to design all of it shallowly.

Candidate: Are we building the whole app, or should I focus on one surface?

Interviewer: Focus on the core: posting media, following users, and the home feed. Assume stories and DMs are out of scope.

Candidate: Got it. Is the feed chronological or ranked?

Interviewer: Ranked eventually, but you can start chronological and tell me where ranking plugs in.

Candidate: Last one. What is our rough scale? Hundreds of millions of users?

Interviewer: Yes. Assume 500 million daily actives. Design for that.

What those answers just told me:

  • 500M DAU means the naive "read everyone I follow at feed time and merge" falls over on day one, so fan-out is the centerpiece.
  • "Ranked eventually" means keep post IDs and post content separable, so a scorer can reorder IDs before hydration.
  • Narrating where I plan to spend my deep-dive budget earns trust early, so I say "fan-out is the hard part" out loud now.

Functional requirements

  • Upload a photo or short video with a caption.
  • Follow and unfollow other users.
  • See a home feed of recent posts from people you follow, newest first.
  • View any single post and its like and comment counts.
  • Like and comment on posts.

Non-functional requirements

  • Read-heavy. Feed loads vastly outnumber posts, and I will quantify the exact ratio because it decides the architecture.
  • Fast feed. Target under 200 ms server-side at p99, because above that the scroll feels sticky and engagement drops.
  • Highly available. A failed write is annoying; a failed feed load is unacceptable. Lean AP over CP wherever possible.
  • Eventually consistent feed. A friend's post two seconds late is nobody's bug.
  • Durable media. We never lose someone's photos, full stop.

Parked on purpose (naming them is a seniority signal, not a gap): content moderation, abuse and spam detection, search, and hashtag discovery.

From the interviewer's chair

This opening is scored before you draw a box. I am listening for whether you pull out the read:write asymmetry and the celebrity case on your own. Open with a login flow or CRUD endpoints and I have quietly downgraded you. Name the hard part first, then go spend your time there.

2Back-of-the-envelope estimation#

Get the numbers on the board so the design stands on something instead of vibes. Round aggressively: the goal is an order of magnitude, not a balance sheet.

The point of this section

You are not here to compute storage to three decimals. You are here to surface one load-bearing number: reads outnumber writes about 50 to 1. That asymmetry is what justifies doing the expensive work at write time and serving nearly free on read. Find it fast, then move on.

Users and writes.

  • 500M DAU. Say each posts 0.2 times per day (most people lurk, a few power users post a lot).
  • That is 100M posts per day.
100,000,000 posts / 86,400 sec ≈ 1,160 posts/sec average
  • Traffic is spiky around evenings and events, so plan for 3x peak: about 3,500 writes/sec.
  • That is modest. Any sharded datastore handles it without breaking a sweat, which already tells me writes are not my problem.

Reads.

  • Each DAU opens the feed maybe 10 times a day, one page per open.
500,000,000 users × 10 opens = 5,000,000,000 feed reads/day
5,000,000,000 / 86,400 ≈ 58,000 reads/sec average
  • At 3x peak that is about 175,000 feed reads/sec.
  • Read-to-write ratio is roughly 50 to 1. When reads outnumber writes 50 to 1, you pay the expensive work once on the write side and let 50 cheap reads ride on top. That one sentence is the thesis of the whole system.

Media storage.

  • Average post is one image at 1.5 MB compressed, plus a 100 KB thumbnail, so call it 1.6 MB.
100M posts/day × 1.6 MB ≈ 160 TB/day
160 TB × 365 ≈ 58 PB/year
  • Call it 60 PB a year of new media before replication; with 3x cross-region replication, closer to 180 PB a year on disk.
  • This goes in blob storage, never a database. Putting 1.6 MB blobs in a relational row is the mistake that ends an interview, so I say explicitly: media and metadata live in different systems.

Metadata.

  • A post row (id, author, caption, timestamps, counters, media pointer) is about 1 KB.
100M posts/day × 1 KB = 100 GB/day of metadata ≈ 36 TB/year
  • Tiny next to media, by a factor of roughly 1,600. It fits comfortably in a sharded datastore, and that contrast is exactly why I split the two.

Feed cache (the number that drives the cost).

  • Keep the latest 500 post IDs per active user in memory; each ID plus a score is about 16 bytes.
500M users × 500 IDs × 16 bytes = 4 TB
~4 TB
The number that drives the costPrecomputed feed held in RAM for 500M active users. Spread over, say, 200 Redis nodes with 32 GB usable each, it fits with headroom. It is why I keep lists as IDs only, cap them near 500, and stop warming dormant users. The push model I am about to argue for is not a fantasy; it fits a budget I can defend.

3Core APIs#

Clean REST over HTTPS, surface kept small on purpose. Every extra endpoint is a thing I have to defend and the interviewer did not ask for.

POST   /v1/posts
GET    /v1/posts/{postId}
GET    /v1/feed?cursor={cursor}&limit=20
POST   /v1/users/{userId}/follow
DELETE /v1/users/{userId}/follow
POST   /v1/posts/{postId}/likes
POST   /v1/posts/{postId}/comments

Upload is a two-step dance so big bytes never touch my app servers:

  • Streaming 1.6 MB through an app tier at 3,500 writes/sec would waste bandwidth and turn my stateless fleet into a file proxy.
  • So I hand the client a signed URL and get out of the way.
// 1) client asks for a place to put the file
POST /v1/uploads
Response: {
  "uploadUrl": "https://blob.example.com/signed-put-url",
  "mediaId": "md_9f2a7c"
}

// 2) client PUTs bytes straight to blob storage, then creates the post
POST /v1/posts
{
  "mediaId": "md_9f2a7c",
  "caption": "sunrise over the ridge",
  "type": "image"
}

Two choices worth defending:

  • The signed URL is scoped to one object key and expires in a few minutes, so a leaked URL is near worthless.
  • The post row is created only in step two, so a half-finished upload leaves an orphan blob (cheap to garbage-collect) instead of a broken post.

The feed response is cursor-paginated, not offset:

  • Offset pagination breaks when new posts arrive mid-scroll: row N shifts, the user sees duplicates or skips.
  • The cursor is an opaque token encoding the score and id of the last item seen. The client treats it as a blob and sends it back.
GET /v1/feed?cursor=eyJzIjoxNjk5...&limit=20
Response: {
  "items": [
    { "postId": "p_55a1", "author": "u_120", "mediaUrl": "...", "likeCount": 4120 }
  ],
  "nextCursor": "eyJzIjoxNjk4..."
}
  • Every write endpoint takes an idempotency key in a header, so a mobile retry after a flaky network never creates a duplicate post or double-like. At this scale, retries are constant, so idempotency is not optional.

4High-level design#

The point of this section

The box diagram is table stakes. What earns points is the single seam: a queue between the post write and the fan-out. That seam lets posting feel instant, absorbs spikes, and guarantees a fan-out backlog can never block or drop a write. Draw the boxes fast, then defend that seam.

The spine is standard, so draw it fast:

  • Clients hit a load balancer, then a gateway.
  • The gateway routes to three services, each owning its own data.
  • Writes and reads take deliberately different paths, and media lives outside the request path entirely.
flowchart LR C[Client] --> LB[Load Balancer] LB --> GW[API Gateway] GW --> POST[Post Service] GW --> FEED[Feed Service] GW --> GRAPH[Graph Service] POST --> PDB[(Post Store)] POST --> BLOB[(Blob Storage)] POST --> Q[Fanout Queue] Q --> FW[Fanout Workers] FW --> FCACHE[(Feed Cache)] GRAPH --> GDB[(Graph Store)] FEED --> FCACHE FEED --> PDB BLOB --> CDN[CDN] CDN --> C
The whole system at a glance. The one seam that matters is the Fanout Queue between the Post Service and the fan-out workers: it lets a post return instantly and stops a fan-out backlog from ever blocking a write.

Posting.

  • The client uploads bytes to blob storage directly via a signed URL, then calls Post Service to create the metadata row.
  • Post Service writes to the Post Store and drops a message on the fanout queue.
  • It returns 201 immediately, before any feed is touched. The post is durable even though feeds are not updated yet.
  • The write path is short; the expensive fan-out happens after I have already told the user "posted."

Fan-out.

  • Workers pull from the queue, look up the author's followers in the Graph Service, and push the new post id into each follower's feed list in the Feed Cache.
  • This is the heavy lifting, done async off the write path, so a slow fan-out never makes a user wait on the post button.

Reading the feed.

  • Feed Service reads a page of post ids straight from the Feed Cache, hydrates them with cached metadata, and returns.
  • No joins, no scanning a follows table at read time.
  • A feed read is one sorted-set range query plus a batched metadata lookup. That is how I keep 175k reads/sec under 200 ms.

Serving media.

  • Images and videos are served by a CDN, not my servers. Blob storage is the origin.
  • The vast majority of media bytes never hit my infrastructure twice, which keeps 60 PB a year from turning into a bandwidth bill that eats the company.

Why split the services this way.

  • Post Service owns post metadata, Graph Service owns follow relationships, Feed Service owns assembling and serving feeds.
  • Their load profiles differ: Feed is read-bound at 175k/sec, Post is write-bound at 3.5k/sec, Graph gets hammered in bursts during fan-out.
  • Coupling them into one service would mean scaling all three for the worst case of any one.

Sortable unique IDs (Snowflake).

  • 64 bits: a timestamp in the high bits, a machine id, and a per-ms sequence in the low bits.
  • Roughly time-ordered, so I get sortable ids for cursoring and shard distribution for free.
  • Generated without a central counter, so there is no id-allocation service to become a bottleneck or a single point of failure.

5Data model#

Use a few stores, each picked for its access pattern. Picking the right store per workload matters more here than finding one database to rule them all.

  • Sharded relational-style store for metadata (sharded by a hash of the id).
  • Wide-column or key-value store for the follow graph and feed lists.
  • Blob storage for media, referenced by key only.

posts

Field Type Notes
post_id bigint Snowflake id, sortable by time, shard key
author_id bigint indexed
media_key string pointer into blob storage
media_type enum image or video
caption text up to 2200 chars
created_at timestamp drives chronological ordering
like_count bigint denormalized counter
comment_count bigint denormalized counter

follows

Field Type Notes
follower_id bigint shard key, "who I follow" lookups
followee_id bigint indexed for "who follows me"
created_at timestamp for ordering and anti-spam
  • Store the edge twice, keyed by follower and by followee, because I need both directions: "who does user X follow" at read time, and "who follows author Y" during fan-out.
  • Both directions are append-heavy, so the extra write is trivial next to the read savings.

feed (in cache, per user)

Field Type Notes
user_id key the feed owner
post_id member element in a sorted set
score number created_at or ranking score, sort key
  • A Redis sorted set capped at the latest 500 entries; trim past the cap with a single ZREMRANGEBYRANK, so per-user memory stays at the 16 bytes times 500 I budgeted.
  • IDs only, not full posts, so a ranking layer can reorder IDs before hydration.
  • A cold user (returning after a month) with no cached feed falls back to on-demand compute from the people they follow, backfilling the sorted set.

likes and comments get their own tables keyed by post_id, with counts denormalized onto the post row:

  • A feed page shows 20 posts; counting likes live for each would be 20 aggregate queries per read times 175k reads/sec, which is absurd.
  • The denormalized counter turns that into a single field read.

6Deep dives#

6.1 Fan-out: the central decision#

The whole interview turns on which feed strategy you pick. State both, commit, then defend the commitment. A senior answer is not "it depends," it is "here is my choice and here is exactly when I would revisit it."

  • Fan-out on write (push). At post time, copy your post id into every follower's feed list. Reads become trivial: just read your list. Writes get expensive: one post by someone with 10,000 followers means 10,000 cache writes.
  • Fan-out on read (pull). Write the post once. At feed time, gather who you follow, pull their recent posts, and merge. Writes are cheap; reads are slow, because a merge across hundreds of authors is real work on the hot path running at 175k/sec.
Default for this product
Fan-out on write (push)
Read cost
Trivial: read one precomputed list.
Write cost
Heavy: one post touches every follower's list.
Best when
Reads dominate and most accounts are normal-sized.
Breaks when
A celebrity posts: one write becomes tens of millions.
Keep for the few
Fan-out on read (pull)
Read cost
Heavy: fetch and merge from everyone you follow.
Write cost
Trivial: store nothing per follower.
Best when
Writes dominate, or the author has a huge following.
Breaks when
A read-heavy feed makes every load expensive.

For a 50:1 read ratio, push wins almost everywhere: precompute the expensive thing once at write, serve it 50 times for free. So push is my default.

Pause before you read on: a celebrity with 40 million followers hits post. With pure push, what exactly breaks, and how bad is it?
Reveal the answer
One post becomes 40 million cache writes, and it happens in a burst every single time they post. The fanout queue backs up for minutes and everyone's feed goes stale, not just the celebrity's followers. That one hot author creates more write work than the rest of the site combined. This is the case that forces a hybrid model, and reaching for it unprompted is what earns the room.

So I go hybrid.

  • Default to push, because reads dominate and I want them cheap.
  • Set a threshold, say 50,000 followers. Above it, the account is marked "wide" and I do not fan out their posts.
  • At feed time, Feed Service merges the reader's precomputed push feed with a small pull from the handful of wide accounts they follow.
  • Most people follow only a few dozen wide accounts, so the pull side stays tiny and fast.
sequenceDiagram participant A as Author participant P as Post Service participant Q as Fanout Queue participant W as Fanout Worker participant G as Graph Service participant FC as Feed Cache A->>P: create post P->>P: write post row P->>Q: enqueue fanout job P-->>A: 201 created W->>Q: pull job W->>G: get followers of author alt author has under 50k followers G-->>W: follower list W->>FC: push post id to each feed else wide account W->>W: skip fanout, mark wide end
The write path. The post is acknowledged right after the durable write, and fan-out runs behind the queue. Normal authors get pushed to every follower; wide authors are skipped and pulled at read time.
sequenceDiagram participant U as User participant F as Feed Service participant FC as Feed Cache participant G as Graph Service participant PS as Post Store U->>F: GET feed F->>FC: read precomputed feed ids F->>G: get wide accounts I follow F->>PS: pull recent posts from wide accounts F->>F: merge and rank by score F->>PS: hydrate post metadata F-->>U: feed page
The read path. Your precomputed push feed and a fresh pull of the few wide accounts you follow get merged by score, then hydrated into full posts in one batch read.

The threshold is a knob, not a law.

  • Raise it and I shift load onto fan-out workers; lower it and I shift load onto feed reads.
  • The merge touches maybe a few dozen wide accounts at most, not hundreds, so the read stays comfortably inside 200 ms.
  • Tune it from production fan-out latency and queue-depth metrics, and ideally make it dynamic per posting rate, not just follower count.

Fan-out is at-least-once, and that is fine here by construction.

  • A worker retry can push the same post id twice.
  • No dedup table needed: the feed is a sorted set scored by post_id, so re-adding an existing id is a no-op.
  • Idempotency falls out of the data structure. Say this out loud; the interviewer is watching for whether you noticed retries can duplicate.
Red flag that fails you here

Committing to pure push, feeling clever about cheap reads, then freezing when I say "a celebrity just posted." Reach for hybrid on your own, and say why in numbers. Candidates who wait to be told about the celebrity case are the ones who have not actually run this system.

6.2 The hot-key problem#

When a celebrity posts, two things catch fire at once, and they fail in opposite directions.

  • Read hot key. Millions open that single post, so one post_id becomes a read key that can melt one cache shard, because hashing by post_id sends every read to the same node.
  • Write hot key. Likes pour in and the like_count counter becomes a write key on the same row.

Fix the read hot key by spreading it:

  • Replicate hot post metadata across multiple cache nodes and let the client library pick a replica at random, spreading reads across, say, 10 copies so each sees a tenth of the storm.
  • Push popular media aggressively to the CDN edge, so the bytes never come back to origin.
  • A post with 20 million views costs me almost nothing after the first few seconds: the 21st viewer and everyone after is served from an edge node near them, not from blob storage.

Fix the write hot key by sharding the counter:

  • Never run UPDATE posts SET like_count = like_count + 1 under viral load. That row becomes a lock convoy: every like serializes on the same row lock and throughput collapses.
  • Split the like_count for a hot post into, say, 100 sub-counters across different keys. A like increments a random sub-counter, so load spreads across 100 keys instead of piling onto one.
  • The displayed total is the sum, computed periodically and cached. Readers see a slightly stale count, which is fine: nobody notices 2,041,180 instead of 2,041,195.
flowchart LR L1[Like] --> S1[(Counter shard 0)] L2[Like] --> S2[(Counter shard 42)] L3[Like] --> S3[(Counter shard 99)] S1 --> AGG[Aggregator] S2 --> AGG S3 --> AGG AGG --> DISP[(Displayed count cache)]
Sharded like counter for a viral post. Each like hits a random sub-counter, so the write load spreads across 100 keys, and an aggregator periodically sums them into a single displayed count.

Promote to the hot path only when a post actually goes hot.

  • The vast majority of posts never go viral, so running 100 sub-counters for every post wastes memory for no reason.
  • Cold posts use one counter; hot posts get split on the fly once they cross a like-rate threshold.

6.3 Keeping the feed consistent-enough#

Follows and unfollows race against fan-out.

  • Unfollow someone and their already-pushed posts are still sitting in my feed cache.
  • Scrubbing every unfollow out of every cached feed is expensive, and at my unfollow rate it is not worth the write traffic.
  • So I filter at read time: Feed Service checks each candidate post against the reader's current follow set before returning the page.
  • The follow set is small (a few hundred accounts) and already cached, so the check is a cheap set-membership test. It quietly fixes stale entries from unfollows, blocks, and deletes in one pass.

Deletes work the same way.

  • Flip a tombstone flag on the post row and let the read-time filter drop it.
  • I do not chase it across millions of feed lists: that is the same write-amplification disaster I just spent a deep dive avoiding.
  • Stale cache entries age out naturally when feeds get trimmed past 500.

The key philosophy, stated exactly as I would in the room:

  • Push the easy case eagerly, and let a cheap read-time filter clean up the messy edges.
  • It is far simpler than keeping every cached feed perfectly in sync, and it fits the eventual-consistency model I agreed to up front.

7Bottlenecks and trade-offs#

The fanout queue is the first thing to break.

  • A spike, or a batch of mid-size accounts posting at once, backs up the queue and feeds go stale.
  • Make fan-out fully async and idempotent (replaying a job re-writes the same ids, so a retry is harmless).
  • Autoscale workers on queue lag (age of the oldest unprocessed job), not raw depth, because depth lies during a burst of tiny events.
  • Accept that staleness under load is a feature, not an outage: a feed a few minutes behind beats the whole write path stalling. Alarm on queue depth and p99 fan-out lag so I see pressure before users do.

The follow graph is a scaling trap.

  • Fan-out reads follower lists constantly, so the Graph Store gets hammered during posting storms.
  • Cache follower lists for active authors and shard the graph by follower_id.
  • The hybrid threshold protects the graph directly: I never read the 40-million-entry follower list of a celebrity for fan-out at all. That single rule removes the worst graph read in the system.

Precomputed feeds waste work on inactive users.

  • Fan out to 40,000 followers but only 2,000 open the app today and you did 38,000 writes nobody read.
  • So I only fan out to recently active users (say active in the last 7 to 30 days) and lazily build the feed for someone returning after weeks, using the same on-demand fallback as cold feeds.
  • This cuts fan-out volume by more than half in practice, because the active tail is always smaller than the total follower count.

Ranking is where chronological bites back.

  • A ranked feed needs signals (recency, affinity, predicted engagement) that change per request and cannot be fully precomputed.
  • Keep fan-out producing a candidate set of a few hundred posts, then run a lightweight ranking pass at read time over just those candidates.
  • Rank the small set, not the whole world: scoring 300 candidates per read is affordable, scoring every post a user could see is not.
  • That keeps p99 in budget and gives the ranking team a clean place to plug in a model without touching the feed plumbing.

Strong consistency is the thing I deliberately give up.

  • Your like count is approximate, your feed may be seconds stale, an unfollowed post may flash before the filter catches it.
  • I trade all of that for availability and speed, the correct trade for a social feed where nothing a user sees is load-bearing for money or safety.
  • The one place I do not compromise is media durability: blob storage with cross-region replication, because losing photos is the one unforgivable failure.

Single points of failure.

  • The gateway and load balancer are replicated across availability zones.
  • The caches are replicated, not just sharded, so losing a node degrades rather than drops traffic.
  • The honest risk: a thundering herd if the whole feed cache cold-starts after a region-wide restart, when every read misses and falls through to the on-demand path, overwhelming the Post Store.
  • Mitigate with staggered warming, request coalescing on the fallback path, and an on-demand builder that acts as a release valve rather than a cliff.

Always let the read:write ratio pick the architecture, not habit.

8Summary#

The decisions I would stand behind, with the one-line reason for each:

  • Read-optimized by design. A 50:1 read ratio means I do work at write time (fan-out) so feed reads are a simple cache lookup. This is the decision everything else hangs off.
  • Hybrid fan-out. Push for normal accounts, pull-and-merge for the handful of celebrity accounts above a 50,000-follower threshold. Kills the write-amplification disaster without slowing normal reads.
  • Media never touches app servers. Signed-URL uploads to blob storage, CDN for delivery, metadata-only in the database. This keeps 60 PB a year affordable.
  • Hot keys get sharded and replicated. Sharded like counters for viral writes, replicated hot-post caches for viral reads, promoted only when a post actually goes hot.
  • Eventual consistency with a read-time filter. Push the easy case, clean up unfollows, deletes, and blocks cheaply at read time instead of chasing them across every feed.
  • Durability is the one hard guarantee. Everything else can be stale or approximate; photos cannot be lost.
  • Out of scope by choice: moderation, abuse detection, search, hashtag discovery, stories, DMs. Naming them keeps me honest about what 45 minutes covered.
Follow-ups the interviewer will ask (and your answers)
"A celebrity with 40 million followers posts. Walk me through what happens."
They are above my 50,000 threshold, so they are marked wide and I skip fan-out entirely. Their post is written once to the Post Store. When any of their followers loads a feed, Feed Service pulls that author's recent posts fresh and merges them with the reader's precomputed push feed by score. The pull side is tiny because a normal user follows only a few dozen wide accounts, so the merge stays inside 200 ms.
"A worker retries and pushes the same post id twice. Duplicate in my feed?"
No. The feed is a Redis sorted set scored by post_id, so re-adding an existing id is a no-op. Fan-out is at-least-once and the data structure makes the push idempotent for free, so I do not need a dedup table.
"A viral post gets a million likes a minute. How do you not melt one row?"
I never increment a single like_count row under viral load, since that becomes a lock convoy. Once a post crosses a like-rate threshold I promote it to 100 sharded sub-counters; each like hits a random shard, and an aggregator sums them periodically into a cached displayed count. Readers see a slightly stale total, which is fine for likes. Cold posts stay on a single counter.
"I unfollow someone. Their posts are already in my cached feed. Now what?"
I do not scrub them out, because at my unfollow rate the write traffic is not worth it. Instead Feed Service filters each candidate post against my current follow set at read time. The follow set is small and already cached, so it is a cheap membership test, and the same pass also drops deleted and blocked posts. Stale entries age out when the feed trims past 500.
"The interviewer flips the feed to ranked. Where does ranking plug in?"
Fan-out keeps producing a candidate set of a few hundred post ids. At read time I run a lightweight scoring pass over just those candidates using recency, affinity, and predicted engagement, then hydrate the top page. I rank the small set, not the whole world, so p99 stays in budget. Because the feed stores ids separately from content, the ranking team plugs a model in at the merge step without touching the fan-out plumbing.

What you can now explain in the room

  • Why a 50:1 read ratio pushes the expensive work to write time and keeps reads a cache lookup.
  • Why pure push breaks on celebrities, and how the 50,000-follower hybrid threshold fixes it.
  • How to derive the load numbers: 175,000 reads/second at peak, 60 PB a year of media, and the 4 TB feed cache.
  • Why media uploads use signed URLs and a CDN so big bytes never touch app servers.
  • How a viral post creates read and write hot keys, and how replication and sharded counters defend each.
  • Why unfollows, deletes, and blocks are cleaned up by a cheap read-time filter instead of chasing every cached feed.

For how specific companies push on these trade-offs, see our related company-specific system design guides.

Get a prep plan built around your Design Instagram interview

Tell us the role and your interview date. We will send back a dated, day by day plan. Free.

Thanks. Check your inbox, your Design Instagram prep plan is on the way.