Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

System Design (High-Level Design)  Module 23 – Case studies: social, feeds and search

Design X (Twitter): timelines and search

Design X (Twitter) timelines and search: hybrid fan-out for huge followings, posts searchable in seconds, paged reply trees and trends.

  • Advanced
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free core, Pro depth: 9 of 30 minutes free
Free: Requirements: four features on one stream · The estimate: 5,787 posts a second, 1.1 million inbox writes · API and data model: posts carry their thread's id · 3 more sections. With a pass: 3 in-depth parts and 3 interview answers, about 21 minutes.

What you will learn

  • Estimate the posts, timeline reads, inbox writes and index memory of a service at the scale of X
  • Choose the follower threshold for pulling posts, with numbers for writes and read latency
  • Design a real-time search index that makes new posts searchable within seconds
  • Read a reply tree keyed by conversation id in pages, with bounded depth and cursors
  • Connect timelines, search, replies and trends to one log of posts

Before you start

What you’ll learn · Before you start

What you will learn

  • Estimate the posts, timeline reads, inbox writes and index memory of a service at the scale of X
  • Choose the follower threshold for pulling posts, with numbers for writes and read latency
  • Design a real-time search index that makes new posts searchable within seconds
  • Read a reply tree keyed by conversation id in pages, with bounded depth and cursors
  • Connect timelines, search, replies and trends to one log of posts

Before you start

On this page

A service like X takes 500 million posts a day and runs four features on that one stream. Home timelines reuse the hybrid fan-out: A feed design that pushes the posts of ordinary authors into their followers' inboxes and pulls the posts of the largest accounts when a feed loads, then merges the two by time. More in “Fan-out: push, pull and hybrid timelines” of a news feed: a new post’s id is pushed into the inbox of every active follower, except when the author has a very large following; those posts are pulled and merged in when a timeline is read. Search must find a post seconds after it is written: an indexer reads the same log of new posts, appends each one to an in-memory index segment for the current hour, and readers see it after the next refresh (search index): The step that makes newly indexed and changed documents visible to search. Elasticsearch refreshes every second by default, so a write becomes searchable at the next refresh, not when it is saved. More in “Inverted indexes, analysers and BM25 ranking”. Replies hang under the post they answer, as a tree whose posts share one conversation id, read a few levels and a page at a time, and trends count terms in sliding windows. This case study sizes each part and follows every path end to end.

Not affiliated

Based on public information about X (Twitter). MySmartCoPilot is not affiliated with X (Twitter), and X (Twitter)’s real architecture may differ from what this lesson describes.

Requirements: four features on one stream

Agree on the scope first, because each feature has its own read path:

Feature In scope
Timelines the home timeline shows followed accounts’ posts, newest first or ranked; a profile shows one account’s posts
Search keywords and hashtags over public posts, in a Latest view and a Top view
Replies a post’s conversation, ranked, a few levels deep, in pages
Trends the terms rising fastest in the last hour, per region
Out of scope direct messages, ads, notifications and media processing

Functionally, people post, reply, follow, read timelines, search, open conversations and see trends. The non-functional requirements are numbers: a timeline page within 200 ms and a search within 100 ms at the 99th percentile; a new post in followers’ timelines within 5 seconds and in search within 10; and deletes, blocks and protected accounts enforced on every read. Elsewhere availability wins over strict consistency: a timeline a few seconds old is acceptable, an empty one is not.

The estimate: 5,787 posts a second, 1.1 million inbox writes

Quantity Assumption Result
Posts written 500 million a day 5,787 a second; about 17,400 at a 3 × peak
Home timeline reads 250 million readers × 8 loads a day 23,148 a second; about 69,000 at the peak
Inbox writes, pushing every post 5,787 posts × 190 active followers reached about 1.1 million a second
Searches 1 billion a day 11,574 a second; about 35,000 at the peak
Search index in memory 7 days × 500 million posts × 100 bytes 350 GB; 700 GB with one replica
Post storage 500 million posts × 1 KB 500 GB a day; about 183 TB a year

Two rows shape the design. Fan-out makes 190 inbox writes for every post, so the pull threshold is the main lever on the write path. And a week of posts fits in the memory of a few dozen servers, so search serves recent posts from memory.

Checkpoint

Check the estimate

  1. Question 1 of 2 If every post is pushed into the inbox of each active follower, and a post reaches 190 active followers on average, how many inbox writes a second does fan-out make at 5,787 posts a second?

    Type a number.

    A hint

    Every follower a post reaches is one write.

    Show the answer: Question 1 of 2

    Answer: 1099530 inbox writes a second (anything from 989577 to 1209483 counts)

    5,787 × 190 ≈ 1.1 million inbox writes a second on average, and three times that at the peak. That is why the accounts with very large followings are pulled at read time instead of pushed.

  2. Question 2 of 2 The search index keeps the last 7 days of posts in memory at about 100 bytes a post. How many gigabytes is that, before replicas?

    Type a number.

    A hint

    500 million posts a day.

    Show the answer: Question 2 of 2

    Answer: 350 GB (anything from 315 to 385 counts)

    500,000,000 × 7 = 3.5 billion posts, and × 100 bytes = 350,000,000,000 bytes, 350 GB. With one replica it is 700 GB, so a week of posts fits in the memory of a few dozen servers.

API and data model: posts carry their thread’s id

  • POST /v1/posts with the text, media ids and an optional reply_to returns the post with its id and conversation id.
  • GET /v1/timelines/home?cursor=… returns a page of posts and the next cursor.
  • GET /v1/search?q=%23final&order=latest&cursor=… returns matching posts; order=top ranks them.
  • GET /v1/conversations/{id}?cursor=… returns a page of a reply tree; GET /v1/trends?region=… returns trends.
Store and key Holds
Posts, by post id author, text, media links, conversation id, reply-to id, audience and a deleted flag; ids are ordered by time
Inboxes, by reader the newest post ids pushed to the reader
Search segments, by hour and partition an in-memory inverted index of that hour’s posts
Reply edges, by conversation and parent the parent’s replies, in rank order
Trend counts, by region, term and minute posts and distinct accounts in each window

Every post carries the id of the post that started its thread. X’s API exposes the same idea: all replies in a thread share a conversation_id, which equals the first post’s own id (X API documentation). Post ids are time-ordered, so a slice of time is a range of ids.

NanoID, ULID & Snowflake ID Generator Generate time-ordered ids and read the time inside them: the property that lets a range of ids stand for a slice of time.

One log feeds four consumers, each at its own offset

A post is stored and logged; fan-out, indexers, replies and trend counters read the log; timeline and search services read their stores.AppsPost servicePost storeids by timePost logLog consumersRead pathsFan-outworkersInboxesids per readerIndexersrefresh 1 sSegmentsby hourRepliesserviceReply edgesby parentTrendcountersCountsper minuteTimelineserviceSearchbroker1. post2. store3. event4. read5. read the stores

One log of posts feeds four consumers; the timeline service and the search broker read what they build

Text description of the diagram

An app sends a new post (1) to the post service, which stores it in the post store, where ids are ordered by time (2), and appends an event to the post log (3).

Four consumers read the log (4), each at its own offset, and each writes its own store: fan-out workers push the post's id into followers' inboxes when the author is below the pull threshold; indexers append the post to the newest search segment, cut by hour and refreshed every second; the replies service adds a reply edge under its parent; and trend counters keep counts per minute.

The read paths (5) read what the consumers built: the timeline service reads a reader's inbox and merges the recent posts of the pulled accounts the reader follows, and the search broker asks every partition of the segments it needs and merges their top results.

The write path. The post service stores the post and appends an event to a partitioned log. Four consumers read that log, each at its own position: a consumer’s position in a partition is a single offset: The position of a record in a log or a partition: 0 for the first record, then 1, 2 and so on. A reader group's committed offset is the next record it has not processed. More in “Queues, logs and pub-sub: three different tools”, which it can also move back to read again (Kafka documentation). Fan-out workers push the id into active followers’ inboxes unless the author is above the pull threshold. Indexers add the post to the newest search segment. The conversation service files a reply under its parent, and trend counters count its terms. Follow one reply through the log:

Interactive · message by message One reply through the log and its four consumers Step through one reply from the post service to the four consumers that read the log, each at its own pace.
One reply through the log and its four consumers, step 7 of 7. Messages so far: Post service to Post log: reply created; Post log to Four consumers: fan-out workers; Post log to Four consumers: indexers; Post log to Four consumers: conversation service; Post log to Four consumers: trend counters.Post servicereply storedPost logevent appendedFour consumerswindow counts updatedreply createdfan-out workersindexersconversation servicetrend countersfour positions, one log

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Stored with its thread. A reader replies to a post. The post service stores the reply with its own id and its conversation id, the id of the post that started the thread.
  2. One event on the log. The service appends one event to the post log. Every feature downstream starts from this event; none of them writes to the post store.
  3. Fan-out reads it. Fan-out workers read the event and push the reply's id into the inboxes of the author's active followers, unless the author is above the pull threshold.
  4. An indexer reads it. An indexer appends the reply to the newest search segment. It is not searchable yet; the next refresh makes it visible, by default about a second later.
  5. Filed under its parent. The conversation service adds a reply edge under the post it answers, keyed by the conversation id and the parent's id.
  6. Counted for trends. Trend counters add the reply's terms and hashtags to the current one-minute window of its region.
  7. Each at its own offset. Each consumer keeps its own position in every partition of the log. A slow indexer delays search alone, and it catches up from its committed offset without disturbing the others.

The read paths. A home-timeline read takes the reader’s inbox, merges the recent posts of the pulled accounts they follow, fetches the posts in one batch, drops what the reader may not see, and ranks the page. A search goes to every index partition of the newest hours, and a broker merges their best matches. A conversation read takes one page of replies under a parent, a few levels deep. Twitter’s engineers described this search shape for their Earlybird servers: posts arrive from an ingestion pipeline, only the newest of a handful of segments is written, and posts were typically searchable 10 seconds after creation, with queries answered in about 50 ms (Earlybird paper).

The model below sends 12,135 posts through a log into a segmented index and measures how long each waited to become searchable.

Visibility delay for three refresh intervals, and a newest-first search over time segments Python · realtime/realtime_index.py
# A real-time search index in miniature. Posts reach the indexer through a log, are appended to the active time
# segment, and become searchable at the next refresh. Part 1 measures the visibility delay, from posting to
# searchable, for three refresh intervals. Part 2 runs a newest-first search over the time segments.
# Seeded, so every run prints the same.
import random

RATE = 300  # posts a second
SECONDS = 40
SEGMENT_POSTS = 3_000  # the active segment closes after this many posts
WORDS = ["the", "match", "goal", "rain", "train", "late", "today", "city", "power", "cut", "news", "live",
         "score", "final", "metro", "traffic", "budget", "vote", "film", "song"]
WEIGHTS = [1 / (rank + 1) for rank in range(len(WORDS))]  # a few words are common, most are rare
TAGS = ["#final", "#rain", "#budget", "#metro"]


def make_posts(seed=7):
    """(post id, created at, reaches the indexer at, words), in the order the indexer receives them."""
    rng = random.Random(seed)
    posts, t, post_id = [], 0.0, 100_000
    while True:
        t += rng.expovariate(RATE)
        if t >= SECONDS:
            break
        # Most posts spend 50-400 ms in the log; 1 in 100 waits behind a slow batch for 1.5-3 s.
        lag = rng.uniform(0.05, 0.4) if rng.random() >= 0.01 else rng.uniform(1.5, 3.0)
        words = rng.choices(WORDS, weights=WEIGHTS, k=rng.randint(5, 12))
        if rng.random() < 0.08:
            words.append(rng.choice(TAGS))
        if rng.random() < 0.002:
            words.append("eclipse")  # a rare word
        posts.append((post_id, t, t + lag, words))
        post_id += 1
    return sorted(posts, key=lambda p: p[2])


class Segment:
    """One time partition: append-only postings, and a count of the documents that readers may see."""

    def __init__(self):
        self.docs, self.postings, self.visible = [], {}, 0

    def add(self, post_id, words):
        doc = len(self.docs)
        self.docs.append(post_id)
        for w in set(words):
            self.postings.setdefault(w, []).append(doc)

    def refresh(self):
        self.visible = len(self.docs)  # one number moves: everything appended so far becomes searchable


def build(posts, refresh_every):
    """Index the posts in arrival order; return the segments and when each post became searchable."""
    segments, visible_at = [Segment()], {}
    pending, i, tick = [], 0, refresh_every
    while i < len(posts) or pending:
        while i < len(posts) and posts[i][2] <= tick:
            post_id, _, _, words = posts[i]
            if len(segments[-1].docs) == SEGMENT_POSTS:
                segments[-1].refresh()
                segments.append(Segment())  # roll over: the old segment is read-only from now on
            segments[-1].add(post_id, words)
            pending.append(post_id)
            i += 1
        segments[-1].refresh()
        for post_id in pending:
            visible_at[post_id] = tick
        pending, tick = [], tick + refresh_every
    return segments, visible_at


def pct(sorted_values, p):
    return sorted_values[min(len(sorted_values) - 1, int(p / 100 * len(sorted_values)))]


def newest(segments, term, k):
    """The k newest searchable posts with the term, newest first; and how much of the index was read."""
    hits, touched, visited = [], 0, 0
    for seg in reversed(segments):
        visited += 1
        for doc in reversed(seg.postings.get(term, [])):
            if doc >= seg.visible:
                continue  # appended after the last refresh: not searchable yet
            touched += 1
            hits.append(seg.docs[doc])
            if len(hits) == k:
                return hits, touched, visited
    return hits, touched, visited


posts = make_posts()
created = {p[0]: p[1] for p in posts}
print(f"posts: {len(posts):,} in {SECONDS} s ({RATE} a second), {SEGMENT_POSTS:,} to a segment")
print()
print("refresh every   delay p50   p99      max      refreshes a minute")
for every in (0.1, 1.0, 5.0):
    segments, visible_at = build(posts, every)
    delays = sorted(visible_at[pid] - created[pid] for pid in visible_at)
    print(f"{every:>8.1f} s     {pct(delays, 50):>6.2f} s  {pct(delays, 99):>5.2f} s  {delays[-1]:>5.2f} s"
          f"  {60 / every:>8,.0f}")

segments, _ = build(posts, 1.0)
print()
print(f"segments after {SECONDS} s: {len(segments)}, the newest still being written")
for term, k in (("#final", 20), ("eclipse", 20)):
    hits, touched, visited = newest(segments, term, k)
    total = sum(len(s.postings.get(term, [])) for s in segments)
    print(f"newest {k} with {term}: {len(hits)} found, {touched} of {total} postings read, "
          f"{visited} of {len(segments)} segments visited")
    print(f"  first ids: {hits[:4]}")

Output

posts: 12,135 in 40 s (300 a second), 3,000 to a segment

refresh every   delay p50   p99      max      refreshes a minute
     0.1 s       0.28 s   1.66 s   3.04 s       600
     1.0 s       0.74 s   2.03 s   3.87 s        60
     5.0 s       2.79 s   5.24 s   7.87 s        12

segments after 40 s: 5, the newest still being written
newest 20 with #final: 20 found, 20 of 246 postings read, 2 of 5 segments visited
  first ids: [112093, 112027, 112077, 112040]
newest 20 with eclipse: 19 found, 19 of 19 postings read, 5 of 5 segments visited
  first ids: [111250, 111147, 110724, 109876]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 realtime_index.py

With a refresh every second, half the posts were searchable within 0.74 s and 99 % within 2.03 s. Refreshing ten times as often cut those to 0.28 s and 1.66 s, yet the slowest post still waited 3.04 s: posts held up in the log set the tail. A refresh every 5 seconds pushed the median to 2.79 s. The search shows why the index is cut by time: the 20 newest posts with #final came from the two newest segments after reading 20 of 246 postings, while a rare word was looked for in all five. Ids a fraction of a second apart can reach the indexer out of order, so the service sorts each page by id.

Checkpoint

Check: One log and its four consumers

  1. Question 1 of 2 Which steps sit on the path of a home-timeline read?

    Choose every answer that is right.

    Show the answer: Question 1 of 2

    Answer:

    • Reading the reader's inbox of pushed post ids
    • Merging the recent posts of the pulled accounts the reader follows
    • Fetching the posts in one batch and dropping those the reader may not see

    A read collects candidate ids from the inbox and the pulled timelines, then hydrates and filters them. Pushing ids into inboxes and indexing posts for search happen on the write path, from the log, so no read waits for them.

  2. Question 2 of 2 In the model, refreshing ten times a second instead of once cut the median delay from 0.74 s to 0.28 s. Why did the slowest post still wait 3.04 s?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Posts held up in the log set the tail; a faster refresh cannot shorten the time before a post reaches the indexer

    Freshness is a budget of waits. In the model, 1 post in 100 spends 1.5 to 3 seconds in the log behind a slow batch, and no refresh interval can make that post searchable before the indexer has it.

Practise: the newest posts in segments

The exercise asks for that Latest search; its sample tests run in the page:

Exercise · Medium · Python

Find the newest posts with a term across time segments

Write newest(segments, term, k, deleted) in newest.py. It answers a Latest search: the ids of the k newest searchable posts that contain term, newest first.

The index is a list of time segments, oldest first. Each segment is a dictionary with three keys:

  • "ids": the post ids in the order they were indexed.
  • "postings": for each term, the positions in "ids" of the posts that contain it, in the order they were indexed.
  • "visible": how many of the segment's posts are searchable; positions from "visible" on wait for the next refresh.

A later post was indexed later, so the newest posts are at the end of the last segment. deleted is a set of post ids that must never be returned.

Read as little of the index as you can: walk the segments from the newest, read each postings list from its end, and stop as soon as you have k posts. A test checks that an older segment is never read when the newer ones already hold enough posts. Return an empty list when k is 0 or nothing matches.

Starter code · newest.py

def newest(segments, term, k, deleted=frozenset()):
    """The ids of the k newest searchable posts that contain term, newest first."""
    # Replace this line with your code.
    return []
The sample tests · test_newest.py
from newest import newest


def segment(ids, postings, visible=None):
    return {"ids": ids, "postings": postings, "visible": len(ids) if visible is None else visible}


class NeverRead(dict):
    """A postings map that fails the test when it is read."""

    def get(self, *args):
        raise AssertionError("an older segment was read although newer segments held enough posts")

    def __getitem__(self, key):
        raise AssertionError("an older segment was read although newer segments held enough posts")


def test_newest_first_across_segments():
    """the newest segment comes first, and each list is read from its end"""
    old = segment([101, 102, 103], {"#final": [0, 2]})
    new = segment([104, 105, 106], {"#final": [1, 2]})
    assert newest([old, new], "#final", 3) == [106, 105, 103]


def test_stops_when_it_has_k():
    """an older segment is not read when the newest one holds k posts"""
    old = segment([101, 102], NeverRead())
    new = segment([103, 104, 105], {"rain": [0, 1, 2]})
    assert newest([old, new], "rain", 2) == [105, 104]


def test_posts_after_the_refresh_are_not_searchable():
    """positions from "visible" on wait for the next refresh"""
    new = segment([201, 202, 203, 204], {"metro": [1, 2, 3]}, visible=2)
    assert newest([new], "metro", 5) == [202]


def test_deleted_posts_never_appear():
    """a deleted post is skipped, and the search keeps looking"""
    old = segment([301, 302], {"vote": [0, 1]})
    new = segment([303, 304], {"vote": [0, 1]})
    assert newest([old, new], "vote", 2, deleted={304, 303}) == [302, 301]


def test_fewer_than_k_and_no_match():
    """all matches when there are fewer than k; nothing for an unknown term or k = 0"""
    old = segment([401, 402], {"film": [1]})
    new = segment([403], {"film": [0]})
    assert newest([old, new], "film", 10) == [403, 402]
    assert newest([old, new], "eclipse", 10) == []
    assert newest([old, new], "film", 0) == []
A hint

for segment in reversed(segments): visits the newest segment first, and for position in reversed(segment["postings"].get(term, [])): reads one postings list from its newest end. Skip a position that is not below segment["visible"], and an id that is in deleted; return as soon as the list of results is k long.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Practice that adapts to you

Practise this lesson

8 questions about 12 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 8 500 million posts are written a day. How many posts a second is that on average?

    Type a number.

    Show the answer: Question 1 of 8

    Answer: 5787 posts a second (anything from 5208 to 6366 counts)

    500,000,000 ÷ 86,400 seconds ≈ 5,787 posts a second. The busiest hour carries about three times the average, so plan the write path for about 17,400 a second.

  2. Question 2 of 8 250 million people load their home timeline 8 times a day. How many timeline reads a second is that on average?

    Type a number.

    Show the answer: Question 2 of 8

    Answer: 23148 reads a second (anything from 20833 to 25463 counts)

    250,000,000 × 8 = 2 billion reads a day, and 2,000,000,000 ÷ 86,400 ≈ 23,148 a second, about 69,000 at a peak of three times the average. Reads are four times the posts, but pushing posts into inboxes costs far more.

  3. Question 3 of 8 Which of these is out of scope for this design?

    Choose one answer.

    Show the answer: Question 3 of 8

    Answer: Direct messages

    The four features are timelines, search, replies and trends. Everything else is left out, because each of those brings its own read path.

  4. Question 4 of 8 A timeline may be a few seconds old. Which requirement is still enforced on every read?

    Choose one answer.

    Show the answer: Question 4 of 8

    Answer: Deletes, blocks and protected accounts

    Availability wins over strict consistency everywhere except the visibility rules: a timeline a few seconds old is acceptable, an empty one is not, and a deleted post must not be shown.

  5. Question 5 of 8 In X's API, what is the conversation_id of a reply?

    Choose one answer.

    Show the answer: Question 5 of 8

    Answer: The id of the first post of the thread, shared by every reply in it

    Every post carries the id of the post that started its thread, which is how reply edges are keyed by conversation and parent.

  6. Question 6 of 8 Post ids are ordered by time. What does that let the search index do?

    Choose one answer.

    Show the answer: Question 6 of 8

    Answer: Treat a slice of time as a range of ids, so segments cut by hour hold contiguous ids

    Time-ordered ids make "the last hour" a range of ids, which is what time-cut segments and cursors rely on.

  7. Question 7 of 8 The indexers slow down while fan-out keeps up. What do readers notice?

    Choose one answer.

    Show the answer: Question 7 of 8

    Answer: New posts appear in timelines on time but reach search late; each consumer reads the log at its own offset

    A consumer's position in a partition is a single offset that it can also move back to read again, which is what lets four features share one log without slowing each other.

  8. Question 8 of 8 In the model, why did the 20 newest posts with #final come from only the two newest of five segments?

    Choose one answer.

    Show the answer: Question 8 of 8

    Answer: The index is cut by time, so a newest-first search reads the newest segments first and stops when it has 20

    Cutting the index by time makes a Latest search cheap: the newest segments hold the newest posts, while a rare word may have to be looked for in all of them.

The deep dives this design must answer

  • The pull threshold: pushing costs writes, pulling costs a merge on every read.
  • Merge latency: every pulled timeline fetched in parallel is one more chance of a slow fetch.
  • The index: one writer appends to the newest segment while many readers search it.
  • Latest and Top: a newest-first search stops after k results; a ranked one needs bounds to skip segments.
  • Deletes and protected posts: both must vanish from search and timelines at once.
  • Partitions and tiers: recent hours sit in memory across partitions; older ones move to cheaper storage.
  • Conversations: a tree of thousands of replies is read a page at a time.
  • Trends: counted in windows, with coordinated campaigns filtered out.

Choosing the pull threshold from the long tail, with numbers Pro: opens with a Pro, Premium or Ultimate pass

The threshold decides where the cost of a large following lands: on the writes when a post is published, or on every read by every follower. This part prices six thresholds in a model of 400 million accounts and picks one against a write budget and a read budget.

In this part:

  • Pricing six thresholds on a power-law follower curve
  • Why parallel fetches set the read tail
  • Choosing against a write budget and a read budget, worked
  • Hysteresis for the accounts near the line, with numbers

1 diagram · 1 runnable example (Python) · about 11 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Read a complete sample first: Design a news feed

Search has to show a post seconds after it is written and still answer in tens of milliseconds. This part follows a post through the index, measures the two kinds of query, and sizes the partitions and storage tiers behind them.

In this part:

  • One writer, many readers: the append-only segment
  • Latest and Top, measured: stopping early and skipping segments
  • Deletes, edits and protected accounts
  • Partitions and replicas, sized with numbers
  • Tiers: merging old slices and moving them to cheaper storage

1 diagram · 2 runnable examples (Python) · about 14 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Conversations, trends, failure drills and the level rubric Pro: opens with a Pro, Premium or Ultimate pass

The last part pages a reply tree of 50,000 posts, connects trends to search, stress-tests the design at 10 × and 100 ×, and turns it into an answer for a design round.

In this part:

  • Paging a reply tree of 50,000 replies, measured
  • Trends next to timelines and search
  • Failure drills, from indexer lag to a lost partition
  • Bottlenecks at 10× and 100×
  • What each level is expected to cover
  • A sample answer and a 45-minute self-mock

1 runnable example (Python) · about 7 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Interview questions

  1. Fresher to mid · Warm-up

    Why is a new post not searchable the instant it is written, and what sets the delay?

    Because search reads an index, not the post store, and three waits stand between the two. The post travels through a log to the indexers, which takes milliseconds normally and seconds when they fall behind. The indexer adds it to the newest in-memory segment. Readers see it only after the next refresh, which makes everything appended so far searchable; Elasticsearch refreshes every second by default for indices that were searched recently. So the delay is the time in the log plus indexing plus up to one refresh interval. A shorter refresh lowers the median, but a slow log still sets the tail. When people must see their own new post at once, show it from the post store rather than from search.

  2. Senior to staff

    An account with 80 million followers posts during a cup final. Its followers open their home timelines within seconds, and its reply thread takes 40,000 replies a minute. Walk through the write and read paths and keep both under budget.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    During a final, search for the match hashtag must show posts within 10 seconds, but the indexers have fallen four minutes behind. Find the likely cause and restore freshness without losing posts.

    Model answer, Pro. First step and follow-ups

  4. Senior to staff

    A post has 2 million replies in 30,000 branches, and readers report repeated and missing replies while they scroll its conversation. Explain why, and design storage and pagination for the conversation view.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • One log of posts feeds four consumers: fan-out, search indexing, conversations and trends.
  • Search freshness is a budget: time in the log, indexing and the refresh interval add up.
  • Cut the search index by time, so that a newest-first search reads only the newest segments.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.