The 100 problems, solved / Social and feeds / 018 · Design Instagram

Design Instagram

Photo posts, follows, a home feed and explore, with uploads that never block the app.

Concepts this design leans on

01The design, in the six-step framework

1. Requirements

Functional

  • Post photos and short videos with captions; follow people; a home feed of posts from followed accounts; an explore tab of recommended posts.
  • Likes and comments; a profile grid of a user's own posts.
  • Out of scope: stories (see stories), direct messages, the recommendation model's internals.

Non-functional

  • 1 billion monthly users, 500 million daily; 100 million posts a day; average 2 MB per photo, 20 MB per video.
  • Upload returns in under 2 s on a mobile network; the feed loads in under 200 ms; images appear in under 300 ms worldwide.
  • Media is never lost; a post is visible to the author immediately and to followers within seconds.
  • Read-heavy: 5 billion feed loads a day; each shows 20 posts.
Back of the envelope

Media ingest: 108 posts/day × 3 MB average ≈ 300 TB/day ≈ 3.5 GB/s; over a year 110 PB of originals plus renditions.

Image serving: 5 × 109 feed loads × 20 posts × 200 KB rendition = 2 × 1016 B/day ≈ 230 GB/s, almost all from the CDN; origin sees a few percent.

Post metadata: 108 × 365 × 500 bytes ≈ 18 TB/year, sharded.

2. Core entities

  • User: id, handle, profile, counts.
  • Post: id (time-ordered), author, caption, media ids, created-at, like and comment counts.
  • Media: id, type, original blob, renditions (sizes and formats), processing status.
  • Follow: follower, followee.
  • Feed inbox: user → recent post ids; Like: (user, post); Comment: post, author, text, time.

3. API

  • POST /media/uploads → signed upload URL and media id; the client uploads bytes straight to storage; POST /media/{id}/complete when done.
  • POST /posts body: caption, media ids → post id; the post is created with the client's locally generated thumbnail until renditions are ready.
  • GET /feed?cursor=, GET /explore?cursor= → pages of hydrated posts with rendition URLs chosen for the device.
  • POST /posts/{id}/likes, POST /posts/{id}/comments, GET /users/{id}/posts?cursor=.

4. High-level design

ClientsEdgeServicesDataupload (signed URL)imagespost, media eventsrenditionsfeed readPhone appCDN (renditions)Load balancerPost + feed APIMedia processing(resize, transcode)Fan-out workersObject storagePost DB(sharded)Feed inboxes(in memory)Events

A post from camera roll to a follower's feed:

  1. The app asks for an upload URL and streams the original directly to object storage, resumable in chunks; the API never carries the bytes.
  2. POST /posts creates the post row immediately. The app already has a small preview, so the author sees the post at once.
  3. Media processing produces renditions (several widths, modern formats, a video transcode) and marks the media ready; the post's rendition URLs change from "original" to the small files.
  4. Fan-out pushes the post id into followers' inboxes as in the news feed design; the feed read merges inbox, celebrity pulls and ranking, and hydrates from the post store. Images load from the CDN.

5. Deep dives

Uploads that never block the app

The 2 s target on a mobile network is impossible if the upload waits for processing, so the design makes the post visible before the media is ready. Three pieces: a direct-to-storage upload through a signed URL (no API server in the byte path, resumable by chunk), a post row that references media by id with a status, and a client-generated preview the author sees immediately. Followers who load the feed in the seconds before renditions exist get either the original (large, fine for a few) or are served the post only after processing, which is the usual product choice. Processing is an asynchronous pipeline consuming the media-created event; a video that fails to transcode leaves the post in a "processing" state the author can see, never a broken feed.

Serving 230 GB/s of images

Every rendition is immutable and named by media id, size and format, so the CDN caches it for a year and a popular post is served from edge memory. The client asks for the width it will draw and sends an Accept header, so a modern phone gets a 480-pixel AVIF of about 60 KB rather than a 2 MB original. Prefetch the next screen of thumbnails while the user scrolls. Origin capacity is for cache misses only, so a long-tail post viewed once still reads from object storage, and a regional object-storage replica keeps that miss within the continent. The number to say: at a 95 percent hit rate the origin serves about 12 GB/s, which a few hundred storage nodes handle.

The home feed versus explore: two different read paths

The home feed is the inbox-plus-celebrity-pull design with chronological or lightly ranked order; it is bounded by who you follow. Explore is unbounded: a ranking pipeline picks candidates from several sources (posts liked by accounts similar to you, trending in your region, topics you engage with), scores a few thousand with a model, and pages through them with a cursor over a precomputed per-user candidate list refreshed every few minutes by a background job. Keeping explore off the home-feed path matters: an explore outage should degrade one tab, not the app. Both tabs hydrate the same way, through batched reads and a post cache.

Likes and comments on a viral post

A viral post receives thousands of likes a second. Writing each like as a row is fine (sharded by post id, append-only), but incrementing a single counter row at that rate is a hot spot. Keep the like record for idempotency ("did I like this?") and compute the displayed count from a sharded counter aggregated every few seconds. Comments are a list per post with cursor pagination, newest or most relevant first, with a per-post cache of the first page; a comment on a viral post invalidates only that post's first-page cache entry. Moderation (a classifier on the comment text) runs in line for obvious abuse and asynchronously for the rest.

The profile grid and private accounts

A profile page lists one author's posts newest first, which is a range read on the post store if posts are sharded by author with a time-ordered id as the sort key; but the feed hydrates by post id, which prefers sharding by post id. The usual answer is both: the post store is keyed by post id, and a small secondary table per author holds (author, post id) in time order, written in the same transaction as the post. Private accounts change fan-out and serving: a post from a private account is pushed only to approved followers, the follow-request flow is a pending edge that the fan-out workers ignore until approved, and every hydration checks that the viewer may see the author's posts, with the check cached per (viewer, author) for a short time so it does not add a lookup per post on every feed load.

6. What a strong answer sounds like

  • Lead with the two big numbers: 3.5 GB/s in and 230 GB/s out, and the design that follows (direct uploads, immutable renditions, CDN).
  • Make the post visible before processing completes and explain how the states are reconciled.
  • Reuse the hybrid fan-out feed and point out explore is a different read path with its own failure domain.
  • Handle the viral post's counters and comments explicitly.
  • Expect follow-ups on video (segmented transcoding and adaptive playback), on deleting a post from CDN and inboxes, and on privacy (private accounts and follower approval changing what fan-out may push).
  • Common mistake: proxying uploads through the API tier, or storing counters as a single row per post.

02Where this question was reported