System-design interview · Core interviews
Design a URL shortener
Design code allocation, durable creation and low-latency redirects; choose caching and partitioning from the expected traffic, and specify how deletion and failed requests affect redirects.
You will learn to
- Explain a redirect using a concrete browser request and stored mapping.
- Derive code allocation, cache capacity, and partitioning from explicit requirements.
- Recover safely from duplicate creation, hot-key traffic, and delayed cleanup.
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.
01Problem and scope
A URL shortener stores a mapping from a short code to a destination URL and resolves that code with an HTTP redirect. Its main engineering responsibilities are unique allocation, durable creation, fast lookup, and defined deletion and expiration behavior. It does not download, compress, or proxy the destination page. For example, a create request maps q7Lm2Ax9 to https://events.example/register?event=design-day&campaign=poster; a browser requests https://s.example/q7Lm2Ax9, receives the redirect, and then contacts the destination website.
Clarify whether destinations can change, whether custom aliases are required, and whether readers must authenticate. This design assumes immutable destinations, public redirects, authenticated creation, optional aliases and expiry, and a maximum 30-second revocation delay for new redirect requests. Immediate security revocation would require a stronger read contract and a different caching decision.
Support creation, resolution, owner listing, deletion and delayed basic statistics. Exclude a marketing dashboard, destination crawling in the request path, editable destinations and authenticated private links from the first implementation. We will discuss the private-link extension later. These exclusions matter: an immutable public mapping can be copied widely; an authorization decision cannot simply inherit that caching policy. The following numbers are hypothetical interview assumptions, not measurements of a company's deployment.
02Functional requirements
- Create: Return success only after the mapping is stored so it will survive the specified storage-node or availability-zone failure.
- Resolve: A currently active code returns its original destination; never another creator's destination.
- Delete: Only the owner may revoke; new requests stop redirecting within 30 seconds.
- Expire: Requests whose authoritative time is past the deadline do not redirect.
- List: An owner can page through their mappings without exposing another owner's records.
- View statistics: Counts may arrive late and are explicitly approximate under telemetry loss.
Request identity and custom aliases
The creator authenticates, submits one destination and receives one code. A repeated submission with the same request identity returns the same code; a deliberately separate request may create another code for the same destination. This avoids combining unrelated campaign statistics merely because two URLs match. An alias such as design-day is a first-claim allocation: another account receives a conflict, never ownership of the existing alias.
Permanent codes and error behavior
Codes are never reused, including after expiry. A printed poster can outlive the retention period, so recycling its alias would create a dangerous new meaning. Unknown and deleted links return an unavailable response without disclosing private account details. Users can see processing or retryable errors; silently inventing a replacement destination is never acceptable. If a destination itself fails, that is outside the shortener's availability promise.
03Non-functional requirements
Redirect speed and revocation are linked: serving a cached mapping avoids a storage read, but that copy cannot immediately know its owner deleted the link. A cache freshness lease is the storage authority's permission to use the copy until a fixed deadline. The targets below set the allowed delay and the failures the service must survive.
- Latency: Redirect p95 below 50 ms and creation p95 below 300 ms inside the serving region.
- Availability: 99.95% successful eligible redirects per month. Validate this target under load and failure; replicas alone do not establish it.
- Durability: Accepted creation survives one storage-node or availability-zone failure.
- Regional recovery: Initially allow 15 minutes of potential data loss, the recovery point objective (RPO), and one hour to restore service, the recovery time objective (RTO) from replicated backups. Test these objectives separately from node/zone failover.
- Retention: Plan for five years of mappings. Permanent code ownership outlives payload retention.
- Revocation and expiry: Stop redirecting new requests within 30 seconds of deletion; responses already emitted may finish. Never serve beyond the mapping's expiry or original freshness deadline.
- Clock budget: Use 25-second cache leases plus five seconds reserved for measured skew and transport. If monitoring exceeds that reserve, stop using cached mappings until revalidation.
- Security: Encrypt transport, authorize owner operations, and retain click metadata only as long as the product needs it.
Invariants and partition behavior
| Rule | Required behavior |
|---|---|
| Permanent ownership | One code has one permanent owner; a completed request identity refers to one mapping. |
| Write-majority loss | Reject creation and deletion with a retryable error. |
| Bounded cached reads | Public reads may continue only to the original freshness deadline; a longer partition produces unavailable responses. |
| Missing regional allocation history | Freeze allocation in the old URL namespace; never reassign uncertain old aliases. |
The revocation guarantee deliberately limits redirect availability during a long partition. The regional RPO permits losing recent mappings, not reusing their codes. New random links may use a distinct recovery prefix or hostname while the old namespace remains read-only. The one-hour recovery target covers serving recoverable mappings; it does not prove that missing allocation history is complete.
04Capacity estimates
Workload assumptions and arithmetic
Assume 500 million new links in a 30-day month and 100 redirects per creation. There are 30 × 24 × 3,600 = 2,592,000 seconds in that month. Average writes are 500,000,000 / 2,592,000 = 193/s; average reads are 50,000,000,000 / 2,592,000 = 19,290/s. A fivefold planning peak is 965 creates/s and 96,450 redirects/s. Benchmark these independently: reads and writes consume different resources.
Worked estimates
| Resource | Calculation | Decision it motivates |
|---|---|---|
| Five-year records | 500M × 12 × 5 = 30B |
Partition the retained mapping table |
| Logical mapping storage | 30B × 500 B = 15 TB |
Three copies need 45 TB before indexes/backups |
| Average redirect payload | 19,290/s × 500 B = 9.65 MB/s |
Small responses; destination bytes are excluded |
| Peak redirect payload | 96,450/s × 500 B = 48.2 MB/s |
Balance network and CPU across API instances |
| Hypothetical distinct hot set | 10M keys × 700 B = 7 GB |
Budget memory from distinct keys, not request count |
Capacity implications and limits
The 700-byte cache figure includes an assumed allowance for key and entry overhead; allocator fragmentation and redundancy add more. At a measured 95% hit rate, peak database reads become 96,450 × 0.05 ≈ 4,823/s. Losing the entire cache restores almost 96,450 reads/s, a twentyfold jump. A cache failure can therefore multiply database load. Limit how many cache misses may fall back to the database. The ratio alone does not prove a 95% hit rate: measure the actual popularity distribution and the effect of the 25-second freshness lease.
05APIs and contracts
Request and response example
The creator sends POST /v1/links, authenticated as account u17, with header Idempotency-Key: create-204 and body {"url":"https://events.example/register?event=design-day&campaign=poster","expiresAt":"2027-01-01T00:00:00Z"}. Success returns 201 {"code":"q7Lm2Ax9","shortUrl":"https://s.example/q7Lm2Ax9"}. The server stores a hash of the validated request payload; retrying the same key with a different payload returns 409. Idempotency means the same logical operation produces the same result despite repeated transport attempts.
Interface contracts
| Endpoint | Contract |
|---|---|
GET /q7Lm2Ax9 |
302 Location: <stored URL> with a deliberate client cache policy |
POST /v1/links with alias |
409 if permanently claimed; 400 for invalid URL/alias |
DELETE /v1/links/q7Lm2Ax9 |
Owner-authorized logical deletion; repeating it is harmless |
GET /v1/links?after=<cursor>&limit=50 |
Stable owner-scoped creation-time/code cursor |
GET /v1/links/q7Lm2Ax9/stats |
Owner-only aggregate plus updatedAt and approximation notice |
Validation and response semantics
Use Cache-Control: no-store on browser redirect responses for this revocation contract; internal caching remains controlled by the service. A permanent redirect cached outside our control would undermine deletion semantics. Return 429 with retry guidance for creation quota exhaustion, and 503 for unavailable authority. Resolve an unknown code with 404; expiration and deletion may also use that response to minimize enumeration clues. A timed-out POST is an unknown outcome, so the client repeats its original key instead of allocating a fresh one.
06Data model and access patterns
For the initial single-database design, use unique keys for code claims and a transaction to save the mapping and creation result together.
The two records answer different questions. Link says which destination a code owns; Request says which result belongs to a creator's submission. Keeping both is necessary because a caller can lose a successful response and retry without intending to create another link.
| Record and fields | Responsibility / constraint |
|---|---|
Link(code PRIMARY KEY, ownerId, destination, createdAt, expiresAt, deletedAt, version, createToken) |
Owns the mapping. |
Request(ownerId, requestKey PRIMARY KEY within owner, payloadHash, candidateCode, state, result) |
Owns retry identity. |
The owner listing uses (ownerId, createdAt DESC, code DESC); expiry cleanup uses (expiryBucket, expiresAt, code). A primary-key constraint or conditional insert resolves simultaneous ownership claims atomically.
The reader's query is SELECT destination, expiresAt, deletedAt, version FROM Link WHERE code='q7Lm2Ax9'. The cache stores those fields plus validatedAt and an absolute validUntil, not a sliding “25 seconds after every hit.” Statistics are derived from click events and never determine whether a mapping exists. Listing indexes may lag after the later sharding step; the creation response and code lookup remain authoritative.
At scale, hash the complete code to a logical partition, then use a routing map to locate that partition's replicated leader. Hash (ownerId, requestKey) to a request partition. The code partition and request partition may be managed by different storage groups, called their owners; they therefore do not share one local transaction. Each partition group owns its own serial writes and committed log. An asynchronous owner index supports listing; a stalled index cannot make an allocated code available to someone else. A compact permanent tombstone preserves claimed aliases after bulky URL payloads are reclaimed.
07Basic working design
Single-server transaction
Use one application server and one SQL database. The app validates the creator's request, begins a transaction, and checks the owner/request-key identity. If a completed row exists with the same payload, it returns that result. Otherwise it chooses eight random base-62 characters, inserts the Link, and inserts the Request result within that same local transaction. A duplicate random code aborts that allocation attempt and causes a retry; a duplicate request key makes the server read the winner's result.
Base 62 uses the ten digits and the uppercase and lowercase English letters as its code alphabet. Random selection makes repeated candidates uncommon; the database's unique key, not the choice of alphabet, decides whether a candidate can be allocated.
Commit and retry boundary
The application reports success only after the database commits. A process crash before commit leaves no accepted link. After commit, a crash can hide the HTTP response but not remove the durable mapping. The reader's GET performs a primary-key lookup and checks deletion/expiry before returning a redirect. This baseline already demonstrates the central guarantee without a cache, queue, ID service, or sharding layer.
Maintenance and baseline limits
An administrator can inspect q7Lm2Ax9 and request u17/create-204 in one transaction when diagnosing a timeout. A scheduled job scans expired rows for reclamation, while every read independently enforces expiry. This is important even at small scale: cleanup is an efficiency operation, not the access-control clock. Start with backups and restore testing. One server can be a reasonable first product, but its availability and storage limits do not satisfy the final workload.
The creator’s creation and request identity commit together in the baseline. The reader follows a redirect; our service never serves the destination page.
Read each connection in order
- sync1. POST create-204 / GET codeCreator and reader clients → Link application
- sync2. Transaction / point lookupLink application → SQL mapping and request tables
- sync3. Return code / 302 LocationLink application → Creator and reader clients
- sync4. Follow LocationCreator and reader clients → Destination website
08Find the baseline flaws
The baseline's single transaction preserves creation retries, but its capacity is limited. Splitting that transaction or adding a cache can introduce new correctness failures while addressing scale. The examples below distinguish the existing capacity limit from those additional races.
| Bottleneck / counterexample | Evidence and design consequence |
|---|---|
| Read throughput gap | Suppose a benchmark gives the baseline database 10,000 indexed reads/s at the required p95. The projected peak is 96,450/s, almost ten times higher. Increasing connection-pool size does not create database capacity; it converts excess work into queues and longer response times. Meanwhile, maintaining and backing up the 15 TB five-year table on one node becomes difficult even if write QPS is modest. |
| Lost create response | A second counterexample is a lost create response. If an engineer moves request-result recording outside the baseline transaction, a crash after inserting the mapping but before storing create-204 can create a second code on retry. The system must retain the original atomic boundary or replace it with an explicit recoverable protocol. Sharding alone is not an excuse to lose that rule. |
| Late cache refill after deletion | Finally, a naive cache can violate deletion. At time 0 a reader fetches active version 2; at time 1 deletion commits version 3; at time 2 the old reader fills an empty cache and starts a fresh long TTL. “Invalidate on delete” did not prevent the late refill. We will use bounded absolute freshness leases obtained from the authority, and optionally versioned tombstones to improve propagation. The stated 30-second promise is proven by the lease deadline, not by optimistic invalidation delivery. |
09Improve the design, step by step
First, replicate the authoritative database and add stateless API instances. The trigger is a single process or zone failure violating accepted-write durability. A leader commits through a majority of three replicas in separate failure domains; APIs use health-checked routing. This survives one failed replica and removes an app bottleneck. It adds replication latency, failover operations and minority unavailability. Asynchronous replicas are cheaper for write latency but cannot satisfy the same acknowledged-loss rule; choose them only for a weaker disaster-recovery contract.
Second, add internal mapping caches and coalesced refills. The trigger is the measured tenfold read deficit. Many repeated reads use copies, while one in-flight refill per code per cache region suppresses a miss storm. Each copy carries an absolute authority-issued validity deadline. This reduces normal database load but costs RAM, cache operations and a bounded revocation delay. A cache outage can overload storage, so the API caps how many cache misses may reach the database per second. Keeping all reads authoritative is simpler and preferable while measured throughput permits it.
Third, partition retained mappings and separate retry reservation from mapping creation. Storage growth triggers this step. A durable
PENDINGrequest row reserves one candidate andcreateToken; conditional mapping insertion then executes at the code owner; completion records the result back at the request owner. A retry resumes the recorded candidate. This distributes bytes and queries without a global transaction, but adds a second durable workflow and cleanup/repair work. A transactional distributed SQL system is a valid alternative when its cross-partition transactions meet the measured cost and latency budget. SQL is not inherently disqualified by scale.Fourth, move statistics and cleanup off the redirect path. The trigger is variable aggregation latency and large expiry scans. Bound a telemetry queue, batch counters, and sweep an expiry index. Redirects no longer wait for analytics; costs become worker capacity, retention and possibly missing counts. Billing-grade exact counting would instead need a durable event acknowledgement and stronger deduplication. We reject that extra latency because this product explicitly promises approximate statistics.
10Detailed architecture
Separate creation and redirect pools
The edge terminates TLS and routes creation and redirect traffic to separate API pools so a creation-abuse spike cannot consume every redirect worker. Creation checks account authentication and quotas. A partition router maps request identities and codes to their respective storage groups; this is routing metadata, not an independent authority that may invent mappings. When an API uses an outdated route, the storage group returns a moved-partition response; the API refreshes the route and its ownership-version number, called an epoch.
Partition authority and fencing
Each storage group contains a leader and replicas; its atomic conditional writes protect keys it owns. During migration, the old owner is fenced from new writes before the new epoch accepts them. Fencing here means the storage write path rejects an obsolete ownership epoch; simply telling clients to refresh is insufficient. The final diagram shows one representative replica group, not a claim that all 30 billion rows sit on one server.
Read copies and asynchronous work
Redirect workers check the internal cache, then contact the authoritative code owner on a miss or expired lease. They may use replicated hot cache entries because public immutable destinations dominate reads. Owner listing, analytics aggregation, and expiry reclamation are asynchronous derived work. A separate expiry worker marks/reclaims records through the same code authority. The diagram's asynchronous arrows represent work that may finish after the response, such as statistics updates. Creation must wait for a durable commit, and an expired cache entry must wait for a current storage read before the API can return success.
Concrete stack and durability assumptions
A practical baseline can use PostgreSQL for transactions and a Redis or Memcached tier only for disposable mapping copies. The final partition-owner diagram specifies stronger storage requirements: conditional writes, durable replication, safe failover, and verified current reads. Use an established implementation providing those guarantees or a supported distributed SQL transaction path; ordinary PostgreSQL streaming replicas do not become a safe majority-election protocol merely because three databases are drawn. A managed store may hide physical shards, leaving only the logical ownership and retry protocol visible to the application.
Creation reserves a retry identity and conditionally inserts at the code owner. Public cache copies expire at authority-issued deadlines; telemetry and reclamation are asynchronous.
Read each connection in order
- sync1. Create or resolve codeCreator and reader clients → TLS edge and traffic routing
- sync2a. Route writesTLS edge and traffic routing → Authenticated creation API
- sync2b. Route readsTLS edge and traffic routing → Redirect API pool
- sync3. Reserve / conditional insertAuthenticated creation API → Partition router and epochs
- sync4a. Persist request tokenPartition router and epochs → Request-state partition leaders
- sync4b. Own code atomicallyPartition router and epochs → Code partition leaders
- replication5. Replicate committed mappingsCode partition leaders → Storage-group replicas
- replication5. Replicate request stateRequest-state partition leaders → Storage-group replicas
- sync6. Lookup valid leaseRedirect API pool → Bounded mapping caches
- sync7. Miss: current authority readRedirect API pool → Partition router and epochs
- sync8. 302 LocationRedirect API pool → Creator and reader clients
- sync9. Request original URLCreator and reader clients → Destination website
- async10. Best-effort clickRedirect API pool → Click and change queues
- async11. Committed mapping changeCode partition leaders → Click and change queues
- async12. Batch derived updatesClick and change queues → Statistics and owner-index workers
- async13. Upsert derived viewsStatistics and owner-index workers → Statistics and owner index
- async14. Reclaim expired payloadExpiry reclamation workers → Partition router and epochs
11Write path and acknowledgement
Creation must preserve one result across retries even when the request record and code mapping have different partition owners. The following request uses account u17, key create-204, candidate q7Lm2Ax9 and allocation token t204 to show the durable transitions.
- The creator submits
u17/create-204. The API authenticates u17, validates the complete URL without changing encoded semantics, and calculates the request payload hash. - At the request partition, insert
PENDING(candidate=q7Lm2Ax9, token=t204, payloadHash=H)if absent. Competing copies of this request read the same durable candidate and token. - Route
q7Lm2Ax9to its code partition. Conditionally insert the mapping withcreateToken=t204. The operation succeeds only if the code is absent, or recognizes the existing mapping with the same token as its own previous success. - If another token owns the candidate, atomically replace the request's candidate only while it is still the recorded rejected candidate; retry the new candidate. A custom alias instead completes with a conflict. Once a mapping was accepted, never rotate that candidate merely because a response timed out.
- Mark the request
COMPLETE(result=q7Lm2Ax9)after verifying that the mapping has the same creation token. Return success. Asynchronous listing receives an idempotent mapping-created event or scans committed changes. - If the API dies between steps 3 and 5, retry sees PENDING, repeats step 3, finds t204, and completes the same result. It does not create a second link.
Retain a compact request-to-code identity for the accepted retry contract; do not silently forget old request identities while promising unlimited retries. Operationally bound retry records by an explicitly documented retention window if storage requires it, and make clients use fresh keys only for intentional new operations.
12Read and delivery path
Redirect serving checks both mapping validity and permission to use a cached copy. This trace resolves q7Lm2Ax9 and shows why an expired lease requires a fresh authoritative read.
- The reader requests
/q7Lm2Ax9. The redirect pool validates code syntax and applies an abuse limit without requiring a creator login. - It reads the cached mapping. A hit is usable only if current safe time is before both
validUntilandexpiresAt, and the record is active. - On a miss, the worker joins one refill for this code. The router contacts the current code authority, whose read returns mapping version 2 and a lease ending 25 seconds after authoritative validation. The deadline is anchored to that validation, never delayed by a slow network response.
- The worker rejects an already-expired response and caches a still-valid one. Inserting it does not extend the lease. Negative results get a short bounded cache lifetime to avoid suppressing a just-created code indefinitely.
- It returns 302 and the original
Location, then emits a bounded best-effort click event. The reader's browser makes a separate connection to the conference site. - At lease expiry, a fresh authority read is required. If deletion committed, the response becomes unavailable. If authority cannot be reached, the service returns 503 rather than refresh stale state locally.
A read replica with unknown lag cannot issue a fresh lease for the revocation contract. Route revalidation to the leader or a replica with a protocol that proves sufficiently current committed state. The cache saves repeated work; it cannot manufacture knowledge during a partition.
For strict expiry, compare the expiration with a conservative upper bound on current time, including measured clock uncertainty. This may stop a link slightly early but cannot grant extra life to an already-expired mapping. Lease issuance must also anchor its deadline to the authoritative validation operation, for example conservatively before a verified current read begins, rather than to the later cache insertion time.
13Correctness deep dive
Code-space arithmetic
| Operation | Preconditions checked by authority | Durable effect / result |
|---|---|---|
| Insert candidate | Code absent | Save owner, immutable destination, token and version 1 |
| Retry same allocation | Existing createToken=t204 |
Return the existing mapping without modification |
| Competing allocation | Existing token differs | Reject; caller may reserve a new random candidate |
| Delete | Owner matches and active | Set deletedAt and increment version; retain claim |
| Revalidate | Current committed row is active | Return data with fixed absolute freshness deadline |
Retry-token ownership proof
Late-refill revocation proof
The code owner compares createToken atomically. The resumed request uses its recorded candidate, so it cannot allocate a second link.
Read each connection in order
- syncPOST create-204The creator’s client → Creation API
- syncInsert PENDING q7Lm2Ax9 / t204Creation API → Request owner
- returnDurable candidate and tokenRequest owner → Creation API
- syncInsert code if absent, token t204Creation API → Code owner
- returnCommitted mappingCode owner → Creation API
- blockedAPI crashes; response lostCreation API → The creator’s client
- syncRetry create-204The creator’s client → Creation API
- syncRead existing PENDINGCreation API → Request owner
- syncRepeat conditional insert t204Creation API → Code owner
- returnExisting t204: same mappingCode owner → Creation API
- syncMark COMPLETE q7Lm2Ax9Creation API → Request owner
- returnReturn original short URLCreation API → The creator’s client
14Failure and recovery
| Failure / trigger | User outcome, surviving state and recovery |
|---|---|
| Crash after code insertion | At t0 PENDING owns t204; at t1 the code leader commits q7Lm2Ax9; at t2 the API crashes before COMPLETE. The creator sees a timeout. Durable request state and the code row survive. A retry or repair worker rechecks the same token and completes. A long outage can leave creation pending; it cannot safely return a different code just to be responsive. |
| Partition after deletion | A minority replica cannot acknowledge deletion. If the majority committed it but the response disappeared, the owner retries a harmless delete. Redirect caches may continue until their pre-existing deadlines, then return unavailable if no current authority is reachable. A regional outage can therefore exhaust caches quickly; meeting a strict deletion bound costs availability during isolation. Replication failover must fence the old leader to prevent split ownership. |
| Cache outage at peak | Database demand jumps from roughly 4,823 to 96,450 reads/s. Set a tested fallback budget, for example 8,000/s, reserve capacity for writes/recovery, and reject excess with a short retry hint plus jitter. Recover caches gradually rather than releasing a synchronized refill wave. Popular entries can be replicated among serving caches; consistent hashing of distinct codes alone cannot split one viral code. |
| Cleanup lag | Readers still enforce expiry, so a late sweeper increases storage usage but not link lifetime. Sweeper jobs are idempotent and verify the row version before reclamation. Maintain backups and test both logical data restoration and permanent-claim restoration: losing tombstones could allow old posters to acquire new destinations. |
| Restore with incomplete claims | Keep the affected old namespace read-only when the regional RPO leaves uncertain allocations. Recover known mappings and tombstones, but do not infer that an absent recovered row was never issued. A new namespace for new allocations preserves old bookmarks from silently acquiring a different destination. |
15Operations, security, and cost
Latency, validation-age and failure signals
Track redirect p95/p99 and eligible-response success, separating cache hits, revalidations and rejected overload. Alert on the longest elapsed time since a served mapping was last checked against current storage, not only hit rate: a high hit rate can conceal broken revocation. Measure request records stuck PENDING, conditional-insert conflicts, ownership-epoch rejection, expiry sweep lag and approximate analytics drop count. Exercise clock-skew alarms because our bound includes a five-second reserve.
URL validation and authorization
Validate only intended URL schemes, impose length limits, and authorize deletion/listing using server-derived identity. A short unguessable code is not a team permission grant. If threat scanning is added, run it in an isolated outbound-fetch service with destination validation, redirect limits and blocked private-network ranges; never let the create API become an internal-network request tool. Rate-limit enumeration and creation abuse independently. Apply privacy retention to referrers and IP-derived statistics.
Cache economics
Cache economics can be stated without guessing cloud prices. Let a database lookup cost C units and a cache lookup cost 0.05C. At 95% hits, cost per lookup is 0.05C + 0.05C = 0.10C before fixed memory/operations, versus C without a cache. The saving must exceed the cost of maintaining approximately 7 GB per hot-set copy and the operational risk. Recalculate with measured hit rates.
Migration and recovery drills
Roll out shard migration by copying a partition, verifying row counts/checksums, replaying changes, fencing the old epoch, then switching routing. Shadow reads compare old/new answers before cutover. Test crashes between every create state transition, cache outage under peak load, a deletion with lost invalidation, and restoring tombstones alongside mappings.
Privacy-aware click analytics
For redirect analytics, record a minimized event such as code, time bucket, coarse country/region, referrer category and client/browser category when the privacy policy permits. These dimensions answer where and when a link is used; keep raw identifiers out of long-lived aggregates and specify retention. Analytics loss or duplication must not change redirect correctness.
16Decision ledger and limitations
| Choice | Benefit | Cost / consequence | Change trigger |
|---|---|---|---|
| Random codes plus conditional insert | Decentralized candidate generation with enforced ownership | Collision retries and permanent claim storage | Extremely high allocation rate may justify reserved batches |
| Bounded internal cache leases | Fast repeated public reads with a provable revocation bound | Authority outage becomes errors when leases expire | Immediate revocation requires a stronger protocol |
| Request reservation then idempotent mapping insertion | Recoverable cross-partition creation without global transaction | PENDING repair and two durable round trips | Use distributed transactions if measured simplicity wins |
| Hash code partitions | Balances distinct mappings and retained bytes | Owner listing is a separate derived index | Access pattern changes may demand another index |
| Approximate asynchronous statistics | Redirects avoid analytics latency | Counters can lag or lose bounded events | Billing or audit requirements need durable event accounting |
A sequential counter encoded in base 62 is an alternative allocation mechanism, but exposes predictable IDs and needs a scalable counter authority. A key-generation service can reserve unused batches durably before distribution; failover must not hand one batch to two owners, and a crashed consumer should waste unused keys rather than reuse uncertain ones. We reject it initially because 965 peak creates/s does not justify that separate service.
The design does not promise instant global deletion, private reader authorization, accurate billing counts or zero regional-disaster data loss. Its next scaling limit may be a small number of hot mappings, route metadata churn, or the retained tombstone/index footprint; measure before adding another database technology.
17Interview closing
“I designed an immutable public mapping service for public short links. Creation and redirects have different loads: roughly 193 average writes and 19,290 average reads per second, with a fivefold peak. I began with one SQL transaction, then added replicated authority, caches for repeated lookups, and code partitions for five-year storage. The database's conditional insert decides ownership; random codes merely make conflicts infrequent. A durable request reservation and token let a timed-out creation recover its original mapping across partitions.
“The reader's redirect validates a cached mapping's absolute lease and expiry, then returns a small 302 response. Analytics and reclamation are asynchronous. I deliberately trade at most 30 seconds of revocation delay for cached reads, and reject stale reads after that deadline during an authority outage. The main remaining risks are cache-loss amplification and hot-key concentration. I would next measure hit rate under the lease policy and run a peak-load cache-failure test.”
If the interviewer says, “private team links must revoke immediately,” adapt the contract explicitly: authenticate each reader, associate membership/version state with the mapping, and perform an authoritative permission check or implement coordinated revocation before returning success. Re-estimate read load because the public-data cache cannot authorize access. Do not claim the existing 30-second design already meets the stronger requirement.
Practise the interview questions
Say your answer aloud before opening the model answer. Then answer the follow-up and compare the reasoning.
What does a URL shortener actually store?
Reveal a model answer
“It stores a mapping from a short code to a destination, plus ownership and expiry. A browser requests the code, receives a redirect, and then contacts the destination. The shortener does not need to download or proxy the destination page.”
Interviewer follow-up
Why does that distinction matter for capacity?
Reveal the follow-up answer
“Redirect traffic includes small responses, not the destination page or video bytes. I estimate those separately and avoid accidentally designing a web proxy.”
What the answer must demonstrate: Do not count destination content as shortener egress.
How do you guarantee that two links do not receive the same code?
Reveal a model answer
“I generate a candidate and atomically insert it only if the code is absent. If two servers choose q7Lm2Ax9, exactly one insert wins; the other retries with a new random code. A random generator gives a low collision probability, while the database constraint gives the uniqueness rule.”
Interviewer follow-up
Would SHA-256 remove the need to check?
Reveal the follow-up answer
“No. Truncating it to a short code leaves a finite collision space, and even full hashes are not a business ownership rule. I still define collision and duplicate-request behavior.”
What the answer must demonstrate: Do not equate a hash with a unique allocation protocol.
One short link receives 50,000 redirects per second. Where would you add capacity, and why would more database shards not fix this hotspot?
Reveal a model answer
“The hot unit is one mapping, so I replicate that entry in cache close to the API. I collapse concurrent misses so expiry does not send 50,000 database reads at once. Adding database shards helps many different keys but does not split this one key.”
Interviewer follow-up
How do you decide the cache size?
What the answer must demonstrate: A balanced partition map does not cure a hot key.
A create request times out after its mapping commits but before the client receives a response. How should the retry avoid allocating a second link?
Reveal a model answer
“The client must reuse the original idempotency key and payload. In the partitioned design I load that request’s durable candidate and create token, resume the conditional mapping insert, and complete the saved result only after verifying that the mapping has the same creation token. For example, request create-204 resumes candidate q7Lm2Ax9 instead of generating another code. A completed result can be returned immediately. The one-database baseline commits both records together; separate owners require this resumable protocol.”
Interviewer follow-up
What if the same request key carries a different destination?
Reveal the follow-up answer
“Compare a saved payload hash and reject the mismatch. Reusing an idempotency key must not silently change the earlier operation.”
What the answer must demonstrate: Do not acknowledge creation before the mapping is durable.
The cleanup worker is two hours behind. Can expired links still redirect?
Reveal a model answer
“No. Every lookup checks the stored expiration against server time, including cached entries. Cleanup controls when we reclaim bytes, while the read check controls product behavior. I also bound cache lifetime by the link deadline.”
Interviewer follow-up
Can you recycle expired codes?
Reveal the follow-up answer
“I would not. A saved bookmark or printed poster could unexpectedly resolve to a new owner. The available namespace makes retaining tombstones preferable.”
What the answer must demonstrate: Do not confuse physical deletion with logical expiry.
How would you extend a public URL shortener to links restricted to authenticated team members?
Reveal a model answer
“I add an authenticated reader and an access list associated with the code. The lookup checks that grant before returning the destination. Public cache entries cannot authorize private access; permission revocation needs a freshness policy independent of the URL bytes.”
Interviewer follow-up
Is a long random code sufficient for a private link?
Reveal the follow-up answer
“It provides possession-based access at best: anyone who obtains it can share it. If the requirement is named team members only, the server must verify identity and membership.”
What the answer must demonstrate: Unlisted and authenticated-private are different contracts.
Your request table and mapping table now live on different shards. Where is the atomic boundary?
Reveal a model answer
I cannot keep claiming one local transaction. I first durably reserve the candidate and token in the request owner. The code owner conditionally inserts that candidate or recognizes the same token. Only after observing that durable mapping do I complete the request result. A retry resumes those states. The tradeoff is an extra durable round trip and repairable pending work.
Interviewer follow-up
What if insertion succeeded but completion never ran?
Reveal the follow-up answer
The retry sees the same pending candidate, sends the same token, and the code owner returns the existing mapping. It must not generate a fresh code merely because the earlier network call timed out.
What the answer must demonstrate: Name the durable state that lets recovery distinguish a retry from a new allocation.
The service promises that new requests stop redirecting within 30 seconds after deletion. Why can a delayed cache refill not extend that bound?
Reveal a model answer
The authority validated version 2 at time zero and issued an absolute deadline of 25 seconds. Deletion commits at time one. A delayed refill at time ten still expires at 25; it does not receive a new lifetime. With our monitored five-second uncertainty reserve, a new request at time 31 cannot use it. Lost invalidation affects speed, not the bound.
Interviewer follow-up
Would a version number without an absolute deadline suffice?
What the answer must demonstrate: Do not silently turn a bounded-staleness contract into immediate revocation.
Blank-page exercise · 45 minutes
Build the answer yourself
Design a public URL shortener for 500 million creations per month and 100 redirects per creation. Derive a baseline and its evolution, then prove code uniqueness, timeout recovery, expiration and a 30-second revocation bound under a hot-link workload.
- Draw the browser’s two requests and identify which bytes our service serves.
- Write the unique-code and expiration invariants.
- Calculate average/peak QPS and distinct-key cache storage.
- Demonstrate a two-writer collision and an idempotent retry.
- Explain hot-key mitigation and the deletion/cache race.
- Answer the private-link extension without relying on secrecy alone.
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 shortenerBoth servers choose q7Lm2Ax9. Who wins?Recall first, then reveal
The first successful conditional insert owns the code; the other retries. Probability reduces retries, while atomic storage enforces uniqueness.
Generate is a guess; insert decides.
Return to lessonDesign a URL shortenerThe cleanup worker is late. May an expired link still redirect?Recall first, then reveal
No. Enforce the expiration during every lookup; reclaim storage asynchronously.
Deadline stops reads; cleanup frees bytes.
Return to lessonDesign a URL shortenerHow should link creation recover after a lost response?Recall first, then reveal
Reuse the same idempotency key and payload; retrieve or complete the originally reserved mapping rather than allocate another link.
Same request, same result.
Return to lessonFinal revision
Summary and interview notes
A URL shortener saves a code-to-destination mapping and returns a small redirect. A unique insert protects code ownership; a saved request result recovers a lost response; a fixed cache deadline bounds deletion delay. Random codes and invalidation messages alone cannot provide those guarantees.
Remember these points
- At the stated load, peak traffic is about 965 creates/s and 96,450 redirects/s; the target page bytes are outside shortener egress.
- A conditional insert decides uniqueness; eight base-62 characters reduce collision retries but do not eliminate them.
- After sharding, a durable candidate and create token let an unknown create outcome resume without allocating another link.
- An absolute 25-second freshness lease plus the stated uncertainty reserve supports a 30-second revocation bound; expiry needs a conservative time check.
- Permanent ownership requires retained claims; incomplete regional recovery must not reopen an uncertain old namespace for allocation.
Interview tips
- Start with one transaction, then identify exactly which atomic boundary sharding removes.
- Prove revocation using a delayed refill after deletion, not only a successful invalidation message.
- Stress the database with total cache loss and one viral key before claiming read scalability.
Important qualifications
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.