System-design interview · Core interviews
Design a URL shortener
Build a durable code-to-URL service, then justify uniqueness, retries, caching and storage growth from one concrete redirect.
You will learn to
Practice in this chapter
8 interview questions with model answers and follow-ups.
Go to interview practiceUseful foundations: HTTP APIs and request lifecycle · Caching: cache hits, misses, write policies and invalidation · Idempotency, retries, and timeouts
Workload and timing examples are interview assumptions.
Dotted concept links open the relevant explanation in a new tab.
01What the short link actually does
Someone prints an event poster containing a registration address. The original address is long, so our service gives them https://s.example/q7Lm2Ax9. When another person opens that address, the browser should reach the original registration page. The service stores a code-to-destination mapping; it does not compress or host the destination page.
For this interview, support creating a link, resolving it, choosing an optional custom alias, expiring it and deleting it as its owner. Destinations do not change after creation. Redirects are public; creation and owner actions require authentication. Click statistics may arrive late. Private links, billing-grade counts and immediate worldwide revocation are separate requirements to discuss if requested.
Two rules matter immediately. A code must never resolve to another creator's destination, and a successful creation must have a durable stored mapping. An unavailable destination is outside our service: we can return the correct redirect even when the event website is down. Ask how quickly deletion must take effect, because that decision later determines whether cached redirects are acceptable.
02Functional requirements
Agree on these supported actions before selecting components.
Create and manage links. Authenticated owners create immutable destination mappings, optionally request a custom alias and expiry, list their own links, and delete them.
Resolve a public code. An active code returns an HTTP redirect to its saved destination. Missing or expired links return an unavailable-link result; deletion follows the cache-freshness policy below. The destination page is fetched by the browser.
Recover a repeated creation. Repeating the same owner-scoped request and payload returns the same link during the supported retry window. Conflicting aliases or changed payloads are rejected.
Record lightweight statistics. Collect approximate click statistics asynchronously; delayed or lost analytics must not change the redirect result.
03Non-functional requirements
Use these as illustrative interview assumptions to agree with the interviewer. Numerical targets require measurement; they are not claims about an existing product or a proven implementation. p95 (the 95th percentile) means 95% of measured operations finish within the stated time.
Workload. Plan for approximately 965 creates/s and 96,450 redirects/s at peak, with 30 billion claimed codes after five years. These are the illustrative assumptions derived below.
Response time. Target p95 of 50 ms for redirects and 200 ms for creation, measured from regional service ingress to response at the planning peak under normal operation. Internet transit and destination-site loading are outside these measurements.
Durability and availability. A successful creation must survive an application restart or one database-node failure within the serving region. Unsafe writes stop rather than acknowledge an unprotected mapping; regional disaster recovery is a separate requirement.
Identity and retries. Codes never change owners or destinations, including after deletion. Retain creation results for at least 24 hours so a matching retry within that window cannot create another link.
Cache freshness. Expiry is checked on every serving path. Deletion has eventual visibility with best-effort invalidation and one-minute internal cache entries; this is not a hard global one-minute revocation guarantee.
Access and abuse. Owner actions require current authentication and ownership checks. Limit creation abuse and accept only supported destination schemes; a public random code is not a private-access credential.
04Follow one creation and one visit
Begin with one application and one relational database. The application receives a destination URL, generates a candidate code and inserts a row containing that code and destination. The code column has a unique constraint, which means the database rejects a second row with the same code. After the transaction commits, the application returns the short URL.
The visit follows three steps:
- The browser requests
/q7Lm2Ax9. - The application looks up that exact code, checks that the row is active and unexpired, and returns HTTP
302with the destination in theLocationresponse header. - The browser makes a second request to the destination website.
The shortener's response contains a header and perhaps a small body; it does not carry the destination page's images or video.
This is already a complete useful service. A missing or expired code returns an unavailable-link response rather than an invented destination. One indexed lookup can serve each redirect. A database transaction publishes a complete mapping before the creator receives success. Next, use the workload to decide when lookup cost or storage growth justifies more components.
The browser contacts the destination after receiving Location; destination content does not pass through the shortener.
Read each connection in order
- syncCreate or visit codeCreator or visiting browser → Short-link application
- syncTransaction or code lookupShort-link application → Mappings and request results
- syncShort URL or 302 LocationShort-link application → Creator or visiting browser
- syncFollow redirectCreator or visiting browser → Destination website
05Estimate the work that could outgrow the baseline
Assume 500 million creations in a 30-day month and 100 visits per creation. These are interview assumptions, not traffic measured from a named company. There are 2,592,000 seconds in that month, giving about 193 creates and 19,290 redirects per second on average. With a fivefold planning peak, test approximately 965 creates and 96,450 redirects per second.
| Quantity | Calculation | Design implication |
|---|---|---|
| Five-year mapping count | 500M × 12 × 5 = 30B | Retention eventually requires substantial storage |
| Logical mapping bytes | 30B × 500 B = 15 TB | Replicas, indexes and backups add to this |
| Peak redirect payload | 96,450 × 500 B ≈ 48.2 MB/s | Estimate our redirect bytes, not destination-page bytes |
| Illustrative cache | 10M distinct entries × 700 B = 7 GB | Size by unique stored entries, not request count |
The calculations identify a read-heavy lookup service. They do not prove that every mapping needs caching or that SQL cannot work. Measure indexed lookup capacity and the distribution of repeated visits. A small number of frequently visited codes can dominate requests even when most stored links are rarely read. That observation motivates a cache more directly than the total row count does.
06Make creation, retry and ownership explicit
The creator sends a destination and optional expiry with an owner-scoped request key.
Creation request
POST /v1/links
Idempotency-Key: create-204
Content-Type: application/json
Example request body
{
"destination": "https://events.example/registration/design-day",
"expiresAt": "2030-12-31T23:59:59Z"
}
Idempotency means retrying this same logical request returns its existing result rather than creating another link. Store the key under the authenticated owner and compare the supplied payload before replaying a result. Reusing it with different input is a conflict.
| Request | Meaning |
|---|---|
POST /v1/links |
Create a mapping; return its code after commit |
GET /q7Lm2Ax9 |
Return the stored destination in a redirect |
DELETE /v1/links/q7Lm2Ax9 |
Owner marks the link deleted |
GET /v1/links?after=<cursor> |
Page through the owner's links |
For a custom alias such as design-day, return a conflict if somebody already owns it. Do not silently generate another alias after the caller asked for an exact one. Validate allowed URL schemes and length; preserve the destination's encoded meaning rather than casually rewriting it.
A creation timeout is an unknown outcome: the database may have committed before the response disappeared. The client retries create-204 or retrieves its status. A request key identifies an operation; two intentionally separate creations may legitimately target the same destination and keep different statistics.
07Store the mapping and the answer to a retried request
Use two records so a redirect mapping and its creation result have distinct purposes.
| Record | Fields | Purpose and uniqueness |
|---|---|---|
Link |
code, ownerId, destination, createdAt, expiresAt, deletedAt |
Answers a redirect lookup; code is unique. |
Request |
ownerId, requestKey, payloadHash, code |
Remembers which result belongs to a creation request; (ownerId, requestKey) is unique. |
In the baseline, insert both within one database transaction so neither becomes visible alone. If concurrent copies of one request choose different candidate codes, the request-key constraint lets only one transaction commit; the loser rolls back its tentative mapping and returns the winner’s saved result. A code collision from an unrelated request instead chooses another candidate.
The main lookup uses the code's primary-key index. Owner listing instead needs an index ordered by (ownerId, createdAt, code). A pagination cursor records the last time-and-code pair; the code breaks ties between links created at the same timestamp. These are different access patterns, so a fast code index does not automatically make owner listing efficient.
Never reuse a previously issued code for unrelated content. Somebody may still have an old poster or bookmark after the original link expires. Retain a compact claim or deletion marker even if old destination bytes are removed. This consumes some permanent identity storage but avoids giving an old URL a new owner. A scheduled cleanup job can reclaim expired payloads; reads must check expiry independently because that job may run late.
08Generate candidates; let storage decide uniqueness
Eight characters drawn from digits and upper- and lowercase letters give 62^8, about 218 trillion possible codes. With 30 billion permanently claimed codes, approximately 0.0137% of the space is occupied. A random candidate is therefore likely to be unused, but random generation never proves uniqueness.
Suppose two application servers both choose q7Lm2Ax9. Each tries an insert against the same unique code constraint. One succeeds; the other sees a collision, chooses a new random candidate and retries. A prior lookup saying “absent” would not be enough: both servers could read that answer before either writes.
A sequential number encoded in base 62 is another option. It avoids random collision retries but needs a safe allocation mechanism and produces predictable identifiers. A separate service can allocate batches, although that adds another component and failover responsibility. At the assumed creation rate, random candidates plus enforced uniqueness are a defensible starting choice.
Keep successful request results long enough for the documented client retry window. After that window, clients need explicit status recovery or a new intentional operation; do not promise indefinite retry recovery while discarding the record that makes it possible.
09Add a cache for repeated visits, then distribute storage
If measured database capacity is below the redirect peak, place an internal cache in front of lookups. A redirect first checks the cache; a miss reads the database and stores the result. Let simultaneous misses for one code share one database lookup, so a viral link does not trigger hundreds of identical cache refills. LRU eviction removes entries that have not been used recently when memory is full; it is a starting policy to test against actual traffic.
Caching creates a visibility tradeoff. A cached destination may remain after its owner deletes the database row. For this worked design, use fixed one-minute cache entries and best-effort invalidation when deletion commits. This gives eventual deletion visibility, not a hard worldwide one-minute deadline: a delayed old read can refill the cache after invalidation. State that limitation explicitly. Strict deadlines require the advanced freshness protocol. Send Cache-Control: no-store on browser redirects so the browser does not retain an uncontrolled redirect after our internal entry expires.
Replicated database storage protects against configured failures; application replicas allow independent request handling. Add partitions when retained bytes or measured throughput justify them. Hashing the code distributes distinct mappings, but one viral code remains one hot key and needs replicated cache copies. For the scaled worked design, choose a distributed SQL store that supports the same atomic mapping/request transaction across its partitions. This preserves the retry rule while adding distributed-transaction latency and operational cost, which must fit the measured budget. A separate cross-partition reservation workflow is an Advanced alternative; splitting the tables without either mechanism would lose the creation guarantee.
For this failure target, configure the selected SQL store to acknowledge mapping/request commits only after a durable majority of three replicas in independent failure domains within one region. Verify that its failover preserves those commits; merely enabling an asynchronous replica does not meet the requirement.
A visit checks the internal cache before reading a mapping. Creation writes the mapping and request result through one distributed SQL transaction. The browser follows the returned Location itself; internal cache invalidation gives the eventual deletion behavior described above.
Read each connection in order
- syncCreate or visitCreator or visiting browser → Replicated short-link apps
- syncLookup / refill / invalidateReplicated short-link apps → Internal redirect caches
- syncCreate transaction / cache-miss readReplicated short-link apps → Replicated SQL partitions
- returnShort URL or 302 (no-store)Replicated short-link apps → Creator or visiting browser
- syncFollow LocationCreator or visiting browser → Destination website
10Explain a lost response and a lost cache
The creation transaction commits Link(q7Lm2Ax9) and the result for u17/create-204, then the application crashes before replying. On retry, the service finds the saved request result and returns the original short URL. The user receives one logical link even though the network carried two attempts. If the crash happened before commit, neither row is committed and the retry may perform the creation.
A cache failure produces a different problem: extra database load. At a measured 95% cache-hit rate, a peak of 96,450 redirects/s sends about 4,823 reads/s to storage. Losing the cache can send almost the entire peak there, roughly twenty times more. Limit concurrent database fallbacks and return a retryable error when that budget is exhausted. An unlimited queue merely turns overload into long waits and memory exhaustion.
For database failover, acknowledge writes only under the chosen durability policy and direct writes to the current database leader. Read replicas can be useful, but lag can hide a newly created link or retain a deleted one. Explain which reads may tolerate that delay. Backups address accidental deletion and larger disasters; replication is not a substitute for testing a restore.
The request key retrieves the committed result instead of producing another link.
Read each connection in order
- syncCreate with key create-204Creator → Application
- syncCommit mapping and request resultApplication → Database
- returnCommitted q7Lm2Ax9Database → Application
- blockedResponse lostApplication → Creator
- syncRetry create-204Creator → Application
- syncRead saved request resultApplication → Database
- returnReturn the same short URLApplication → Creator
11Protect the service and measure the user experience
Public short links attract spam, malicious destinations and enumeration. Rate-limit creation by authenticated account and apply separate read-abuse controls. A hard-to-guess code is not authorization for private content. If threat scanning is required, run outbound fetching in an isolated service with destination restrictions; the redirect worker should not fetch arbitrary URLs into the application network.
Measure creation and redirect latency separately, cache-hit rate, database fallback load, collision retries, unavailable-link responses and replication or recovery lag. Test a viral link and total cache loss rather than only uniform random traffic. Watch deletion behavior as well as raw availability: quickly returning an invalid link is not a successful user outcome.
Emit click statistics asynchronously so a popular link does not update one contested counter on every request. Under the chosen approximate contract, events may be delayed or lost within a documented buffer policy. Keep personal click data minimal. Cost is driven by retained mapping/index bytes, replicated cache memory, lookup work and the small redirect responses. The target website's delivery bill is not ours.
12Check the design against the requirements
Use the agreed lists to check the finished design. The tests below still need to establish the targets; a proposed mechanism is not a measured result. FR refers to the numbered functional requirements above; NFR refers to the numbered non-functional requirements.
| Requirement | Design mechanism | Validation and remaining limit |
|---|---|---|
| FR1–3: creation, aliases, listing and retry | Unique code and owner/request constraints; atomic mapping/result commit; owner/time index. | Race two alias claims and lose a create response; recover one result within 24 hours. Check cross-owner listing isolation. |
| FR2 + NFR2: correct fast redirect | Indexed code lookup and replicated internal caches; browser follows Location. | Load-test the 96,450/s peak with hot keys and cache loss. Measure p95; the diagram alone does not establish 50 ms. |
| NFR3: survive one database-node failure | Synchronous replicated commits, with unsafe writes refused. | Kill a database node after acknowledged creation and confirm the mapping and retry result remain. Regional loss is not covered. |
| NFR5–6: expiry, deletion and ownership | Serving-time expiry and owner checks; no-store browser redirects; best-effort cache invalidation. | Test expired cache entries and delayed refill after deletion. State the remaining eventual-deletion limit. |
| FR4: approximate statistics | Bounded asynchronous click processing. | Interrupt analytics and verify redirects continue; report count delay or loss rather than claiming exact totals. |
13Rapid revision
Remember: Randomness supplies candidates; the unique insert prevents two owners from claiming one code.
| Question to answer | Interview answer | Limit to state |
|---|---|---|
| What does a visit do? | Look up the code and return 302 with Location; browser requests the target | We do not host the target page |
| What makes a code unique? | Atomic unique insert, with random retry on collision | Large namespace alone is insufficient |
| What happens after a lost create response? | Return that owner’s saved request result | Commit mapping and result together, or recover partial work |
| Why cache? | Repeated visits avoid database reads | Cached deletes lag; cache failure can overload storage |
| Why partition? | Spread mappings and requests across partitions | Popular codes and owner listings need separate handling |
| Why separate cleanup? | Delete expired records outside redirect handling | Reads still enforce expiry |
| What can be approximate? | Delayed click statistics | Code ownership and destinations must be correct |
A concise spoken answer should follow the browser through one creation and one visit, justify the unique constraint, use the traffic estimate to introduce caching, and then explain what happens when the cache or response disappears. Name stronger revocation and cross-partition retry requirements as follow-up work rather than silently claiming they are solved.
Practise the interview questions
Say your answer aloud before opening the model answer. Then answer the follow-up and compare the reasoning.
Does the shortener download the destination page?
Reveal a model answer
No. It returns a redirect response containing the destination URL. The browser then contacts that website separately.
Interviewer follow-up
Why does this matter for estimates?
Reveal the follow-up answer
Count redirect response bytes in our egress, not the destination page or its media.
What the answer must demonstrate: Separates the redirect response from the browser’s destination request.
Why is a large random code space not enough?
Reveal a model answer
Two requests can still generate the same candidate. The database’s unique constraint accepts only one competing insert; the loser chooses another code.
Interviewer follow-up
Why not check first?
Reveal the follow-up answer
Both requests can observe absence before either inserts, so a check followed by an unguarded write races.
What the answer must demonstrate: Names an atomic uniqueness constraint and the check-then-write race.
How does a retry avoid creating a second link?
Reveal a model answer
Scope a request key to the authenticated owner and store its payload identity and result atomically with the mapping. Replay a matching completed request.
Interviewer follow-up
What if the payload differs?
Reveal the follow-up answer
Reject the reused identity as a conflict; it represents a different operation.
What the answer must demonstrate: Keeps request identity, payload validation and mapping commit connected.
Why can caching change deletion behavior?
Reveal a model answer
The database can contain a deletion while a cache still holds the prior mapping. Serving the copy therefore needs an agreed freshness policy.
Interviewer follow-up
Can ordinary TTL prove immediate revocation?
Reveal the follow-up answer
No. Immediate revocation requires current authority checks or an explicit coordinated protocol.
What the answer must demonstrate: States a deletion visibility contract instead of treating invalidation as guaranteed.
Why does hashing codes not fix one viral link?
Reveal a model answer
Hashing distributes different codes. Every lookup for the same code still targets its owner.
Interviewer follow-up
What helps?
Reveal the follow-up answer
Replicated cache copies, coalesced misses and bounded origin fallback distribute repeated reads.
What the answer must demonstrate: Distinguishes distribution of different keys from repeated reads of one key.
Can a delayed cleanup job extend a link lifetime?
Reveal a model answer
No. Every serving path checks expiry. Cleanup removes obsolete storage later.
Interviewer follow-up
Why retain an old claim?
Reveal the follow-up answer
Reusing the code could send an old bookmark to an unrelated creator.
What the answer must demonstrate: Separates expiry enforcement from physical deletion and permanent claims.
What must be reconsidered after splitting storage?
Reveal a model answer
The mapping and request result may no longer share one local transaction. Retain transactional support or specify a recoverable creation workflow.
Interviewer follow-up
Is changing the partition map sufficient?
Reveal the follow-up answer
No. Data must be copied, writes safely handed over, and old owners prevented from conflicting.
What the answer must demonstrate: Recognizes which transaction boundary the new storage layout removes.
What changes for private links?
Reveal a model answer
Authenticate readers and evaluate current access before returning a destination; code secrecy does not establish permission.
Interviewer follow-up
Can the public cache authorize access?
Reveal the follow-up answer
No. It may store mapping bytes, but authorization needs its own declared freshness and revocation contract.
What the answer must demonstrate: Separates possession of a URL from current reader authorization.
Blank-page exercise · 45 minutes
Build the answer yourself
Design a public URL shortener for 500M creations per month and 100 redirects per creation. Establish the working baseline, estimate its load, then defend your caching and retry choices.
- Use 5 minutes to agree the numbered functional and non-functional requirements, including aliases, owner listing, latency, durability and deletion visibility.
- Use 8 minutes to trace creation and a browser redirect.
- Use 7 minutes to calculate throughput, retained bytes and cache assumptions.
- Use 10 minutes to define API, records, uniqueness and retry behavior.
- Use 10 minutes to add scale and examine lost responses, hot keys and cache failure.
- Use 5 minutes to check the final design against the numbered FR/NFR lists, state unmeasured targets and eventual-deletion limits, then summarize tradeoffs.
Check that each component and design decision follows from your requirements and workload.
Recall the key ideas
Answer from memory before opening each card. Explain why the choice works and what it costs. Revisit missed cards tomorrow.
Design a URL shortenerTwo servers generate q7Lm2Ax9 at the same time. Which step decides who owns it?Recall first, then reveal
Both attempt the unique insert. Only one commits; the other chooses a new candidate. A preliminary “absent” lookup would not prevent the race.
Generate, then claim.
Return to lessonDesign a URL shortenerCreation succeeds, but its response is lost. What must the retry reuse?Recall first, then reveal
Reuse the same request key for that owner; the server returns its saved creation result.
Retry the operation, recover its result.
Return to lessonDesign a URL shortenerAn expired link still occupies storage because cleanup is late. May it redirect?Recall first, then reveal
No. The redirect handler checks expiry during the read; cleanup delay does not extend the link’s lifetime.
Expiry first; reclamation later.
Return to lessonFinal revision
Summary and interview notes
A shortener saves a code and destination, then redirects visits. A unique insert prevents duplicate codes; a saved creation result lets the caller recover a lost response.
Remember these points
- The browser fetches the target separately.
- Read-heavy traffic motivates a measured cache.
- A deleted or expired code must not acquire a new owner.
- Sharding must preserve the creation/retry boundary.
Interview tips
- Explain one request before naming extra services.
- Connect each scaling component to measured work.
Important qualifications
- Exact global revocation deadlines and cross-partition retry proofs belong to explicit follow-up contracts.
Continue after the core interview
Explore the advanced version
The advanced lesson keeps the full detailed design. Use these sections when you want to examine the stronger requirements and failure cases.
- Bounded revocation and clock uncertainty
Needed when the product requires a provable deletion deadline despite delayed cache fills.
- Cross-partition creation protocol
Needed when request identity and mapping ownership cannot share a transaction.
- Incomplete regional allocation recovery
Needed to preserve old code claims after a recovery point loses recent history.
- Storage ownership and migration fencing
Needed when explaining safe ownership changes beyond the logical partitioning decision.
Technical references
- RFC 9110: HTTP semantics and redirectsDefines 302 and Location semantics; cache lifetime and revocation remain explicit application decisions.
- PostgreSQL constraintsPrimary and unique constraints are an example of authoritative duplicate-key enforcement.
- DynamoDB condition expressionsAn alternative implementation of atomic conditional insertion, rather than a read-then-write uniqueness check.
Practice marks stay in this browser.