Design Paste bin Interview Questions | JiQuest

add

#

Design Paste bin

System design deep dive · HLD

Design Pastebin: full high-level design.

Requirements, back-of-envelope capacity estimation, a high-level design diagram, a deeper deployment architecture diagram, sequence diagrams for paste creation-with-expiry and retrieval-with-view-tracking, and an entity-relationship diagram for the data model - with the reasoning an interviewer expects behind every box and arrow.

8M/dayNew pastes created
30:1Read : write ratio
512KBMax paste size (inline vs blob)
Editorpaste text, set expiry Paste servicesplits meta/content Metadata DBid, expiry, visibility Blob storelarge paste content CDN / cachehot pastes only above inline threshold

1. Clarify requirements before drawing any box

A strong system design answer starts by pinning down scope out loud. For Pastebin, that means separating this from a URL shortener up front: the interesting problems here are content size variance, expiry-driven storage lifecycle, and view tracking - not redirect mechanics or hash collisions.

Functional requirements

Create a pasteAccept arbitrary text (with an optional syntax-highlighting language hint), an optional expiry, and a visibility level.
Visibility levelsPublic (listed/searchable), unlisted (accessible only via its exact URL), or private (requires the owner's login to view).
Retrieve a pasteGiven a paste's short ID, return its content (if not expired and the viewer is authorized) and record a view.
ExpiryA paste can be set to expire after a fixed duration (10 minutes, 1 day, 1 week, never) after which it is no longer retrievable and its storage is reclaimed.

Non-functional requirements

Read-heavy at high skewA small fraction of pastes (shared in a chat or forum) get most of the traffic; most pastes are read a handful of times or never again.
Size variancePaste sizes range from a one-line snippet to multi-megabyte log dumps; the storage design must not penalize the common small case for the rare large one.
Prompt expiry enforcementAn expired paste must become genuinely inaccessible quickly, not just hidden from a listing while still fetchable by direct URL.
Reasonably low read latencyFetching a paste should feel instant (well under 200ms) since pastes are frequently opened directly from a shared chat link.
Explicitly out of scope Real-time collaborative editing of a paste, version history/diffs across edits, and full-text search across all public pastes are called out as extensions rather than core requirements, so the core design stays focused on create-with-expiry-and-visibility, retrieve-with-view-tracking, and the metadata/blob storage split.

2. Back-of-the-envelope capacity estimation

These numbers decide the inline-vs-blob-storage size threshold, how aggressively expired data can be reclaimed, and whether the metadata table alone (without content) is small enough to keep entirely cache-warm.

MetricAssumptionResulting estimate
New pastes (writes)8 million/day~93 writes/sec average, ~800 writes/sec peak
Paste retrievals (reads)30:1 read:write ratio~2,800 reads/sec average, ~24,000 reads/sec peak
Paste size distribution90% under 10KB, 9% under 512KB, 1% up to a few MBMedian paste is small enough to store inline with metadata; the 1% tail drives the blob-store need
Storage growth~8M/day × ~8KB average (blended) × 90-day median retention~5.7 TB live at any time before expiry reclamation, growing more slowly than raw write volume thanks to expiry
Cache size for hot pastes~5% of daily pastes account for ~70% of reads (viral shares)~400K hot pastes/day × ~8KB ≈ 3.2 GB - comfortably fits a Redis/CDN cache tier
Why this matters The size-distribution number (90% under 10KB) is what justifies storing small pastes' content directly alongside metadata in the database rather than always forcing a second blob-store round-trip - paying for a separate storage tier only for the minority of genuinely large pastes, which is the core storage-design decision this system is built around.

3. High-level design (HLD)

The HLD names the major components and the one-directional data flow between them, without committing yet to specific infrastructure, regions, or replica counts - that level of detail belongs in the architecture diagram in the next section.

Clientweb / API / CLI API gatewayauth, rate limit Create servicewrite path Retrieve serviceread path ID generator Metadata cacheid, expiry, visibility Blob storelarge paste bodies View-count queueasync increments Expiry reaperasync cleanup
Stateless servicesFast-path infraDurable storageAsync / edge

What each box owns

Create service (write path)

Accepts the paste content, visibility, and expiry choice; requests a globally unique short ID; decides inline-vs-blob storage purely on content size against the threshold (512KB in this design); writes metadata (and inline content, if small) to the metadata store, or writes large content to the blob store first and records only a reference in metadata. Visibility is enforced at write time by generating an unguessable ID for unlisted/private pastes rather than a short sequential one, since "unlisted" security depends entirely on the ID being unguessable.

Retrieve service (read path)

Looks up metadata by ID first (cache, then DB), checks expiry and visibility/auth before returning anything, then either returns the inline content directly or fetches the referenced blob. It publishes a "view happened" event to the view-count queue and returns without waiting for that event to be processed, exactly like the click-tracking pattern in a URL shortener - view analytics must never add latency to a paste being read.

ID generator

For public pastes, a short Base62 ID derived from a Snowflake-style unique numeric ID (cheap to guess, but that's fine - public pastes are meant to be discoverable). For unlisted/private pastes, a longer cryptographically random ID (128+ bits of entropy) is generated instead, since "unlisted" is a security property that depends entirely on the ID space being too large to enumerate or guess.

Metadata cache/store, blob store, view-count queue, and expiry reaper

The metadata store holds every paste's id, owner, visibility, expiry, and either inline content (small pastes) or a blob reference (large ones) - kept small and cache-friendly since it's on every read's critical path. The blob store holds only large paste bodies, addressed by content hash, and is never consulted for the 90% of pastes that fit inline. The view-count queue batches increments asynchronously rather than writing on every single read. The expiry reaper runs as a background job that deletes expired metadata rows and their associated blobs, reclaiming storage without needing every read to double as a cleanup check.

4. Detailed architecture diagram

The architecture diagram takes every HLD box and answers "how is this actually deployed?" - replica counts, sharding, regions, and the specific technology choice, which is what an interviewer is checking for once they've accepted the high-level shape.

Edge layer CDN (edge-cached pastes) API gateway + L7 LB Rate limiter (anti-abuse) Malware/secret scannerasync, post-write Region: us-east-1 Create svc ×6 pods Retrieve svc ×14 pods Redis cluster (metadata, 6 shards) Snowflake ID svc ×3 Region: eu-west-1 (active-active) Create svc ×4 pods Retrieve svc ×10 pods Regional Redis clustercross-region invalidationvia pub/sub Metadata store Shard 0-3 Shard 4-7 sharded by hash(paste_id), inline content up to 512KB Blob storage tier Object storage (S3-class) Lifecycle policy auto-delete on expiry timestamp match View analytics pipeline Kafka topic → batched counters owner-facing view-count dashboard
DecisionChoiceReasoning
Inline vs blob threshold512KB, chosen from the size-distribution estimateKeeps 90%+ of pastes servable from a single cache/DB lookup with no second blob-store round-trip, while still handling multi-megabyte log dumps without bloating the hot metadata table.
Blob expiry enforcementObject storage lifecycle policy, not an application-level delete job aloneBelt-and-suspenders: the storage layer itself guarantees reclamation even if the application-level expiry reaper has a bug or an outage.
Unlisted/private paste ID entropy128+ bits of cryptographic randomness, distinct from the short public-paste ID scheme"Unlisted" is a security property enforced purely by the ID being unguessable; a short, dense ID space would make brute-force enumeration of private content plausible.
Malware/secret scanningAsynchronous, post-write, not blocking paste creationScanning for leaked credentials or malicious payloads must not add latency to every paste creation; instead it flags/removes content shortly after write, accepting a brief window of exposure as the trade-off.

5. Sequence diagrams for the two critical flows

A sequence diagram is where an interviewer checks whether you actually understand call order, what is synchronous versus fire-and-forget, and exactly where the inline-vs-blob branch happens.

5.1 Create paste with expiry and visibility (write path)

Client Create svc ID generator Metadata store Blob store 1. POST /paste {content=2.1MB, expiry=1d, visibility=unlisted} 2. requestId(entropy=high) 3. return random 128-bit id 4. size 2.1MB > 512KB → blob path 5. PUT content, addressed by content hash 6. INSERT metadata{id, blob_ref, expiry, visibility} 7. ack 8. 201 Created {unlisted_url}

Step 4's branch is the single most important decision point in the whole write path: a small paste would instead write its content directly into the metadata row in step 6 and skip the blob store entirely, saving a network round-trip for the 90%+ common case. Step 2 requests high-entropy ID generation specifically because this paste is unlisted - had visibility been public, a cheaper, shorter Base62-encoded sequential ID would be requested instead, since guessability isn't a concern for content the owner wants discoverable.

5.2 Retrieve paste with view-count tracking (read path)

Client Retrieve svc Metadata cache Blob store View queue 1. GET /p/9kQ2f... 2. GET metadata by id 3. hit: {expiry, visibility, blob_ref} 4. check not expired; check visibility/auth 5. GET content by blob_ref 6. publish view event (fire-and-forget) 7. 200 OK, paste content + syntax hint alt: expired or unauthorized → 404/403, no blob fetch

Step 4's expiry and visibility check happens before ever touching the blob store, so an expired or unauthorized request is rejected cheaply without an unnecessary storage read. Step 6's fire-and-forget view event is the same pattern as a URL shortener's click tracking - view-count accuracy is valuable to the paste's owner but must never gate whether the reader actually sees the content, so the response in step 7 does not wait for it.

6. Entity-relationship (ER) diagram and schema

The data model has to answer three questions: where does small content live versus large content, how is view volume recorded without write-amplifying the hot metadata row, and how is the inline/blob split actually represented.

pastes PK id VARCHAR owner_id BIGINT NULL visibility ENUM inline_content TEXT NULL FK blob_id BIGINT NULL syntax_lang VARCHAR created_at TIMESTAMP expires_at TIMESTAMP NULL paste_content_blobs PK id BIGINT content_hash VARCHAR storage_ref TEXT size_bytes BIGINT paste_views PK id BIGINT FK paste_id VARCHAR viewed_at TIMESTAMP N1 1N many pastes may reference one dedup'd blob (by content hash); one paste has many paste_views

Key modeling decisions

inline_content and blob_id are mutually exclusive, nullable columnsExactly one is populated per row depending on size at creation time - the schema itself documents the inline/blob branching decision from the sequence diagram.
paste_content_blobs is keyed by content_hash, enabling dedupTwo users pasting the same large stack trace or log file share one physical blob; multiple pastes rows can reference the same blob_id, saving storage on duplicate content.
paste_views is append-only and never joined on the hot read pathThe retrieve service only ever writes to it asynchronously (sequence 5.2, step 6); reading aggregate view counts is a separate, lower-priority query path for the owner's dashboard.
NoSQL alternativeA key-value store keyed on paste id (DynamoDB with TTL support) elegantly handles expiry natively via the store's own TTL feature, removing the need for a separate expiry-reaper job entirely.
Storage choiceUse whenWatch out for
Relational (Postgres/MySQL), sharded by hash(paste_id)You want inline content and metadata in one row for a single-lookup read, plus straightforward joins for an owner's paste-list dashboard.Very large inline_content values (near the 512KB threshold) bloat row size and index locality; the threshold must be enforced strictly.
Key-value with native TTL (DynamoDB)Expiry is a first-class requirement and you want the storage layer itself to reclaim expired items without a custom reaper job.Content-hash-based blob dedup and cross-paste aggregate queries (e.g. "most-viewed public pastes today") are harder to express than in a relational model.

7. Deep dives interviewers actually probe

How do you decide the inline-vs-blob storage threshold, and does it ever change?

The 512KB threshold is chosen from the size-distribution data (capacity section): it's set high enough to keep the vast majority of pastes as a single fast lookup, but low enough that the metadata table's row size stays predictable and cache-friendly. It is intentionally a config value, not a hardcoded constant, since a platform's actual paste-size distribution can shift over time (e.g. more log-dump-style pastes as it gains adoption in an ops-tooling context) and the threshold should be re-tuned from real percentile data periodically rather than fixed at launch.

How is "unlisted" actually enforced - is it real security or just obscurity?

It is explicitly security-through-obscurity, and the design should say so plainly rather than imply otherwise: unlisted pastes rely entirely on the ID being computationally infeasible to guess (128+ bits of randomness, never listed or indexed anywhere, never appearing in a sitemap or search-engine-crawlable page). This is materially weaker than "private" (which requires actual authentication) and that trade-off should be surfaced to users - an unlisted paste's URL, if it ever leaks via a referrer header, browser history sync, or being pasted into a public chat, becomes effectively public.

// Unlisted/private IDs use real entropy; public IDs can be short and guessable by design
String unlistedId = base62Encode(secureRandomBytes(16)); // ~128 bits
String publicId   = base62Encode(snowflakeId());          // dense, sequential-ish, fine to guess

How does expiry actually get enforced end-to-end, not just "hidden from listing"?

Two independent layers, deliberately redundant: the retrieve service checks expires_at against the current time on every read regardless of cache state (so even a stale cache entry can't serve expired content past its expiry instant), and a background reaper plus the blob store's own lifecycle policy physically delete the underlying data afterward. The read-time check is what guarantees expired content is genuinely inaccessible immediately, independent of how promptly the asynchronous cleanup job gets around to reclaiming its storage.

How do you handle abuse - malware droppers, leaked credentials, or C2 payloads pasted as "plain text"?

An asynchronous post-write scanner (architecture diagram) checks new pastes against known-secret patterns (API key formats, private key headers) and malware-signature/URL-reputation databases, flagging or removing matches shortly after creation - deliberately not blocking creation synchronously, since scanning against large signature databases is too slow to sit on the write's critical path. This accepts a short exposure window as the trade-off for keeping paste creation fast for the overwhelming majority of legitimate pastes.

What is the single biggest bottleneck as this scales 10x?

Not the read path - cache hit rate and the CDN layer keep that cheap and horizontally scalable. The real bottleneck becomes blob-store write and reclaim volume for the large-paste tail: at 10x scale, even a small percentage of multi-megabyte pastes adds up to serious storage churn, and expiry reclamation has to keep pace with creation or storage grows unbounded. This is solved by making sure the expiry reaper (and the object store's native lifecycle policy) scales independently as a background batch process, rather than trying to squeeze cleanup into the synchronous request path anywhere.

8. Summary: what a strong answer covers

Clarified scope before designingJustified every number with a calculationInline vs blob split driven by real size distribution Named unlisted-visibility as obscurity, not real access controlTwo independent layers enforcing expiryAsync, non-blocking view tracking
Interview tip When asked to design Pastebin, the strongest signal is treating it as a distinct problem from a URL shortener - the interesting engineering is the size-driven storage split and expiry lifecycle, not redirect mechanics - and being honest that "unlisted" is obscurity-based security, not a substitute for real authentication.
No comments
Leave a Comment