Design Instagram
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
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
3 API
| Endpoint | What it does |
|---|---|
POST/v1/media/uploads | Start 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/posts | Create a post referencing upload_ids, caption, location; returns post_id with status 'processing' |
GET/v1/feed?cursor= | Ranked home feed page |
GET/v1/stories/tray | List of followed users with active stories, ordered, with seen state |
POST/v1/posts/{id}/likes | Like a post (idempotent per user) |
GET/v1/users/{id}/posts?cursor= | Profile grid |
4 Data model
postsSharded Postgres (by author_id) + Memcachedpost_id (PK, time-sortable 64-bit)author_idcaptionmedia: [{media_idtypewidthheight}]locationstatuscreated_atInstagram famously ran sharded Postgres with IDs that encode the shard; profile grid is a single-shard range scan.
mediaS3-like object storage + metadata in Postgresmedia_idowner_idoriginal_keyrenditions: {1503206401080video_hls}blurhashstatusObjects 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 / Cassandrauser_id → list of post_ids with timestamps (capped ~500)Same hybrid fan-out as a news feed; only IDs stored.
storiesCassandra with TTLauthor_id (partition)story_id (clustering)media_idcreated_atexpires_at; TTL 24 hPer-author partition; reads fetch active stories of followed users. TTL removes data automatically.
likesCassandra + counter servicelikes: (post_id, user_id) PK; like_count in Redisflushed to DBMembership table answers 'did I like it'; count is a separate, batched counter.
5 Architecture
Hover a box to highlight its connections.
6 Request flows
Upload and publish a photo post
- 1App compresses the photo on device (e.g. to ~1080 px, ~500 KB) and calls POST /media/uploads
- 2Upload Service returns a pre-signed resumable URL; the app PUTs bytes directly to object storage, resuming after network drops
- 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
- 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
- 5Workers mark the media ready and set post status=published, then emit PostPublished
- 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
- 1Stories Service gets the user's followee list (cached) and, for each, checks a small Redis key 'has active story, latest ts'
- 2It builds the tray: followees with active stories, ordered by closeness/affinity and unseen first
- 3For the first few trays it prefetches story media URLs so the app can preload the first frames from the CDN
- 4Viewing a story writes (story_id, viewer_id) to a viewers table and updates the user's seen state
- 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
Lessons this design uses
Practice questions
- A user updates their profile photo, the page refreshes, and the old photo appears. A second refresh shows the new one. What is happening and how do you fix it?
- Users of a photo-sharing app upload 10 million photos a day. Estimate the storage you need for five years, and say what you would store where.
- You have 45 minutes to design Instagram. How do you decide what is out of scope, and how do you say it to the interviewer without sounding like you are avoiding work?
- A celebrity posts a photo on your platform and within a minute it gets 2 million requests per second. What breaks between the CDN and object storage, and how do you design for it?
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.