SevyDevy

Guide · System design

Short Video App System Design: Infinite Feed and Prefetching

23 min readUpdated By Saket Bhatnagar

Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.

In short

  • Short video app system design is mostly about one moment: the swipe. The next video must already be on the phone, so it plays the instant it appears.
  • The server keeps a ranked list of videos for each user's session. It hands them out in pages of 10 with a cursor, a bookmark for where you stopped, and refills early.
  • The app prefetches, meaning it downloads ahead, in a sliding window. On good networks, the next two videos get their first 2 seconds, and later ones get only cover images.
  • Videos are cut into 2-second files called segments, in several qualities, and served from a CDN, a network of servers near users. Prefetch less on slow networks, and track wasted bytes.
  • Beginners can stop after the section on storing and delivering videos. Engineers then get a sized Bloom filter, a compact bit set that stops repeats, an ffmpeg video-tool check of segment length, and Android Media3 preloading rules.

What goes wrong when a short-video app waits for the swipe to load?

The user sees a spinner. If the next video starts downloading only after the swipe, every swipe waits on the network, and on a phone that can take a second or more.

Picture a common setup. A small team runs a dance-video app with about 1.2 million daily users, many on patchy 4G in small towns around Bhopal. The setup is made up, but the problem is real.

People film 15 to 30 second clips and swipe through other people's clips. The first version was the naive one. The feed API, the server endpoint the app calls for videos, returned 10 videos per request.

The app played one video at a time. It asked for the next 10 only when the user reached the tenth.

The team measured time to first frame: the gap between a swipe and the first picture on screen. In Bhopal city it was about 0.9 seconds, and in nearby towns often over 2 seconds (made up). They found three problems.

  • Every swipe started a fresh download, so every swipe waited on the network.
  • After every tenth video, the screen froze while the app asked the feed API for more.
  • A clip the user watched an hour ago sometimes showed up again, because each request ranked videos from scratch.

Users noticed. Sessions with three or more spinners ended about 40 percent sooner than smooth ones (made up). So the team fixed the swipe first, and then the feed behind it.

What does short video app system design involve?

Short video app system design means building two loops that meet at the swipe. A server loop decides which videos come next. A phone loop gets those videos ready before they are needed.

Uploading clips, converting them into different qualities and storing them matter too. A guide on designing a video platform like YouTube covers that side. Here we stay on the watching side, because that is where users feel speed.

The requirements the team writes down

  • Feed: an endless vertical list of videos, one per screen, with no visible end and no loading pause.
  • Start time: the first frame within 300 milliseconds of a swipe, for 95 percent of swipes.
  • No repeats: a user should not see the same clip twice within a week, unless they search for it.
  • Data cost: no more than a fifth of the bytes the app prefetches should go unwatched.
  • Scale: about 1.2 million daily users and around 60 swipes each, so about 72 million video starts a day.

That last line is worth working out. 72 million starts over 86,400 seconds is about 833 starts per second on average. Evenings run about four times busier (made up), so the team plans for about 3,300 per second.

How does an infinite feed get its next videos?

An infinite feed gets its next videos in small pages from a ranked list the server keeps for each user. The app asks for more well before it runs out, so the user never reaches an end.

Scoring every video in the catalogue on every request would be far too slow. So the feed service works in two steps. Candidate generation pulls about 500 likely videos for this user from several sources.

Then a ranker orders those 500 by how likely the user is to watch each one through. The feed recommendation side has its own guide; here we treat the ranker as a box.

The service saves the ranked list as a feed session: a short-lived list kept in a cache, one per user. Each page request takes the next 10 videos from it. When fewer than 20 remain, the service ranks a fresh batch and adds it to the end.

The app tracks its place with a cursor. A cursor is an opaque token, a string the app stores and sends back without reading. Here it names the feed session and the position in it.

The naive version sent a page number, and the server ranked from scratch each time. A fresh ranking is a different list, so page 3 of it can skip or repeat videos. That was the repeat bug.

The team also changes when the app asks. Instead of waiting for the tenth video, the app asks for the next page when five unwatched videos remain. That leaves several swipes of runway, even when a request is slow.

The diagram shows the whole design. Small JSON pages, plain text lists of video details, come from the feed API. Heavy video bytes come only from the CDN.

Short-video feed architecture

Notice the two paths: video ids and details travel on the API path, while video bytes travel only through the CDN.

Diagram connections:

  • Short-video app to Feed API (Next page)
  • Feed API to Ranking service (Refill when low)
  • Feed API to Seen-videos filter (Skip repeats)
  • Ranking service to Video metadata store (Read candidates)
  • Short-video app to CDN edge servers (Fetch segments)
  • CDN edge servers to Segment storage (On cache miss)
  • Short-video app to Watch events in Kafka (Send events)
  • Watch events in Kafka to Ranking service (Fresh signals)
  • Short-video app: Plays the feed and prefetches ahead
  • Feed API: Hands out pages of 10 video ids
  • Ranking service: Orders about 500 candidates per user
  • Seen-videos filter: Remembers what each user was served
  • Video metadata store: Titles, cover images, playlist links
  • CDN edge servers: Serve video segments near users
  • Segment storage: Every video in several qualities
  • Watch events in Kafka: Watches, skips and likes, kept durably

Here is the core of the page handler. The names sessions, candidates, ranker and seen are stand-ins for real services. The seen service records which clips each user was already served.

python
def next_page(user_id, cursor, size=10):
    """Return the next videos in a user's feed, plus the cursor for later."""
    session = sessions.load(user_id, cursor)       # ranked list kept in a cache
    if session.remaining() < 2 * size:             # refill before it runs dry
        pool = candidates(user_id, limit=500)
        session.append(ranker.rank(user_id, pool))
    page = []
    while len(page) < size and session.remaining() > 0:
        video = session.pop_next()
        if seen.might_contain(user_id, video.id):  # skip anything served before
            continue
        seen.add(user_id, video.id)
        page.append(video)
    sessions.save(session)
    return page, session.cursor

What just happened? The refill check tops up the list once fewer than 20 videos remain. The seen check skips anything this user was already served.

We ran the handler against in-memory stand-ins whose refills overlap on purpose, like the naive ranking did. Fifteen pages gave 150 videos, and all 150 were different.

The seen check uses a Bloom filter per user. A Bloom filter is a compact set made of bits. Asked whether a clip is in it, it can wrongly say yes, but it never wrongly says no.

A wrong yes skips one fresh clip, which costs little. A clip the filter holds is never served twice. A plain Bloom filter cannot delete single entries, so the team keeps one filter per week, checks the last two and drops the oldest.

How does prefetching make the next video start instantly?

Prefetching makes the next video start instantly by downloading its first few seconds while you watch the current one. When you swipe, the player reads from the phone's memory instead of waiting on the network.

Think of a pani puri vendor at a busy evening stall. He fills the next puri while you are still eating the one in your hand. It lands on your plate the moment you are ready.

Notice what he does not do. He never fills ten puris at once, because they would go soggy before you reach them. Prefetching has the same limit: fetching too far ahead wastes data, battery and memory.

Where the picture breaks: a puri is either filled or not. A video can be fetched in part, a few seconds at a time, and that is exactly what the app does.

So the team builds a prefetch window. It is a short range of positions around the playing video, with less effort spent the further away a video sits.

  • Previous video: keep what is already downloaded, in case the user swipes back.
  • Next video: fetch the first 2 seconds and prepare the decoder, the part of the phone that turns compressed video into pictures.
  • The video after that: fetch the first 2 seconds too, but only on a good network.
  • Three to five ahead: fetch only the cover image and title, which are tiny.
  • Anything further: fetch nothing yet, because the ranking may still change.

Android's own media library uses the same idea. The Android team calls its Media3 preload manager the right fit for vertical feeds and carousels. It loads items in order of how close they are to the playing one.

The diagram shows the window while the user watches video 12. Each swipe shifts the whole window one video forward.

The prefetch window

Notice how effort drops with distance from the playing video: seconds of video for the next two, covers only after that.

Diagram connections:

  • Video 11, just watched to Video 12, playing (Swiped past)
  • Video 12, playing to Video 13, next (Next swipe)
  • Video 13, next to Video 14
  • Video 14 to Videos 15 to 17
  • Videos 15 to 17 to Video 18 onward
  • Video 11, just watched: Cached bytes kept for a swipe back
  • Video 12, playing: Streams the rest while it plays
  • Video 13, next: First 2 s fetched, decoder ready
  • Video 14: First 2 s fetched on good networks
  • Videos 15 to 17: Cover image and title only
  • Video 18 onward: Nothing fetched; order may still change

Here is the window logic in Python. The list feed holds the video ids the app has, and playing is the index of the video on screen. Index 11 is video 12, because Python counts from 0.

The value network is the connection type the phone reports. The flag save_data is true when the user turned on a data saver.

python
SLOW = {"slow-2g", "2g", "3g"}
SEGMENT = 2  # seconds; players fetch whole segments

def seconds_to_prefetch(distance, network, save_data):
    """Seconds of video to fetch for the item `distance` places ahead."""
    slow = save_data or network in SLOW
    if distance == 1 or (distance == 2 and not slow):
        return SEGMENT  # the first segment only
    return 0  # 0 means cover image and title only

def plan_window(feed, playing, network, save_data):
    plan = {}
    ahead = feed[playing + 1 : playing + 6]        # look 5 videos ahead
    for distance, video_id in enumerate(ahead, start=1):
        plan[video_id] = seconds_to_prefetch(distance, network, save_data)
    return plan

feed = [f"v{n}" for n in range(1, 31)]             # v12 sits at index 11
print(plan_window(feed, 11, "4g", save_data=False))
print(plan_window(feed, 11, "3g", save_data=False))
text
{'v13': 2, 'v14': 2, 'v15': 0, 'v16': 0, 'v17': 0}
{'v13': 2, 'v14': 0, 'v15': 0, 'v16': 0, 'v17': 0}

What just happened? On 4G, videos 13 and 14 each get 2 seconds, and videos 15 to 17 get covers only. On 3G, only video 13 gets video bytes.

A result of 0 still fetches the cover image, because a blank card looks broken. The slice feed[playing + 1 : playing + 6] is the window: five videos ahead, and never more. Every amount is 2 seconds because players fetch whole 2-second files called segments, covered below. Asking for 1 second still downloads all 2.

How much should a short-video app prefetch on a slow network?

On a slow network, prefetch less: only the next video, only its first 2 seconds, and at a lower quality. A small buffer that is complete beats a big one still downloading when the user swipes.

Prefetching competes with the video playing now. If the phone gets 1 Mbps (one megabit per second) and spends half of it on video 14, video 12 may stall. So the playing video always comes first.

Media3's simpler playlist preloading follows this rule. The Android team's write-up says preloading starts only when nothing is loading for the current playback, so preloading does not compete with the playing video for bandwidth.

On the web, the Network Information API reports an effective type: slow-2g, 2g, 3g or 4g. The browser works it out from recent download speed and round-trip time, how long a message takes to go and come back.

The same API has a saveData flag, true when the user chose to reduce data use. Treat both as hints. As of 19 October 2026, Chrome, Edge and Samsung Internet support them, but Firefox and Safari do not.

So the player also trusts its own numbers. It measures how fast the last few segments arrived, and uses that speed to choose a quality.

The team's prefetch plan by network, per swipe ahead (made up; 1 KB = 1,000 bytes)
NetworkNext videoVideo afterPrefetch qualityData at risk
Wi-FiFirst 2 sFirst 2 s720p at 1.5 MbpsAbout 750 KB
Good 4GFirst 2 sFirst 2 s540p at 0.9 MbpsAbout 450 KB
3G or weak 4GFirst 2 sCover only360p at 0.5 MbpsAbout 125 KB
Data saver onFirst 2 sCover only240p at 0.25 MbpsAbout 60 KB

To check a row, multiply seconds by bit rate and divide by 8. Wi-Fi is 4 seconds at 1.5 Mbps: 6 megabits, which is 750 KB. Real first segments run a little bigger, because each opens with a frame stored whole.

Compare that with the naive fix of downloading whole videos. A 20-second clip at 1.5 Mbps is about 3.75 MB. Fetching the next three in full puts about 11 MB at risk if the user closes the app.

By that maths, the Wi-Fi window risks 750 KB, 15 times less. On this guide's test clip, it measured about 880 KB against 12.1 MB, still about 14 times less. In the team's logs, most skips happen in the first two seconds (made up), so the first seconds are the bytes worth betting on.

How are short videos stored and delivered so they start fast?

Short videos start fast when they are cut into small segments, stored in a few qualities, and served from a CDN close to the user. The app then needs only the first small segment to begin.

The app uses HLS (HTTP Live Streaming), a format written up in RFC 8216. An RFC is a numbered internet document; this one is informational, not a formal standard.

In HLS, a video becomes many small media files called segments, plus a playlist, a text file listing them in order. Bit rate is how much data one second of video uses.

A master playlist lists variant streams: the same video encoded at different bit rates. Each variant must state its peak bit rate. The RFC says clients should switch between variants to adapt to network conditions.

The team makes two choices for short clips. Segments are 2 seconds long, so the first one is small. The feed response also carries each video's playlist link and first segment link, which saves round trips on every swipe.

A CDN (content delivery network) is a set of servers in many cities that keep copies of popular files. When a dance clip goes viral, nearly all requests for it stop at a nearby CDN server. Only misses reach the team's own storage.

How does the feed react to what you just watched?

The feed reacts by sending every watch, skip and like as an event, and using fresh events when it ranks the next batch. A clip you loved two minutes ago can shape what you see ten swipes later.

The app sends small events: the video id, seconds watched, whether it looped, and any like or share. The team treats a skip within one second as a strong sign of no interest. It treats a full watch with a loop as a strong sign of interest.

The events flow into Kafka, an event log that stores events durably and lets many services read them at their own pace. One consumer updates counts per user and per video every few seconds.

The ranker reads those fresh counts each time it refills a feed session. A 2022 paper on real-time recommendation goes a step further than fresh counts.

Its authors argue that keeping model training separate from serving stops a recommender from learning from user feedback in real time. Their system trains online instead, learning from feedback as it arrives.

Freshness clashes with prefetching. If the ranker swaps out video 13 after the app fetched its first 2 seconds, those bytes are wasted. So the team locks the head of the list: once a video is sent to the app, it never changes.

Fresh signals only reorder videos still waiting on the server. Follow the loop in the diagram: it starts and ends at the app, and the lock lives in the feed session box.

How the feed learns from each swipe

Notice the lock in the feed session: fresh signals reorder only videos not yet sent, so prefetched bytes are not wasted.

Diagram connections:

  • Short-video app to Event log in Kafka (Events)
  • Event log in Kafka to Signal updater
  • Signal updater to Fresh signals cache (Updated counts)
  • Fresh signals cache to Ranker (Read at refill)
  • Ranker to Feed session (Append fresh batch)
  • Feed session to Short-video app (Next page)
  • Short-video app: Sends watch, skip and like events
  • Event log in Kafka: Keeps events in order within each partition
  • Signal updater: Updates counts every few seconds
  • Fresh signals cache: Recent likes and skips per user
  • Ranker: Orders the next batch of candidates
  • Feed session: Sent videos locked, the rest can change

What goes wrong in an infinite short-video feed?

The usual failures are repeats, stalls after fast swipes, wasted data, memory pressure, hot videos and empty feeds for new users. Each one has a known guard.

  • Repeats. The same clip shows up twice. Guard: the per-user seen filter, checked before every page leaves the server.
  • Stalls after fast swipes. A user flicks faster than the window can fill. Guard: cancel stale prefetches and fetch the new next video first.
  • Wasted data. The app fetches videos the user never reaches. Guard: a small window, a smaller one on slow networks, and a waste metric.
  • Memory pressure. Every preloaded item holds memory buffers, and low-cost phones run out. Guard: remove far items from the window and reuse a small pool of players.
  • Hot videos. A viral clip's first segment is requested everywhere at once. Guard: the CDN absorbs the reads; check that it merges simultaneous misses for one file into one fetch.
  • Empty feed for new users. With no history, the ranker has little to go on. Guard: start with popular clips in the user's language and city, then adapt.

The player pool deserves a word. Creating a fresh player for every video is the naive choice, and each player holds its own buffers. The team keeps three players and moves them along the window, a design choice rather than a library rule.

How big should the seen-videos filter be?

About 504 bytes per user per week, if a user is served up to 420 clips a week and you accept 1 wrong yes in 100. The standard sizing formula gives that, and a short test confirms it.

For n items and a wrong-yes rate p, a Bloom filter needs about m = -n ln(p) / (ln 2) squared bits. It uses k = (m / n) ln 2 hash functions, and each hash picks one bit to set.

Here 420 is 60 clips a day for 7 days. The code below builds a filter of that size, adds 420 served clips, then asks about 100,000 clips it never saw.

python
import hashlib, math

def bloom_size(n, p):
    """Bits (m) and hash count (k) for n items at a wrong-yes rate p."""
    m = math.ceil(-n * math.log(p) / math.log(2) ** 2)
    return m, round(m / n * math.log(2))

class Bloom:
    def __init__(self, n, p):
        self.m, self.k = bloom_size(n, p)
        self.bits = bytearray((self.m + 7) // 8)

    def _spots(self, item):
        d = hashlib.sha256(item.encode()).digest()
        a, b = int.from_bytes(d[:8], "big"), int.from_bytes(d[8:16], "big")
        return [(a + i * b) % self.m for i in range(self.k)]

    def add(self, item):
        for s in self._spots(item):
            self.bits[s // 8] |= 1 << (s % 8)

    def might_contain(self, item):
        return all(self.bits[s // 8] >> (s % 8) & 1 for s in self._spots(item))

week = Bloom(n=420, p=0.01)                        # 60 clips a day for 7 days
served = [f"vid-{i}" for i in range(420)]
for v in served:
    week.add(v)
unseen = [f"new-{i}" for i in range(100_000)]
print("bits:", week.m, "hashes:", week.k, "bytes:", len(week.bits))
print("served clips it forgot:", sum(not week.might_contain(v) for v in served))
print(f"unseen clips wrongly skipped: {sum(map(week.might_contain, unseen)) / 1000:.2f}%")
text
bits: 4026 hashes: 7 bytes: 504
served clips it forgot: 0
unseen clips wrongly skipped: 1.06%

What just happened? 420 served clips fit in 4,026 bits (504 bytes) with 7 hashes. The filter remembered all 420, and wrongly skipped 1.06 percent of unseen clips, close to the 1 percent target.

Two limits matter. A heavy user served twice the planned 420 clips pushed the same filter's wrong-yes rate to 15.85 percent in our test. So size the filter for heavy users, not the average one.

A plain Bloom filter also cannot forget one clip. In our test, clearing one clip's 7 bits made the filter forget 4 other served clips too. That is why the team drops a whole week's filter instead.

For 1.2 million daily users with two weekly filters each, the total is about 1.21 GB: 1.2 million x 2 x 504 bytes.

How do you check that video segments are really 2 seconds long?

Read the playlist your packager writes. The EXT-X-TARGETDURATION line, the longest segment length rounded to whole seconds, should say 2, and every EXTINF line, one segment's length, should be about 2.0 seconds.

Pause and predict: you ask ffmpeg, a free command-line video tool, for 2-second segments without touching anything else. What lengths do you get?

A keyframe is a frame stored whole, so playback can start from it. The frames between keyframes store only changes. In our runs, ffmpeg cut segments only at keyframes.

The test below ran on ffmpeg 6.1.1. It makes a 20-second 720p test pattern at 1.5 Mbps with the H.264 encoder libx264, and cuts it twice with a 2-second target.

bash
mkdir -p a b
# 1. Encoder defaults, 2-second segment target
ffmpeg -f lavfi -i testsrc2=size=1280x720:rate=30:duration=20 \
  -c:v libx264 -b:v 1500k -maxrate 1500k -bufsize 3000k \
  -f hls -hls_time 2 -hls_playlist_type vod a/clip.m3u8
# 2. Same, with a keyframe every 60 frames (2 s at 30 fps)
ffmpeg -f lavfi -i testsrc2=size=1280x720:rate=30:duration=20 \
  -c:v libx264 -b:v 1500k -maxrate 1500k -bufsize 3000k \
  -g 60 -keyint_min 60 -sc_threshold 0 \
  -f hls -hls_time 2 -hls_playlist_type vod b/clip.m3u8
grep -c EXTINF a/clip.m3u8 b/clip.m3u8
grep -h -E "TARGETDURATION|EXTINF" a/clip.m3u8 b/clip.m3u8 | sort | uniq -c
ls -l a/clip0.ts b/clip0.ts

The first grep counts segments per playlist. The second counts each distinct duration line. Expect this:

text
a/clip.m3u8:3
b/clip.m3u8:10
      1 #EXT-X-TARGETDURATION:2
      1 #EXT-X-TARGETDURATION:8
     10 #EXTINF:2.000000,
      1 #EXTINF:3.333333,
      2 #EXTINF:8.333333,

What just happened? With encoder defaults, keyframes landed 250 frames (8.3 seconds) apart, so ffmpeg could not cut at 2 seconds. Playlist a has three segments of 8.3, 8.3 and 3.3 seconds. Its target duration says 8, because 8.33 rounds to 8.

With -g 60, a keyframe every 60 frames, playlist b has ten 2-second segments. The flag -sc_threshold 0 stops extra keyframes at scene changes. The script also sets -keyint_min 60, a common habit, but libx264 caps it at 31 when -g is 60, so here it changes nothing.

The ls line shows why this matters. The first segment was about 1.68 MB in a, but about 440 KB in b. Byte counts vary slightly between runs; segment counts and durations did not.

Each full clip came to about 4.0 MB, not the 3.75 MB the plain bit-rate maths gives. Two things add up. The encoder overshot slightly: the same video with no container was 3.87 MB.

The segment file format, MPEG-TS, then wraps the video in small packets with their own headers, which added about 4 percent more in our run.

RFC 8216 points the same way: it says a segment containing H.264 video should contain an IDR frame, the keyframe that resets decoding.

How do Android apps preload a short-video feed with Media3?

Android apps that play video with Media3, Android's media library, can use its DefaultPreloadManager. Your app says how much of each item to load, and the manager decides the order by distance from the playing item.

As of 19 October 2026, the latest stable Media3 release is 1.11.1, from 10 September 2026. For each item, the app can ask for a set duration of video, metadata only, or nothing yet.

That maps onto the team's window: 2 seconds for each of the next two videos on good networks, and metadata only further ahead. The Android team's own example loads 5 seconds of the next item and 3 seconds of the previous one.

Three rules from the Android documentation and blog posts matter in production.

  • Build each player from the same builder as the preload manager. A preloaded item must be played on a player made by that builder, so the two share parts such as the load control and bandwidth meter.
  • Remove items far from the user's position. Each preloaded item holds memory buffers, and the Android team calls forgotten removals the primary cause of memory issues in preloading code.
  • Release the preload manager when the screen closes, and release it before the player.

How do you explain short video app system design in a system design interview?

Start from the swipe, then walk outward from the phone to the servers, giving a number at each step.

  1. Clarify the scope

    Ask whether the question is about the feed or about uploads. Agree on daily users, swipes per user and a start-time target.

  2. Do the rough maths

    Turn users and swipes into video starts per second: 72 million a day is about 833 a second. Estimate bytes per start at each quality.

  3. Draw two paths

    Show the feed API for small JSON pages and the CDN for video segments. Keep them separate on the board.

  4. Explain paging

    Describe a ranked feed session per user, read with a cursor, refilled before it runs dry, and checked against a Bloom filter of served clips.

  5. Explain the prefetch window

    The next video is partly fetched with its decoder ready. Less is fetched further ahead, and less again on slow networks.

  6. Close the feedback loop

    Events go into a stream and fresh signals feed ranking. The head of the list stays locked, so prefetched bytes are not wasted.

  7. Name the metrics

    Time to first frame, stall rate, wasted bytes and repeats per session. Say what alert you would set on each one.

Questions people ask

How do short-video apps load the next video so fast?
They prepare the next video before you swipe. Its first few seconds are usually already on the phone, and the player is ready to decode them. Short segments, several qualities and CDN servers close to users do the rest. The exact setup inside any one company is not fully public, but this pattern is widely described.
What is the difference between prefetching and preloading a video?
The words are often used loosely. In this guide, prefetching means downloading bytes ahead of time. Preloading can also mean getting the player ready, such as reading the video's tracks and warming up the decoder. Media3, for example, lets an app choose a duration, metadata only or nothing for each item.
How many videos should a short-video app prefetch?
Usually one or two videos ahead get real video bytes, and a few more get only cover images. The right number depends on the network, the phone's memory and how fast your users swipe. Start small, then raise it only if time to first frame improves without a big jump in wasted bytes.
Why not just download the whole next video in advance?
Because many users skip early, so most of those bytes may never play. Whole downloads also compete with the video playing now and fill the phone's memory. Fetching the first few seconds gives the same instant start for a fraction of the data, and the rest streams in if the user keeps watching.
Does a short-video feed use cursor or offset pagination?
Cursor pagination is the usual choice. If the server ranks afresh on each request, a page number points into a different list each time, so it can skip or repeat clips. A cursor names the user's feed session and the position in it, and the server continues from there.
How do short-video apps avoid showing the same video twice?
They keep a record of what each user was served and check it before sending a page. A Bloom filter per user is one compact option, because it never misses a clip it has stored. Old filters are dropped after a set time, such as a week or two, so old favourites can return.
Is HLS or DASH better for a short-video app?
Both work, because both split video into segments and offer several qualities. HLS is written up in RFC 8216. DASH is an international standard, ISO/IEC 23009-1, whose sixth edition was published in July 2026. Teams usually choose by the players their users' devices support, and some offer both.

Sources and further reading

Practise this with Lala

Practise