Design a short-video feed (ShareChat Moj / Reels)
The interviewer asks
“Design a short-video app like Moj or Instagram Reels. Users upload 15-60 second videos and scroll an endless, personalised 'For You' feed that must start playing instantly, even on a 4G phone in a Tier-2 town.”
What a strong answer looks like
A strong answer splits the problem into two very different paths. The upload path is an async media pipeline: resumable upload to object storage, transcoding into an HLS bitrate ladder, moderation, and push to a CDN. The watch path is a recommendation problem: candidate generation from many sources, a ranking model scoring a few hundred candidates, and a feed service that hands the client small pages of video IDs plus CDN URLs. The candidate then shows how instant start is achieved (prefetch the next 2-3 videos, small first segment, CDN edge hit rate above 95%), how watch-time signals flow back through Kafka into features within minutes, and how cost is controlled, because egress bandwidth, not compute, is the biggest bill.
Concepts to mention
1 Requirements
Functional
- Upload a short video (up to 60 s, up to 100 MB) with caption, hashtags, audio track
- Personalised infinite 'For You' feed, plus a 'Following' feed
- Video starts playing within a fraction of a second of swiping
- Like, share, comment, follow, and 'not interested' actions
- Content moderation before a video is widely distributed
- Creator sees view counts and basic analytics
Non-functional
- 150M daily active users, ~30 min average session, peaks at 9-11 pm
- Time to first frame p95 under 500 ms on 4G; rebuffer ratio under 1%
- Feed API p99 under 200 ms
- New uploads playable within 2 minutes; viral videos must not overload origin
- 99.95% availability for the watch path; upload path can degrade
- Work on low-end Android phones and patchy networks (adaptive bitrate)
Out of scope (say it out loud)
- Live streaming
- Video editing tools and filters on device
- Ads auction and creator monetisation
- Direct messaging
2 Estimates
3 API
| Endpoint | What it does |
|---|---|
POST/v1/uploads | Create an upload session; returns upload_id and pre-signed multipart URLs for object storage |
POST/v1/uploads/{upload_id}/complete | Finish upload with caption, hashtags, audio_id; starts the processing pipeline |
GET/v1/feed/for-you?cursor=&limit=10 | Next page of ranked videos: id, CDN manifest URL, thumbnail, author, counts |
GET/v1/feed/following?cursor= | Recent videos from followed creators |
POST/v1/events (batched) | Client sends batched watch events: video_id, watch_ms, completed, skipped, liked |
POST/v1/videos/{id}/like | Like or unlike (idempotent per user and video) |
GET/v1/videos/{id} | Video metadata for deep links and shares |
4 Data model
videosCassandra (or DynamoDB)video_id (PK, Snowflake ID)author_idcaptionhashtagsaudio_idstatus (processing|live|blocked)renditionsduration_mscreated_atSimple key lookups at huge read volume; status gates distribution. A secondary table videos_by_author (author_id, created_at DESC) serves profile pages and the Following feed.
video_filesS3 / GCS + CDNraw/{video_id}hls/{video_id}/{rendition}/seg_N.tsmaster.m3u8thumb.jpgImmutable objects, so CDN can cache forever; versioned paths avoid invalidation.
countersRedis + Cassandra countersvideo_idviewslikessharescommentsViews aggregated in a stream job and flushed in batches; never one DB write per view.
user_features / video_featuresFeature store (Redis/Aerospike online, Hive/Iceberg offline)user embeddingrecent topicslanguage; video embeddingcompletion ratefreshnessOnline store gives under 10 ms lookups for ranking; offline copy is used for model training.
seen_setRedis (Bloom filter per user)user_id -> bloom of video_ids seen in last 7 daysRemoves already-watched videos from candidates cheaply; small false-positive rate is fine.
followsCassandrafollower_idfollowee_idcreated_atPartition by follower for the Following feed; reverse table for creator follower counts.
5 Architecture
Hover a box to highlight its connections.
6 Request flows
Upload a video
- 1App calls POST /uploads; upload service returns pre-signed multipart URLs (5 MB parts) so the bytes go straight to object storage, not through our servers
- 2App uploads parts with retry; on a dropped connection it resumes from the last part
- 3App calls /complete with caption and hashtags; upload service writes videos row with status=processing and publishes video.uploaded to Kafka
- 4Transcode workers pull the job, produce an HLS ladder (240p, 360p, 480p, 720p, 1080p) with 2 s segments, a thumbnail and a small first segment for fast start
- 5Moderation runs ML classifiers (nudity, violence, hate speech in 15+ Indian languages via ASR + OCR); confident-bad is blocked, uncertain goes to a human queue
- 6Status becomes live; an embedding is computed and inserted into the ANN index; the video enters a small 'cold start' test pool of a few hundred viewers
Scroll the For You feed
- 1App asks for the next page when 3 videos are left in its local queue
- 2Feed service collects ~1,000-2,000 candidates from several sources: ANN neighbours of the user embedding, trending in user's language and state, followed creators, fresh videos in test pools
- 3Filter out seen videos (Bloom filter), blocked authors, and wrong language
- 4Light ranker (small model) cuts to ~300; heavy ranker (deep model on user and video features) predicts watch time, completion, like and share probability
- 5Re-rank for diversity (no two videos from the same creator or audio in a row) and business rules; return 10 IDs with manifest URLs
- 6App prefetches the first segment of the next 2-3 videos at a low bitrate; swiping plays from local buffer, the player then switches bitrate based on bandwidth
Learn from a view
- 1App batches watch events (watch_ms, skip, loop, like) every few seconds and on app background
- 2Gateway writes them to Kafka, partitioned by user_id
- 3Flink jobs update real-time features: user's recent topics, video completion rate, trending scores per region
- 4Updated features land in the online feature store within 1-2 minutes, so the next feed page already reflects what the user just skipped
- 5Events are also stored in the data lake for daily model training
7 Deep dives
Instant start: how the first frame shows in under 500 ms
Instant start is a client-plus-CDN problem, not a server one. First, the feed response already contains the CDN manifest URL, so there is no extra API call when the user swipes. Second, the app keeps a local queue and prefetches the first 1-2 segments of the next 2-3 videos at a modest bitrate (360p or 480p). When the user swipes, playback starts from local storage in tens of milliseconds. Third, the transcoder makes the first segment short (about 1 s) and starts each segment with a keyframe so the player can decode immediately. Fourth, the player starts at a conservative bitrate and steps up with ABR (adaptive bitrate) once it measures bandwidth, which matters on Jio and Airtel 4G in smaller towns. The cost of prefetching is wasted bandwidth when users skip, so limit it: prefetch only the first segment, cancel prefetches the user scrolls past, and use network type (Wi-Fi vs cellular) and data-saver mode. On the CDN side, segments are immutable and cached with long TTLs, so hit rate stays above 95%. Measure time-to-first-frame and rebuffer ratio from the client, broken down by network and region; these are the SLOs that matter, not server latency.
Recommendation: two-stage retrieval and ranking
You cannot score 100M videos per request, so the feed uses a funnel. Candidate generation pulls ~1,000-2,000 videos from cheap sources: approximate nearest neighbours of the user's embedding in a vector index (FAISS, ScaNN or Milvus), trending videos for the user's language and region, videos from followed creators, and collaborative signals ('people who finished X also finished Y'). A light model trims this to ~300, then a heavier model scores each candidate using user features, video features and cross features. The objective is a weighted mix of predicted watch time, completion, likes, shares and negative signals like fast skips and 'not interested'. Final re-ranking adds diversity and freshness rules. Latency budget: ~30 ms retrieval, ~60 ms ranking on GPUs or optimised CPUs, ~20 ms feature fetch, well inside 200 ms. Precompute where possible: you can build the next page in the background while the user watches the current one. For a new user with no history, use language picked at signup, location, device and the most popular videos, then learn fast from the first 20 swipes because short videos give very dense feedback.
Cold start for new videos and the viral curve
A new video has no engagement data, so the system cannot rank it well. The standard answer is staged exposure. Every live video first goes to a small test pool, for example 300-500 users who like that topic and language. If completion rate and like rate beat the baseline for its category, it is promoted to a bigger pool (5k, then 50k, then open distribution). If it fails, it stops. This gives every creator a fair chance and caps the damage from low-quality content. Moderation must sit before wide distribution: ML first, and humans before a video crosses a reach threshold, because the cost of a harmful video going viral is much higher than a delay. Viral videos also cause a different technical problem: hot keys. A video getting 50k views per second means its counter and metadata rows become hot. Cache metadata in the feed service and CDN, and aggregate counters in Flink with per-minute flushes instead of incrementing one row per view.
Cost: bandwidth is the bill
At roughly 25B views a day, egress dominates cost. Levers a senior candidate should mention: serve most views at 480p because phone screens and networks rarely need 1080p; use efficient codecs (H.264 for compatibility, H.265 or AV1 for devices that support hardware decoding, which can cut bytes by 30-50%); keep CDN hit rate high with immutable, versioned URLs; use multiple CDNs with price-based routing plus ISP-embedded caches (Indian ISPs host caches for large players); and limit wasteful prefetching. Transcoding is the second cost: transcode lazily for higher rungs, for example produce 720p and 1080p only once a video passes the first test pool, since most uploads never get many views. Delete raw uploads after a retention window. Storage tiers matter too: move videos with no views in 90 days to cheaper cold storage, and accept a slower first byte if one is ever requested.
8 Trade-offs
HLS segments on a CDN vs progressive MP4
Progressive MP4 is simpler and one file per rendition, but it cannot switch bitrate mid-play. HLS (or DASH) with 2 s segments supports adaptive bitrate and fast start, which matters on Indian mobile networks. The cost is more objects and a manifest fetch; we hide the manifest fetch by prefetching. For very short clips some apps use MP4 at a fixed low bitrate, which is a valid choice if you explain it.
Pull-based ranking at request time vs precomputed feeds
A Twitter-style precomputed timeline does not fit: the For You feed is not from people you follow and should react to your last few swipes. We rank at request time but build the next page ahead in the background, getting most of the latency benefit. The Following feed can use fan-out on read from videos_by_author because follow counts per user are modest.
Fresh features vs simplicity
Daily batch features are simple and cheap, but the feed would ignore what you did in the last hour. Streaming features through Kafka and Flink add operational cost, but short-video engagement depends heavily on session intent. A common middle path: daily-trained model weights, real-time session features.
Exact view counts vs approximate
Showing '1.2M views' does not need exact real-time counts. Aggregate in the stream job and flush every few seconds. Creator payouts or ad billing, if added later, would use a separate, deduplicated batch pipeline.
9 Bottlenecks
Evening peak and cricket-match spikes
Traffic triples between 9 and 11 pm and swings with events. Feed and ranking services are stateless and autoscale on CPU/GPU utilisation, but GPU capacity is slow to add, so keep headroom and a fallback: if the heavy ranker is overloaded, serve light-ranker results or cached popular-in-region feeds. Users barely notice a slightly worse ranking; they notice errors.
Transcoding backlog
Upload spikes (festivals, trends) create a queue of transcode jobs. Prioritise: creators with many followers and short videos first, produce low rungs first so the video goes live quickly, add higher rungs later. Use spot instances for the worker fleet, since jobs are retryable.
Hot video metadata and counters
A viral video can get tens of thousands of reads and likes per second. Cache metadata in local in-process caches with short TTLs, and use sharded counters or stream aggregation for likes and views.
10 Failure handling
Recommendation service down
The feed service falls back to a precomputed list of top videos per language and region, refreshed every few minutes and stored in Redis. The app keeps playing; personalisation drops for a while. This is a graceful degradation, not an outage.
CDN or region problem
Use multiple CDNs; the manifest URL host can be switched per region via DNS or by the feed service. Client retries a failed segment on an alternate host.
Transcoder crash mid-job
Jobs are idempotent: output paths are keyed by video_id and rendition, so a retry just overwrites. Kafka consumer commits offset only after completion; failed jobs go to a dead-letter queue after N tries.
11 What impresses
- Separating the upload pipeline from the watch path and treating them with different SLOs
- Talking about time-to-first-frame and rebuffer ratio as client-measured SLOs
- Prefetching the next 2-3 videos with limits to avoid wasting user data
- Explaining the candidate-generation funnel with a latency budget
- Staged exposure / test pools for new creators
- Estimating egress bandwidth and naming it as the dominant cost
- Multilingual moderation for Indian languages using ASR and OCR
12 Mistakes
- Streaming video bytes through application servers instead of object storage + CDN
- Designing a follower fan-out timeline like Twitter for a feed that is mostly not from follows
- Ignoring adaptive bitrate and low-bandwidth users
- Writing one database row update per view
- No fallback when the ML ranking service is slow or down
- Skipping moderation or putting it after the video is already viral
13 Follow-ups
Lessons this design uses
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.