System Design · step by step

Design Instagram

Step 1 / 9
The numbers to beatimmutableimages~95%+from edgelongTTL

In the interview room

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

Functional requirements

What it must do — agree on these before drawing a single box.

  • Post: upload a photo with a caption.
  • Scroll: open the app to a fast feed of posts from people you follow.
  • Serve media: load images quickly for hundreds of millions of viewers.
  • Like & view: count taps on a post, even a viral one.
  • Stories: ephemeral posts that vanish after 24h.

Non-functional requirements

The qualities that shape the whole design — each one names the mechanism that buys it.

Heavy media without bloating the database
Store pixels in an object store and keep only a pointer + caption row in the DB, so each store scales and costs out on what it’s best at.
Images load fast everywhere
A CDN caches immutable images at the edge with a near-infinite TTL, so ~95%+ of bytes are served near the viewer and origin stays nearly idle.
Posting stays snappy
Store the original, then generate thumbnails and variants asynchronously in a worker fleet, so heavy CPU work never blocks the upload path.
Scrolling is a cheap read
Precompute a per-user list of recent post IDs via fan-out on write, so opening the app is one cache lookup, not a live fan-in across everyone you follow.
Millions of likes a minute survive
Emit likes as events and fold them into sharded/approximate counters asynchronously, with an optimistic bump on the tap — no hot-row write storm.
Billions of users and celebrity hotspots
Shard the metadata DB by id and handle extreme accounts specially — pull-based hybrid feeds and hot-key counter sharding — so no single row or shard is the bottleneck.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

Pixels in a blob store, pointer in the DBover the image as a BLOB column

A multi-MB binary in every row bloats the database, wrecks backups and makes queries crawl. Databases are tuned for small structured rows; blob storage holds cheap durable bytes, and a pointer links the two.

A CDN edge cacheover serving every image from origin

One central store adds cross-region latency to every photo and becomes a bandwidth choke point. Immutable images cache at the edge with a very long TTL — the first viewer pulls origin, everyone nearby is served locally.

Resize asynchronously in workersover resizing during upload

Generating every size before responding blocks the user on heavy CPU and can’t scale past the encoder fleet. Store the original and let workers finish the variants a beat later, off the request path.

Fan-out on writeover a live fan-in query per refresh

Merging every followed account on every scroll is the hottest path doing a huge read — it collapses under load. Do the merge once at post time so opening the app is a single cheap cache read.

Async, sharded countersover a synchronous UPDATE per tap

Every tap contending on one post row is a hot-row write storm that turns a viral post into a global lock. Fold like events into sharded counters asynchronously — a sliver of staleness for surviving virality.

What this teaches

Learn system design by building a photo-sharing app like Instagram step by step. An interactive guide covering media upload, blob storage and CDN delivery, image processing, the timeline feed, like/view counters, ephemeral stories, and sharding for scale.

Key takeaways

  • Split media from metadata: pixels in a blob store, a pointer in the DB.
  • A CDN serves immutable images from the edge, so media loads fast.
  • Async image processing makes the many sizes without slowing uploads.
  • A precomputed feed cache turns scrolling into a cheap lookup.
  • Likes/views aggregated asynchronously via events and sharded counters.
  • Stories expire automatically via TTLs — no cleanup job needed.
  • Sharding plus hybrid feeds and hot-key handling absorb celebrity scale.

Concepts covered

  • What is Instagram?
  • Bytes here, rows there
  • Push photos to the edge
  • Process uploads async
  • Precompute the timeline
  • Counters the async way
  • Stories & ephemerality
  • Shard everything

Design Instagram — read the full walkthrough as text

the same steps, decisions & trade-offs, for reading, reference & search

The big idea

What is Instagram?

Upload a photo; billions of people scroll endless photos. It sounds like a feed, but the twist is media: images are big, immutable, and viewed far more than they’re posted — so the bytes, not the rows, dominate the design.

Two big moves: keep heavy media out of the database (store pixels in a blob store + CDN, keep only pointers in the DB), and precompute feeds so scrolling is a cheap lookup. Everything else builds on those.

How to read this: We add one piece at a time, problem then fix, and the diagram grows. Hit Begin.

Step 1 · The skeleton

Bytes here, rows there

A post is two very different things: a big image and a little bit of structured data (caption, author, timestamp). Cram the image into the database and it bloats, slows, and gets expensive fast.

Design decision: A post = a big image + a little caption/author/timestamp. Where do the bytes go?

The call: Pixels in an object store; a pointer + caption row in the DB. — Each store does what it’s best at — the blob store holds cheap durable bytes, the DB holds small queryable rows pointing at them. They scale and cost out independently.

Split them. The API Gateway stores the image in an Object Store and writes a row to the Metadata DB holding the caption and the image’s location — a pointer, not the pixels. Each store does what it’s best at.

Metadata vs blob: Databases are great at small, queryable rows and terrible at giant binaries. Blob stores are the reverse. Keeping a pointer in the DB lets each scale and cost out independently.

Step 2 · Serve images fast

Push photos to the edge

Serving every image from a central object store adds cross-region latency to every photo and hammers one place. In a media app, slow images are a slow app.

Design decision: Hundreds of millions of viewers need the same immutable images. How do you serve them?

The call: A CDN edge caches images; origin is pulled once on a miss. — Immutable images cache at the edge with near-infinite TTL — the first viewer pulls from origin, everyone nearby is served from the edge. ~95%+ of bytes never touch origin.

Put a CDN in front of the object store. The first request for an image pulls it from the store; the edge then serves everyone nearby. Since an uploaded image never changes, it caches at the edge with a very long TTL.

Immutable media caches beautifully: No edits means no invalidation — the hardest cache problem simply vanishes. The CDN absorbs the overwhelming majority of image bandwidth, leaving origin nearly idle.

Step 3 · One photo, many sizes

Process uploads async

A phone shouldn’t download a 12-megapixel original to show a 150px thumbnail. But generating every size during upload would make posting slow and block the user on heavy CPU work.

Design decision: A 12MP original is too heavy for a 150px thumbnail. When do you make the other sizes?

The call: Store the original, then resize asynchronously in workers. — The post appears instantly; thumbnails and variants finish a beat later, off the request path. Heavy CPU work scales on its own fleet.

After storing the original, hand off to Image Processing workers that generate thumbnails and display variants asynchronously and write them back to the blob store. The post appears immediately; its optimized sizes finish a beat later.

Do heavy work off the request: Resizing, transcoding and moderation don’t belong on the upload path. Offloading them keeps posting snappy and lets the processing fleet scale on its own.

Step 4 · The feed

Precompute the timeline

Building a feed by querying every account a user follows, every time they refresh, is far too slow — reads are the hottest path in the whole app.

Design decision: Reads dominate. How do you build a user’s feed on refresh?

The call: Precompute a per-user list of recent post IDs; fan out on write. — Do the expensive merge once when someone posts, not on every scroll. Opening the app becomes a single cheap cache read.

Maintain a Feed Cache: a precomputed list of recent post IDs per user, updated by fan-out when people they follow post. Opening the app reads that list and hydrates image URLs from the CDN — fast and cheap.

Fan-out on write: Do the expensive merge once at post time, not on every scroll. (For mega-popular accounts, pull their posts at read time instead — the hybrid model, just like a news feed.)

Step 5 · Likes at scale

Counters the async way

A viral post can take millions of likes in minutes. Doing a synchronous UPDATE count = count + 1 per tap creates brutal write contention on one row and can stall everything.

Design decision: A viral post takes millions of likes in minutes. How do you count them?

The call: Emit like events to a stream; fold into sharded counters async. — The tap returns instantly (optimistic bump); the authoritative total is aggregated from the stream into sharded counters that spread the hot key. A sliver of staleness buys surviving virality.

Emit likes as events onto a stream and fold them into Counters asynchronously (sharded/approximate counters for the hottest posts). The user sees an instant optimistic bump; the authoritative total catches up moments later.

Decouple the count from the tap: Exact, synchronous counts don’t survive virality. Aggregating asynchronously — and sharding hot counters — trades a sliver of staleness for surviving a stampede of taps.

Step 6 · Here then gone

Stories & ephemerality

Stories vanish after 24 hours. Sweeping the database for expired posts and pulling them from every feed would be a constant, expensive chore.

Design decision: Stories vanish after 24h. How do you make old ones disappear?

The call: Give each story a TTL so storage and cache auto-expire it. — Lifetime becomes a property of the data — the store reclaims it for free and feeds just stop showing it. The same trick powers sessions and OTPs.

Give each story a TTL so it auto-expires in storage and the cache; feeds simply stop showing it once it’s gone. No cleanup job — expiry is a property of the data, and the system reclaims it for free.

Let TTLs do the work: Encoding lifetime into the data turns “delete old stuff” from a scheduled sweep into automatic behavior. The same trick powers sessions, OTPs and short-lived caches.

Step 7 · Scale & the sharp edges

Shard everything

Billions of users and posts overflow any single database, and celebrity accounts create hotspots in both the feed fan-out and the counters.

Design decision: Billions of users overflow one DB, and celebrities create hotspots. What’s the plan?

The call: Shard metadata by id; hybrid feed + hot-key counters for celebrities. — Hash-sharding spreads the median load across many shards; the handful of extreme accounts get bespoke handling (pull not push, split counters) so no single shard or row is the bottleneck.

Shard the Metadata DB (by user/post id) behind the gateway and load-balance the stateless services. Handle celebrities with the hybrid feed (pull, not push) and hot-key counter sharding, so no single row or shard becomes the bottleneck.

Uniform sharding, special-cased hotspots: Hash-sharding spreads the average load; the few extreme accounts need bespoke handling. Designing for both the median and the outlier is what makes it survive real traffic.

You did it

You just designed Instagram.

  • Split media from metadata: pixels in a blob store, a pointer in the DB.
  • A CDN serves immutable images from the edge, so media loads fast.
  • Async image processing makes the many sizes without slowing uploads.
  • A precomputed feed cache turns scrolling into a cheap lookup.
  • Likes/views aggregated asynchronously via events and sharded counters.
  • Stories expire automatically via TTLs — no cleanup job needed.
  • Sharding plus hybrid feeds and hot-key handling absorb celebrity scale.
built to be posted, not memorized — make the calls, kill the CDN, run the gauntlet.
Finished this one? 0 / 62 System Designs done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.