Design problems
Social, feeds & contentMediumSDE-2 35 min

Design Instagram

Asked at
GoogleMetaMyntraShareChat

The interviewer asks

“Design Instagram: users upload photos and short videos, follow other users, scroll a home feed, view and post 24-hour Stories, and like and comment on posts.”

What a strong answer looks like

A strong answer is media-first. Uploads go directly from the phone to object storage via pre-signed URLs (resumable for video); an async pipeline makes several image sizes and video renditions, and only then is the post published. Everything is served through a CDN, which carries more than 95% of the bytes. Metadata (users, posts, follows, likes) sits in sharded Postgres/Cassandra with caches; the feed reuses the hybrid fan-out pattern (push IDs for normal users, pull for celebrities) plus ranking. Stories are a separate, time-limited system: a per-user story tray built at read time with TTL storage. Deep dives: the upload and processing pipeline, CDN and image formats for slow networks, Stories, and like counters on viral posts. Numbers: 500M DAU, ~100M uploads/day, ~1 PB/day of new media including renditions.

Concepts to mention

Pre-signed direct-to-storage uploadsResumable uploadsAsync media processing pipelineImage renditions and modern formatsCDN with immutable URLsAdaptive bitrate videoHybrid feed fan-outStories with TTL and read-time traySharded counters for hot likesSigned URLs for private content

1 Requirements

Functional

  • Upload a photo or short video (up to 60 s for feed posts) with caption and tags
  • Follow and unfollow users; public and private accounts
  • Home feed of posts from followed accounts, ranked
  • Stories: photo/video that disappears after 24 hours, with a viewers list
  • Like and comment on posts; show counts
  • User profile grid of their posts

Non-functional

  • Scale: 2B MAU, 500M DAU, ~100M new posts/day and ~500M stories/day
  • Feed load p99 < 300 ms server side; first image visible < 1 s on 4G
  • Media durability: no acknowledged upload is ever lost
  • Availability 99.95%; reads prioritised over writes
  • Upload completes on flaky mobile networks (resumable)
  • Post visible to followers within ~10 s after processing

Out of scope (say it out loud)

  • Direct messages
  • Reels recommendation (separate short-video design)
  • Ads and Shopping

2 Estimates

Upload rate~7k/s average (peak ~20k/s)(100M posts + 500M stories) ÷ 86,400 ≈ 6,900/s
Raw media ingest~1.2 PB/dayAssume average 2 MB per item (photos ~1-3 MB, videos 5-20 MB): 600M × 2 MB = 1.2 PB/day
Stored with renditions~1.5 PB/dayPhotos: 4-5 sizes add ~40%; originals of stories can be deleted after expiry (keep for archive if user opts in)
Feed reads~100k/s avg, ~300k/s peak500M DAU × ~17 feed loads/day ÷ 86,400 ≈ 100k/s
Media egress~5-20 Tbps peak via CDN300k feed loads/s × ~10 images × ~150 KB ≈ 450 GB/s ≈ 3.6 Tbps for photos; video and stories add several times more; almost all served by CDN
Likes~50k/s average~4B likes/day ÷ 86,400 ≈ 46k/s, with very hot keys on viral posts

3 API

EndpointWhat it does
POST/v1/media/uploadsStart an upload: type, size, checksum; returns upload_id and pre-signed (resumable) URL
PUT{presigned_url}Client uploads bytes (chunked/resumable) straight to object storage
POST/v1/postsCreate a post referencing upload_ids, caption, location; returns post_id with status 'processing'
GET/v1/feed?cursor=Ranked home feed page
GET/v1/stories/trayList of followed users with active stories, ordered, with seen state
POST/v1/posts/{id}/likesLike a post (idempotent per user)
GET/v1/users/{id}/posts?cursor=Profile grid

4 Data model

postsSharded Postgres (by author_id) + Memcached
post_id (PK, time-sortable 64-bit)author_idcaptionmedia: [{media_idtypewidthheight}]locationstatuscreated_at

Instagram famously ran sharded Postgres with IDs that encode the shard; profile grid is a single-shard range scan.

mediaS3-like object storage + metadata in Postgres
media_idowner_idoriginal_keyrenditions: {1503206401080video_hls}blurhashstatus

Objects are immutable; new edit = new media_id. CDN URLs are signed and versioned.

followsSharded Postgres / graph store + cache
(follower_id, followee_id, status: active|requested, created_at)

Private accounts need approval; both directions stored.

feed_inboxRedis / Cassandra
user_id → list of post_ids with timestamps (capped ~500)

Same hybrid fan-out as a news feed; only IDs stored.

storiesCassandra with TTL
author_id (partition)story_id (clustering)media_idcreated_atexpires_at; TTL 24 h

Per-author partition; reads fetch active stories of followed users. TTL removes data automatically.

likesCassandra + counter service
likes: (post_id, user_id) PK; like_count in Redisflushed to DB

Membership table answers 'did I like it'; count is a separate, batched counter.

5 Architecture

CLIENTSEDGESERVICESASYNCDATAAPI callsupload via signed URLfetch mediaon missMediaUploadedresize, transcoderenditionspost rowsPostPublishedfeed inboxesMobile AppCDNAPI GatewayUpload/Post ServiceStories ServiceLikes/CommentsServiceFeed + RankingKafkaMedia WorkersFan-out WorkersPostgres (sharded)Object StorageCassandra (stories,likes)Redis / Memcached
ClientEdge / gatewayService / workerQueue / streamCacheDatabase / storageAI / modelThird partyrequestasync (event / queue)

Hover a box to highlight its connections.

6 Request flows

Upload and publish a photo post

  1. 1App compresses the photo on device (e.g. to ~1080 px, ~500 KB) and calls POST /media/uploads
  2. 2Upload Service returns a pre-signed resumable URL; the app PUTs bytes directly to object storage, resuming after network drops
  3. 3App calls POST /posts with the upload_id and caption; the post row is saved with status=processing and the author sees it locally at once
  4. 4A MediaUploaded event triggers media workers: verify the file, strip EXIF location if needed, create 150/320/640/1080 px versions in WebP/AVIF and JPEG, compute a blurhash placeholder, run safety classifiers
  5. 5Workers mark the media ready and set post status=published, then emit PostPublished
  6. 6Fan-out workers push post_id into followers' feed inboxes (pull for celebrities); followers fetch images from the CDN on view

Open the Stories tray

  1. 1Stories Service gets the user's followee list (cached) and, for each, checks a small Redis key 'has active story, latest ts'
  2. 2It builds the tray: followees with active stories, ordered by closeness/affinity and unseen first
  3. 3For the first few trays it prefetches story media URLs so the app can preload the first frames from the CDN
  4. 4Viewing a story writes (story_id, viewer_id) to a viewers table and updates the user's seen state
  5. 5After 24 h, Cassandra TTL removes the story rows; media is moved to the private archive or deleted by a lifecycle job

7 Deep dives

Media upload and processing pipeline

Never route large uploads through API servers: they would hold threads and bandwidth for slow mobile clients. The app gets a pre-signed URL and uploads straight to object storage, using resumable or chunked upload so a dropped 4G connection resumes rather than restarts. The app also compresses before upload, which on Indian mobile data saves users money and cuts our ingest. Processing is async and event-driven: an upload event goes to Kafka, and a worker pool creates renditions. Images get 4-5 sizes in modern formats (WebP or AVIF, with JPEG fallback), and a tiny blurhash so the feed shows a coloured placeholder instantly. Videos are transcoded into an adaptive ladder (for example 240p to 1080p) packaged for HLS/DASH, which is CPU heavy and runs on autoscaled or GPU instances. Each step is idempotent and keyed by media_id so retries are safe. Content-safety models (nudity, violence) run in the same pipeline; a post is published only after the required steps pass. Total time target: a few seconds for photos, under a minute for short videos.

Serving media fast on slow networks

Media is more than 95% of bytes, so the CDN is the main serving system. URLs are immutable (contain media_id and size), so they can be cached for a year with no purge needed. The client asks for the size it needs based on screen width and network quality: a 320 px image on 2G, 1080 px on Wi-Fi. This alone can cut bytes by 3-5x on low-end phones. The feed API returns image dimensions and blurhash so layout does not jump. The app prefetches the next few posts' images while the user scrolls and the first second of videos, but backs off on metered or slow connections. Videos use adaptive bitrate so playback starts with a low rendition and climbs. For private accounts, CDN URLs are signed with short expiry so a leaked URL stops working. Origin protection uses a shield tier so a viral post does not cause a miss storm on storage.

Stories: time-limited content

Stories behave differently from posts: they live 24 hours, are viewed in a tray at the top of the app, and almost every active user checks them several times a day. Fan-out on write is wasteful because each story expires quickly and is posted far more often than feed posts. So the tray is built at read time: for each followee, check a tiny 'active story' marker (author_id → latest story timestamp) in Redis. With ~200-500 followees this is one batched multi-get. The story contents are stored per author in Cassandra with a 24-hour TTL, so expiry needs no cleanup job. Ordering the tray uses an affinity score (how much you interact with that person), with unseen stories first. The viewers list is a write per view; for big creators this is millions of rows, so it is written to Cassandra with the story as partition key and counted with an approximate counter. Highlights (stories kept on profile) copy the reference to a permanent table so the TTL does not delete them.

Likes on viral posts

A celebrity post can get a million likes in minutes, and every like increments the same counter: a classic hot key. Writing each like as an UPDATE on one database row would serialise on a row lock. Instead separate two things. Membership ('did user U like post P') goes to a likes table keyed by (post_id, user_id), which makes likes idempotent and spreads writes over Cassandra partitions (split hot posts into sub-partitions if needed). The count is approximate in real time: increments are buffered in memory or in sharded Redis counters (post:123:c0 … c15) and summed every few seconds; the total is flushed to the posts store periodically. Users accept that the displayed count lags slightly. Like events also flow to Kafka for notifications ('X liked your post', batched) and for ranking features.

8 Trade-offs

Process on device vs on server

Client-side compression and resizing save upload bandwidth and server cost, but quality varies across phones and you must still re-encode on the server for consistency and safety. Do both: light client compression, authoritative server renditions.

Postgres vs Cassandra

Sharded Postgres works well for posts, users and follows (relational, transactional, well understood); Cassandra suits very high write rates with simple access patterns and TTL (likes, story views, stories). Using both is reasonable when each is matched to an access pattern.

Pre-generate renditions vs resize on the fly

Pre-generating fixed sizes costs storage but makes serving trivial. On-the-fly resizing at the edge saves storage and supports any size, but adds latency on first request and CPU at the edge. Pre-generate the common sizes; resize on the fly for rare ones.

9 Bottlenecks

Transcoding backlog during peaks

New Year's Eve and Diwali night bring 5-10x story uploads. Prioritise photos and stories (short-lived, users expect them now) over long videos, autoscale workers on queue depth, and publish a low rendition first and add higher ones later.

Storage cost

Petabytes per day is expensive. Move older, rarely viewed media to infrequent-access tiers, delete intermediate renditions that can be regenerated, and deduplicate re-shared media by content hash.

10 Failure handling

Media worker fails mid-processing

The Kafka message is not committed, so another worker retries; outputs are written to deterministic keys (media_id + size) so retries overwrite safely. After N failures the post moves to a dead-letter queue and the user is told to retry.

CDN or region issue

Multi-CDN with DNS steering moves traffic to another provider; the app retries image loads with a fallback host. Object storage is replicated across zones.

Post published but fan-out delayed

The post is in the author's profile and visible via pull; followers see it in feed once fan-out catches up. No data loss because Kafka retains the event.

11 What impresses

  • Keeping media bytes away from API servers entirely
  • Choosing image size by network quality, with blurhash placeholders
  • Building the Stories tray at read time instead of fanning out
  • Separating like membership from the like counter
  • Idempotent processing steps keyed by media_id
  • Thinking about data cost for Indian users on prepaid plans

12 Mistakes

  • Uploading images through the application servers
  • Storing images in the database
  • Serving original full-size images to every client
  • Treating stories exactly like posts with fan-out and manual deletion
  • Incrementing a like count row in SQL for every like
  • Forgetting the CDN or content moderation

13 Follow-ups

Q1How would you add Reels recommendations from accounts users do not follow?
Q2How do you handle a private account switching to public, or a block?
Q3How would you implement hashtag and location pages?
Q4How do you delete all media for a user who deletes their account?
Q5How would you detect re-uploaded copyrighted or banned images?
Q6How would you make the app work well on 2G in rural India?

Saved on this device. Create a free account to keep it on every device.

Practise this with Sam

A real mock interview, out loud, with an honest scorecard. Free to start.

Start free