System design interview

Design a pastebin

Random keys, metadata and bodies apart, expiry, burn after reading and CDN-cached reads.

At a glance

Level
Foundation
Topics
Storage, Caching
Main parts
CDN · Paste service · Metadata DB · Object storage · Scan queue · Background worker
What you'll learn
Practise this problem
Overview
ClientCDNPasteserviceMetadata DBObjectstorageScan queueBackgroundworkerClientCDNPaste servicecreate and readMetadata DBObject storageScan queueBackgroundworker

Press Play to follow a request through the design, or step through it.

  • Client and edge
  • Service
  • Data store
  • Cache and queue
  1. The creator submits the text. The CDN passes writes straight through to the origin.
  2. The Paste service checks the size and the client's rate, then picks a random 8-character key.
  3. It stores the compressed body in object storage under that key. The body is never changed after this.
  4. It inserts the metadata row. A key clash fails the insert, so the service picks another key and tries again.
  5. It adds a scan job to the queue, so checking the content never slows down the create call.
  6. The service answers with the short link.
  7. The creator receives the link to share.
  • Client
  • CDN: caches paste views
  • Paste service: create and read
  • Metadata DB: key, expiry, status
  • Object storage: compressed bodies
  • Scan queue
  • Background worker: scan, expire, delete
On this page

In 2 minutes

Key ideas
  • The numbers. Traffic is small, but storage grows without end and nearly every read is a repeat.
  • Where bodies live. Object storage plus a metadata database.
  • Key generation. 8 random characters.
  • Expiry. A lazy check on read plus batch cleanup.
  • Content scanning. Asynchronous, after the link is returned.
Read the 2-minute answer

I would split a paste into two parts: a small metadata row and a large, immutable body. The row lives in a database with the key, owner, expiry, visibility and status. The body lives in object storage, compressed. The key is 8 random characters, with a unique constraint and a retry, so links cannot be guessed.

A CDN sits in front of reads. Because bodies never change, the CDN can cache them for as long as the paste has left to live, and a viral link barely reaches the origin. At 1 million pastes a day and 10 reads each, the origin sees about 116 reads a second at peak. Expiry is a lazy check on every read plus a batch worker that deletes bodies first and rows last.

Burn after reading is one conditional update, so exactly one reader wins, and a reveal screen stops link previews from burning it. A queue feeds a scanner that looks for leaked secrets and abuse, and a flagged paste is blocked and purged from the CDN. The main trade-off is a short window where a bad paste is live before the scan finishes, in exchange for fast, always-available creates.

Step 1 of 6: Clarify the scope

Questions to ask first

Ask these before you draw anything. Guess why each answer changes the design, then open it to check.

How large can one paste be, and is it text only?
Why it matters: A hard size cap bounds storage and abuse, and text-only content can be compressed and scanned.
How is a paste found: only by its link, or also by browsing and search?
Why it matters: Link-only access lets keys be random and unguessable. A public listing or search needs an index of its own.
Which expiry options exist, and may a paste live forever?
Why it matters: Expiry decides whether we need a cleanup job and how fast storage grows.
Is burn after reading required?
Why it matters: A one-view paste turns a plain read into a write that must be atomic, or two readers can both see it.
Can a paste be edited after it is created?
Why it matters: Immutable content can be cached for a long time with no invalidation, which is the biggest read-path win.

Requirements

Functional

  • Create a paste from text, with an optional title, language, expiry and visibility.
  • Return a short link, and show the paste to anyone who has it.
  • Expire pastes on time, and delete a burn-after-reading paste after its first view.
  • Let the creator delete a paste, and block pastes that break the rules.

Non-functional

  • Paste view: p99 under 200 ms, with 99.95% availability.
  • Durability: an accepted paste is never lost before it expires.
  • Paste size capped at 512 KB, and 20 new pastes a minute per client.
  • A deleted or expired paste is unreachable within 1 minute.
  • A link that goes viral adds almost no database reads.

Step 2 of 6: Size it

Show the maths

These numbers are assumptions for the exercise, not facts about a real system:

  • 1 million new pastes a day, and each paste is read 10 times on average, so 10 million reads a day.
  • The average paste is 10 KB and the cap is 512 KB. Text compresses about 3 to 1.
  • A metadata row (key, owner, times, size, flags, body location) takes about 200 bytes.
  • Peak traffic is 10 times the average, and a CDN answers 90% of reads from its cache.

The arithmetic:

Writes per second
1M ÷ 86,400 s ≈ 11.6
≈ 12/s, 120/s at a 10x peak
Reads per second
10M ÷ 86,400 s ≈ 116
≈ 116/s, 1,160/s at peak
Origin reads at peak
1,160/s × (1 − 0.9 cache hits)
≈ 116/s reach the service, a trickle
Body storage a year
1M × 10 KB = 10 GB a day; × 365 = 3.65 TB; ÷ 3 for compression
≈ 3.65 TB raw, ≈ 1.2 TB compressed
Metadata a year
1M × 200 B = 200 MB a day; × 365
≈ 73 GB, which fits on one database node
Key space
62^8 ≈ 2.2 × 10^14 keys; 10 years of pastes is 3.65 billion
≈ 0.002% of keys used after 10 years

Step 3 of 6: Sketch the path of one request

Step through the diagram at the top of the page to watch a request move through these parts.

Every request goes through a CDN. Reads of a paste it already holds are answered there and never reach our servers. Everything else passes to the Paste service, which creates and serves pastes.

To create a paste, the service checks the size and the client's rate, then picks a random 8-character key. It writes the compressed body to Object storage under that key, and a small row to the Metadata DB: owner, expiry, visibility, size and status. It also drops a scan job on the Scan queue and returns the link at once, so scanning never slows the create call.

To read, the service looks up the row to check that the paste exists, has not expired and is not blocked. It then fetches the body and returns it with cache headers. A Background worker takes scan jobs from the queue, and on a timer it deletes expired pastes: bodies first, then rows.

Other paths through the system

Read a paste

  1. A reader opens the link. The CDN looks for the paste in its cache, using the key.
  2. On a miss, the request reaches the Paste service.
  3. The service reads the metadata row to check that the paste exists, has not expired and is not blocked.
  4. It fetches the body from object storage.
  5. It returns the text with a cache header, so the CDN can keep a copy.
  6. The reader sees the paste. Later readers of the same link are served by the CDN alone.

Scan and expire

  1. The worker takes the next scan job from the queue.
  2. It reads the paste body and looks for leaked secrets and known abuse patterns.
  3. A bad paste is marked blocked in its metadata row, so the service stops serving it.
  4. On a timer, the worker also asks for rows whose expiry time has passed, using an index on expiry.
  5. It deletes the bodies of those pastes from object storage.
  6. Last, it deletes the rows. If it crashes in between, the next run repeats the same safe deletes.

Step 4 of 6: Go deep on the hard part

Metadata and bodies apart

InterviewerWhere do you store the paste text and its details, and how do you handle size and key choice?Split the paste into two parts that behave very differently.

Split the paste into two parts that behave very differently. The body is large, written once, never changed and read by key. The metadata is small, changes (status, expiry) and needs a unique key and quick lookups.

Object storage suits bodies: it is cheap per gigabyte, durable, and made for reading a whole object by name. A database suits metadata: a row holds the key, owner, created and expiry times, size, visibility, status and the body's location. At 200 bytes a row, a year of metadata is about 73 GB, so one primary with replicas is enough for a long time. Bodies go to a store that grows to terabytes without any work from us.

Enforce the size cap while the upload streams in, by checking Content-Length and cutting the stream at 512 KB. Do not read an unbounded body into memory first. Compress text with a fast algorithm such as gzip or zstd before storing it. Text shrinks about 3 to 1, and the CDN can pass the compressed bytes straight to browsers that accept them. Very small pastes, under a few kilobytes, can live in the metadata row to save a second round trip. That is an optimisation to add later, not a starting point.

Keys. Generate 8 random characters from a secure random source, using the 62 letters and digits. That gives about 2.2 × 10^14 keys, so a clash is rare, and a unique constraint on the key catches the rare one: the insert fails and the service tries another key. Do not use a counter. A counter makes links guessable, which would expose unlisted pastes.

Write order. Store the body first, then insert the row. A crash between the two leaves an orphan body that no row points to, which is harmless and found later by a sweep. The reverse order would leave a row that points at nothing, and readers would see errors.

Expiry and burn after reading

InterviewerHow do you expire pastes, and how do you make a paste vanish after exactly one view?Store an expiry time on each row.

Store an expiry time on each row. Expiry is then enforced twice, for two different jobs.

  • Lazy check. On every read, compare the expiry time with now. An expired paste returns "gone" even if the cleanup has not reached it yet. This gives correctness.
  • Batch cleanup. A worker runs every few minutes, finds rows past their expiry through an index on the expiry column, and deletes them in batches of about 1,000. It deletes the body first and the row last, and every step is safe to repeat. This gives the storage back.

A scan of the whole table would not scale, so the index matters. For a fixed menu of expiries, such as 1 hour, 1 day and 1 week, the object store's own lifecycle rules can remove bodies by age, with the database sweep removing rows.

Burn after reading. Two readers who open the link at the same moment must not both see the paste. Make the read a claim: one conditional update that sets the status to burned only where the status is still active, and returns the body location. Exactly one request gets a row back. The others see no row and get "gone". The winner fetches the body and returns it, and the worker deletes the data later.

Three details make this work in practice. First, a burn paste must be sent with a no-store cache header, or the CDN will serve a second reader from its copy. Second, many chat apps fetch a link to build a preview, and that fetch would burn the paste before the person arrives. So the first page is a small "reveal" screen, and only the button on it makes the claiming request. Third, if the winner's connection drops after the claim, the paste is lost. That is the defined behaviour of burn after reading, and the page should say so before the reader reveals it.

Fast reads, private pastes and abuse

InterviewerHow do you serve reads cheaply, keep some pastes private, and deal with abuse and leaked secrets?CDN reads. A paste never changes, so the URL for a key always returns the same bytes.

CDN reads. A paste never changes, so the URL for a key always returns the same bytes. That makes it ideal for a CDN: send a public cache header and the CDN keeps the copy close to readers. Set the cache lifetime to the smaller of the paste's remaining life and a fixed ceiling such as one hour, so a cached copy never outlives the paste. At a 90% hit rate, about 116 of 1,160 peak reads a second reach the origin. A viral link is cheaper still, because the CDN collapses simultaneous misses for one key into a single origin request.

Deleting early. A creator's delete or an abuse takedown removes the row, then calls the CDN's purge by key. The purge is what meets the target of unreachable within a minute. Without it, a short cache lifetime would be the only protection.

Visibility. There are three levels. A public paste can be listed. An unlisted paste is reachable by anyone with the key, and its 8 random characters are the secret. A private paste needs sign-in and a check that the viewer is the owner. Private responses carry a private cache header so shared caches never store them. Slow down guessing by rate limiting repeated not-found answers per client.

Abuse. Pastebins attract spam, malware links and accidentally leaked credentials. Scan after the create call returns, using the queue. Look for known secret formats, such as private key headers and cloud access keys, plus bad links and repeated spam text. A flagged paste is marked blocked, which stops serving it at once, and the CDN copy is purged. Per-client create limits and a report button cover the rest.

Serving safely. Return text as plain text, with a no-sniff header, from a separate domain from the main site. Then a paste containing script can never run with the site's cookies. Add a no-index header to keep spam pastes out of search results.

Step 5 of 6: Name the trade-offs

Where bodies live
Chose
Object storage plus a metadata database
Over
Everything in one database table

Bodies are large, immutable and read by key, so a cheap, durable store fits them. The cost is two systems to run and an orphan sweep for crashes between the two writes.

Key generation
Chose
8 random characters
Over
A counter encoded as a short string

Random keys cannot be guessed, so unlisted pastes stay unlisted. The cost is a unique-key check on insert and a rare retry.

Expiry
Chose
A lazy check on read plus batch cleanup
Over
A timer per paste

Reads are always correct and cleanup is cheap and restartable. The cost is that storage is freed minutes late, not instantly.

Content scanning
Chose
Asynchronous, after the link is returned
Over
Scan before returning the link

Creating stays fast and a scanner outage does not stop writes. The cost is that a bad paste can be live for a short time before it is blocked.

Step 6 of 6: Say what breaks next

Follow-up questions an interviewer asks once the design is on the board. Answer each one out loud, then open it to compare.

How would you support editing a paste?
Keep bodies immutable and treat each edit as a new version with its own key, then point a stable link at the latest version. Old versions stay cacheable and reachable for history. This keeps the CDN simple because no cached object ever changes.
How would you add search over public pastes?
Index only public pastes, and do it asynchronously from the same queue so creating stays fast. A separate search index holds the text and the language. Unlisted and private pastes are never indexed.
How do you handle a legal takedown?
Mark the row blocked first, which stops serving at once, then purge the CDN and delete the body. Keep a small record of the key and the reason, without the text. Backups age out on their normal schedule.
What changes if pastes can be up to 100 MB?
Uploads would go straight from the client to object storage with a signed URL, in resumable parts, so the service never carries the bytes. Scanning becomes slower and costlier, so large pastes might be scanned in the background and held as unlisted until clean. The CDN would also need range requests.

Free ATS check

See your resume the way an ATS reads it, then fix what it misses.

Drop in a PDF for a score out of 100, every fix that cost a point, and the line it belongs to. It runs in your browser, with no signup for the score.