URL Shortener System Design Interview Questions & Follow-Ups
How to reason through a URL shortener design in an interview — a clear opening sequence, the signals that separate strong candidates from memorized answers, a full question bank across every major area, and a 30-second recall card.
Interview Structure
A URL shortener interview rewards a clear reasoning sequence over a memorized architecture. The goal is to show you can scope, estimate, design, and reason about failures — not recite a fixed diagram.
Recommended reasoning sequence
1. Clarify product semantics (dedup? mutable destinations? custom aliases? expiry? analytics?). 2. Establish workload assumptions explicitly, rather than assuming fixed numbers. 3. Estimate mapping volume and redirect/write rates from those assumptions. 4. Start with a simple app + database design. 5. Identify the repeated-read pressure this creates. 6. Introduce a cache to address it directly. 7. Choose a short-code generation strategy based on the actual requirements (not a default). 8. Separate analytics from the redirect path. 9. Reason about failure modes and hot keys. 10. Evolve the architecture only when a genuine scale/SLO signal requires it.
The prompt itself may be phrased as "design TinyURL" or "design Bitly" rather than the generic "design a URL shortener" — these are the same problem with the same reasoning sequence above; don't let the brand name in the prompt change the fundamentals you reach for.
Editorial note: this guidance is the author's synthesis of common system-design interview practice, not any specific company's rubric or interviewer feedback.
What Strong Candidates Do
This interview is used to separate candidates who memorize architectures from candidates who reason about systems under stated assumptions.
Strong signals
✅ State the read/write ratio and traffic-skew assumption explicitly before designing around it — naming it as an assumption, not a fixed universal fact. ✅ Calculate storage and explain why it usually isn't the bottleneck for this workload — while noting that this depends on the assumed mapping volume and retention policy. ✅ Describe precisely when and why the naive design breaks — 'hot rows get re-read on every redirect' is stronger than 'the DB can't scale.' ✅ Name multiple ID-generation approaches and justify a choice based on stated requirements, rather than defaulting to one. ✅ Raise the redirect-status trade-off (301 vs 302 vs other signals) unprompted — it signals product+infrastructure thinking. ✅ Trace the cache-hit path, cache-miss path, and write path separately. ✅ Reason about which component fails first under a specific stated failure scenario, rather than reciting a fixed failure order. ✅ Describe evolution stages by the signal that triggers each transition, not a memorized traffic number.
Common mistakes
❌ Jumping straight to a distributed architecture without first presenting the naive design and its actual breaking point. ❌ Recommending a database technology without justifying it against the workload's actual requirements. ❌ Treating analytics as an afterthought, then bolting it on synchronously. ❌ Reducing redirect-caching trade-offs to 'one status code caches, the other doesn't' — real HTTP caching behavior also depends on cache-control headers and client behavior. ❌ Asserting that one specific component 'always' fails first in a viral spike, instead of reasoning from the stated scenario.
Question Bank: Likely Questions & Follow-Ups
A structured bank of the questions most likely to come up, organized by the area they probe. Each includes a strong-answer direction, likely follow-ups, and links into the deeper technical pages.
Scope & Requirements
Should the same long URL always produce the same short code?
State it as a product decision, not a default. Deterministic dedup (same input -> same code) is simpler to reason about and avoids redundant rows, but breaks if different owners/campaigns should get independent codes for the same URL. See Trade-offs -> Deterministic Deduplication vs Multiple Codes per URL.
- What data would you hash to define "the same URL" for dedup purposes? — Tests whether the candidate considers query-string/fragment normalization, not just raw string equality.
- What if a user wants two separate short codes for the same destination for two different campaigns? — Tests whether the candidate recognizes dedup is a per-owner or per-context decision, not global.
Can the destination URL change after creation?
If yes, this is a small but consequential requirement: cache invalidation on update becomes mandatory, not optional, and analytics attribution must handle a mapping's destination changing mid-life.
- How would you propagate an update to a cached mapping across multiple cache nodes? — Tests whether the candidate reaches for direct invalidation rather than relying on TTL expiry alone.
Are custom aliases required, and do they interact with the generated-code namespace?
If yes, custom aliases and generated codes share one namespace and one uniqueness constraint, so schema length must accommodate the longer of the two, and a race between a user claiming an alias and the system generating the same string must be handled by the database constraint, not assumed away. See Failure & Scale -> Custom Alias Race.
- Would you reserve a namespace of short generated codes so they never collide with human-chosen aliases? — Tests awareness that a shared namespace needs an explicit collision policy, not just a bigger keyspace.
Do links expire, and does analytics need to be exact or can events be sampled/lost?
Both change the design meaningfully: expiry requires distinguishing 'not found' from 'expired' end-to-end (cache, database, response), and accepting approximate analytics allows a much simpler, higher-throughput event pipeline than an exact-count requirement would.
- If analytics must be exact, what does that do to your event-delivery guarantees? — Tests whether the candidate connects an exactness requirement to explicit deduplication and at-least-once handling, not just 'use Kafka'.
Capacity
What is the redirect-to-create ratio, and why does it matter?
Frame it as a stated assumption (a common illustrative range is 100:1 to 1000:1 for public-link products), not a fact — but whatever ratio is assumed, it should directly justify a cache-first architecture, since the overwhelming majority of traffic is reads against a small, highly skewed set of hot mappings.
- What would change if the ratio were closer to 2:1 instead of 100:1? — Tests whether the candidate recognizes that a low read/write ratio would materially weaken the case for aggressive read caching as the central design decision.
When does storage actually become a concern for this system?
Storage for URL mappings themselves is usually small relative to modern infrastructure even at large mapping counts — but this is workload-dependent (it changes with average URL length, metadata retained per mapping, and retention policy), and analytics-event storage is a completely separate, much larger-volume concern that should be estimated separately.
- How does analytics-event storage volume compare to mapping storage volume? — Tests whether the candidate treats these as two distinct estimates rather than one number.
How does cache hit ratio change database read load, and what assumptions go into that number?
Present it as a formula (effective DB load ≈ (1 - hit_ratio) × total reads) with an explicitly labeled illustrative hit ratio, not a fixed universal percentage — the real ratio depends on traffic skew, TTL policy, and cache sizing. See Complete Design -> Caching Strategy.
- What traffic pattern would make cache hit ratio much lower than assumed? — Tests whether the candidate connects hit ratio to the underlying access-pattern assumption (Zipf-like skew) rather than treating it as a given constant.
ID Generation
Why Base62, and how many codes does 6/7/8 characters actually provide?
Base62 ([0-9a-zA-Z]) packs more information per character than hex or Base10, keeping codes short and URL-safe. Namespace size grows exponentially with length (62^6 ≈ 56.8B, 62^7 ≈ 3.5T, 62^8 ≈ 218T) — the right length is chosen from expected volume, desired occupancy, and acceptable retry rate, not memorized as a fixed number. See Architecture Deep Dive -> Short-Code Generation.
- If you expect 10 billion mappings over the system's lifetime, how would you pick a length? — Tests whether the candidate reasons from occupancy ratio rather than reciting a fixed 7-character answer.
Random vs sequential code generation — what's the real trade-off?
Sequential (auto-increment + Base62) is simple and collision-free but trivially enumerable — someone can iterate every code ever issued. Random Base62 is non-enumerable but introduces a small, manageable collision probability that must be handled with a uniqueness constraint and retry. See Trade-offs -> Random vs Enumerable Codes.
- Does hiding the code space actually stop someone from discovering private links? — Tests whether the candidate distinguishes non-enumerability from true access control — random codes reduce casual discovery, they do not replace authorization if links are meant to be private.
How do you handle collisions?
A database UNIQUE constraint on short_code plus a bounded retry loop on conflict. Distinguish per-attempt collision probability (n/N, the operationally relevant number) from the birthday-bound probability that any collision has ever occurred historically — they answer different questions. See Architecture Deep Dive -> Collision Handling.
- At what occupancy ratio would you increase the code length instead of just retrying more? — Tests whether the candidate recognizes that retry rate rising past a comfortable threshold is the actual signal, not a fixed percentage memorized in advance.
Does deploying multiple app servers require Snowflake-style ID generation?
No — this is a common but incorrect assumption. A central database sequence or a random-Base62-with-retry scheme both work fine with many concurrent app servers, because uniqueness is enforced by the database, not by the app tier. Snowflake-style generation solves a different problem: decentralized generation without any database round-trip.
- So when would you actually reach for Snowflake here? — Tests whether the candidate can name specific reasons — removing DB dependency for ID allocation, genuine multi-region local generation, or extremely high creation throughput — rather than treating it as a mandatory upgrade.
What happens when a Snowflake node's clock moves backward?
It can produce duplicate or out-of-order IDs for the affected window, since the scheme relies on monotonically advancing time. Production implementations typically detect clock rollback and refuse to generate IDs (or wait) until the clock catches up. See Architecture Deep Dive -> Distributed ID Generation Mechanics.
- Are Snowflake IDs non-enumerable once Base62-encoded? — Tests a subtle but important point: encoding does not equal obfuscation. A Snowflake ID's timestamp/worker structure is still present in the encoded value, so it remains predictable unless further permuted.
Redirect Semantics
301 vs 302 — which should a URL shortener use, and why?
Not a fixed universal answer — a genuine product trade-off. 301/308 signal permanence and let browsers/CDNs cache the redirect, reducing origin load but making the destination effectively harder to change and hiding the click from analytics on cached hits. 302/307 signal 'not permanent,' keep the origin in the loop for every redirect (accurate analytics, immediate effect of destination changes), at the cost of higher redirect-serving load. See Trade-offs -> Permanent vs Temporary Redirect.
- If a link's destination can be edited after creation, does that change your answer? — Tests whether the candidate connects the earlier requirements question (mutable destinations) to this specific redirect-status decision.
Can a CDN cache redirects, and how does that affect analytics?
Yes for permanently-cacheable, publicly shareable links — but an edge-cached redirect never reaches the origin, so that click is invisible to server-side analytics unless a separate edge-logging mechanism exists. See Complete Design -> Production Evolution.
- How would you invalidate an edge-cached redirect if the mapping is updated or expires? — Tests whether the candidate has an explicit invalidation path rather than assuming edge caches simply expire in time.
What should happen for an expired link — is it 404 or 410?
A meaningful distinction: 404 says 'not found' (ambiguous — never existed, or is it gone?), while 410 Gone explicitly communicates that the resource existed and was intentionally/permanently removed, which is more precise for an expired-by-design mapping. Either can be a reasonable product choice, but it should be a stated choice.
- Should the response body differ for 'never existed' vs 'expired'? — Tests UX awareness beyond raw HTTP status: a clearer user-facing message can be given even if the underlying status code is shared.
Cache
Why use a cache at all here?
Because the workload is read-dominated with skewed (hot-key) access patterns — a small fraction of mappings receive the large majority of redirect traffic. Caching those hot mappings removes the majority of read load from the database with a comparatively small, well-understood correctness surface. See Complete Design -> Caching Strategy.
- What would happen to this argument if traffic were uniformly distributed across all mappings instead of skewed? — Tests whether the candidate understands caching's effectiveness is tied to the skew assumption, not caching in the abstract.
What is cache-aside, concretely, for this system?
Check the cache first; on a miss, read the authoritative database, populate the cache with an expiry-bounded TTL, then return. On write, eagerly populate the cache right after the durable database write completes, so the first redirect after creation is very likely already a hit.
- Why populate the cache on write instead of waiting for the first read to populate it? — Tests understanding of why write-time population matters specifically for freshly-created, soon-to-be-viral links (e.g. links shared on social media).
What happens when the cache is completely down?
Every redirect falls back to the database — latency rises and database load increases substantially, but redirects should still succeed (correctness over performance) as long as the database itself can absorb the load, possibly with reduced availability targets during the incident. See Failure & Scale -> Redis / Cache Unavailable.
- Does the app need any code path that doesn't assume the cache is present? — Tests whether the candidate designed the read path to genuinely tolerate cache absence, not just optimize for the happy path.
What happens when the cache restarts empty?
A burst of cache misses hits the database simultaneously for previously-hot keys — a cold-cache stampede. Request coalescing (one in-flight DB lookup per key) and gradual/prioritized warming for known-hot keys reduce the impact. See Failure & Scale -> Cold Cache / Cache Stampede.
- How would you identify which keys to pre-warm before traffic resumes? — Tests whether the candidate would use recent access logs/analytics to prioritize warming rather than warming blindly or not at all.
How do you stop a stampede on a single popular key becoming hot again?
Request coalescing/miss locking ensures only one request performs the actual database lookup for a given key while concurrent requests wait for that result, rather than all issuing redundant duplicate queries. See Architecture Deep Dive -> Cache Stampede.
- Does jittering TTLs help here too, and why? — Tests whether the candidate understands jitter prevents many keys from expiring in the same instant, which is a related but distinct cause of stampede-like load spikes.
How do you handle one extremely hot URL beyond normal caching?
A shared distributed cache still concentrates one hot key's traffic on whichever node/shard owns it. A local, process-local cache on each app server for the small set of currently-hottest keys removes that traffic from the shared cache entirely. See Architecture Deep Dive -> Hot-Key Protection.
- Why doesn't clustering the shared cache automatically solve this? — Tests the key distinction: clustering spreads different keys across nodes, but one single key's traffic still lands on one owning node/shard regardless of cluster size.
How do you ensure a cached link never outlives its own expiry?
Bound the cache TTL by the mapping's remaining lifetime: cache_ttl = min(configured_ttl, expires_at - now), rather than applying a fixed TTL regardless of expiry. On explicit update or deletion, invalidate directly instead of waiting for TTL.
- What happens if a request arrives in the narrow window after expiry but before the cache entry naturally expires? — Tests whether the candidate has a consistent answer here — this is exactly what the bounded-TTL approach is designed to prevent.
Would you negative-cache nonexistent codes, and what's the risk?
Yes — it protects the database from repeated lookups of codes that will never resolve, which matters against both random guessing and scanning traffic. The risk: a negative entry must not make a just-created mapping or newly-claimed alias appear falsely missing, so creation should invalidate any matching negative entry or the negative TTL should be kept short.
- How would a malicious scanner exploit negative caching if it weren't handled carefully? — Tests connecting this to the abuse/scanning failure scenario, not just the caching mechanism in isolation.
Database
Why a relational database here rather than NoSQL?
Frame it as workload-dependent rather than universally correct: the access pattern is simple key lookups with a strict uniqueness requirement, which a relational database with a primary-key index handles well and gives strong consistency for. A distributed KV/NoSQL store is a reasonable alternative if write throughput needs to scale beyond a single primary far more than this workload assumes, or if the team already operates one at scale. See Trade-offs -> Database Choice.
- What specifically would push you toward a distributed KV store instead? — Tests whether the candidate can name the actual triggering condition (write throughput beyond a single primary, existing operational expertise) rather than treating one database family as inherently correct.
What index is required, and why?
A unique index on short_code, since every read and every write conflict-check depends on fast, exact lookup by that column. Additional indexes (e.g. on owner_id, if accounts are in scope) are added only if a query pattern actually needs them.
- Would you index long_url or a hash of it? — Tests whether the candidate connects this to the dedup decision — a dedup lookup needs an index on the URL (or its hash); a purely generative design does not.
Do cache misses read from the primary or a replica?
A genuine trade-off, not a default: reading from a replica offloads the primary but risks replica lag returning a stale or momentarily-missing result for a very recently written mapping; reading from the primary guarantees freshness but adds load to the single most critical write path. See Trade-offs -> Primary vs Replica Reads.
- How would you mitigate the staleness risk if you chose replica reads? — Tests whether the candidate proposes something concrete — e.g. a short grace period reading from primary right after creation, or accepting brief staleness as a stated trade-off.
What can replica lag actually break?
A cache miss hitting a lagging replica right after a write can return 'not found' for a mapping that was just created, or a stale long_url after an update — this is worse than a slow response, it's an incorrect one. See Failure & Scale -> Read Replica Lag.
- How would a client experience this bug in practice? — Tests whether the candidate can describe the concrete symptom — e.g. 'link works for the creator immediately but 404s for the first few other visitors' — not just the abstract mechanism.
What happens if the database primary fails?
Depends on the failover mechanism in place: with automatic failover, a replica is promoted and writes resume after a bounded gap during which writes fail or queue; without it, writes fail until manual intervention. Reads can often continue from replicas throughout. Treat any specific failover-time number as illustrative, not a hard guarantee. See Failure & Scale -> Database Primary Failure.
- Do redirects (reads) need the primary at all in steady state? — Tests whether the candidate recognizes reads can be served from cache and replicas, so primary failure primarily threatens the write path (new URL creation), not existing redirects.
How do custom-alias races get resolved?
The database UNIQUE constraint is the actual source of truth for who wins — the first successful INSERT/commit wins, and the loser receives an explicit conflict response, even if both requests appeared to pass an earlier availability check. See Failure & Scale -> Custom Alias Race.
- Why isn't an earlier read-then-write check on the alias enough? — Tests understanding of the check-then-act race condition — only a database-enforced constraint at write time is airtight.
Analytics
Why not write analytics synchronously on the redirect path?
Because it would couple redirect latency and availability to the analytics store's health, and would make the database (or analytics store) absorb write volume proportional to all redirect traffic rather than just creation traffic — a significant and unnecessary risk for a metric that doesn't need to be immediately consistent.
- Is there any analytics signal that genuinely needs to be synchronous? — Tests whether the candidate can articulate a boundary — e.g. abuse/rate-limit counters might need tighter consistency than click analytics, and would be architected separately.
If the event broker (Kafka/SQS) is unavailable, do redirects fail?
They should not — the analytics-emission step must have an explicit bounded failure policy (a short publish timeout, non-blocking producer, and an explicit choice to drop/sample/buffer within strict bounds on failure) so that redirect serving never waits indefinitely on the broker. See Failure & Scale -> Analytics Broker Unavailable.
- What's the actual cost of choosing to drop events during an outage? — Tests whether the candidate can reason about the trade-off explicitly (some click data permanently lost) rather than assuming 'async' means no cost at all.
Do we need strict ordering of analytics events per URL?
Usually not, if the analytics product needs commutative aggregates (counts, counts-over-time) rather than an exact event sequence — partitioning purely by short_code buys per-URL ordering at the cost of concentrating a viral URL's entire event volume on one partition. See Architecture Deep Dive -> Analytics Partitioning.
- What would break if you spread a viral URL's events across partitions instead? — Tests whether the candidate recognizes that spreading breaks per-URL ordering but is fine if aggregation happens downstream regardless of arrival order.
What happens to a viral URL's events if they're keyed strictly by short_code?
All of that URL's events land on a single partition, creating a hot partition and, in turn, a hot consumer — this is exactly the same hot-key problem seen in caching, but manifesting in the event-streaming layer instead.
- Is this the same underlying problem as the Redis hot-key scenario? — Tests the candidate's ability to recognize the same conceptual pattern (skewed access to one key) recurring in a different subsystem.
How do you prevent duplicate click counting?
Include a stable event ID in each analytics event and define explicit deduplication semantics at query/aggregation time — at-least-once delivery can produce duplicates, and a storage engine's built-in merge behavior should not be assumed to handle this automatically unless verified.
- Is exact deduplication always necessary? — Tests whether the candidate recognizes approximate analytics can be an explicitly acceptable trade-off for cost/throughput, if the product doesn't need exact counts.
How do you recover from an analytics consumer backlog?
Scale out consumers to increase drain rate, and confirm the broker's retention window is longer than the time needed to catch up so no events are lost while backlogged — the redirect path itself is unaffected since analytics is fully decoupled from it. See Failure & Scale -> Analytics Queue Backlog.
- What if the backlog grows faster than you can add consumers? — Tests whether the candidate considers sampling or temporarily dropping lower-priority event fields as a release valve, rather than assuming infinite consumer scaling is always available.
Failure Scenarios
Redis (or the shared cache) becomes completely unavailable — walk through what happens.
Reads fall through to the database for every request, raising latency and database load; writes are unaffected since the database is authoritative. Redirects should keep working, just slower, as long as the database can absorb the load. See Failure & Scale -> Redis / Cache Unavailable.
- At what point would degraded-but-working become an outage? — Tests whether the candidate identifies the actual limiting resource — database connection pool or CPU saturation — rather than giving a vague answer.
Redis comes back, but the cache is completely cold — is that the same as Redis being down?
No — it's a distinct scenario: the cache is reachable and correct, but empty, so it briefly behaves like an all-misses cache, which is the cold-cache-stampede scenario rather than the cache-unavailable scenario. See Failure & Scale -> Cold Cache / Cache Stampede.
- Would your mitigation for cache-down also help here? — Tests whether the candidate recognizes coalescing/warming addresses this scenario specifically, while the cache-down scenario's mitigation is simply 'tolerate reading from the DB'.
The database primary is unavailable — what's actually affected?
New URL creation (writes) is affected; existing redirects (reads) can often continue from cache and read replicas. The exact recovery path depends on whether automatic failover is configured. See Failure & Scale -> Database Primary Failure.
- What if a code was just created seconds before the primary failed, and isn't cached or replicated yet? — Tests whether the candidate identifies this narrow, genuinely lost-availability window rather than claiming everything keeps working.
A read replica is stale — what does that break, specifically?
It can make a cache miss return 'not found' for a mapping that was just created, or return a stale destination after an update — an incorrect answer, not just a slow one. See Failure & Scale -> Read Replica Lag.
- How would you detect this is happening in production? — Tests whether the candidate names a concrete signal — replication lag metric, or an elevated not-found rate immediately following creation events.
One URL suddenly becomes viral — walk through which component is under the most pressure, and when.
There's no single fixed answer — it depends on the current cache state and deployment. If the URL is already cached, the shared cache absorbs almost all of it and the app tier's request-handling capacity becomes the limiting factor. If it's freshly created and not yet cached, the first burst can hit the database directly until the cache populates, making cache-population speed and stampede protection the deciding factor.
- Why is 'the app tier always fails first' not a safe universal claim? — Tests explicitly against the memorized wrong answer — the real bottleneck depends on the scenario's specific starting conditions, not a fixed component ranking.
An analytics queue becomes unavailable — do redirects fail?
No, if the design correctly isolates analytics emission with a bounded failure policy — redirects should keep working with some data loss/degradation in analytics, not an availability incident in the redirect path. See Failure & Scale -> Analytics Broker Unavailable.
- What would indicate the isolation wasn't actually implemented correctly? — Tests whether the candidate can name the failure symptom of a design mistake here — e.g. redirect latency rising in step with broker latency.
A random-code scanning attack causes millions of cache misses — what protects the system?
Negative caching for nonexistent codes absorbs repeated lookups of the same guessed code; rate limiting by IP/client bounds the request rate regardless of hit/miss; and monitoring for an abnormal not-found rate helps detect the pattern early. See Failure & Scale -> Malicious Random-Code Scan / Abuse.
- Why doesn't a large keyspace alone prevent this? — Tests whether the candidate recognizes a large keyspace makes success unlikely per-guess, but a scanner can still generate enormous request volume regardless of eventual success rate — the defense is rate/negative-caching, not keyspace size.
A mapping has expired but a stale cache entry still redirects successfully — how does this happen, and how do you prevent it?
It happens when the cache TTL isn't bounded by the mapping's own expiry (a fixed TTL outliving expires_at). Prevented by cache_ttl = min(configured_ttl, expires_at - now) and explicit invalidation on any expiry-relevant mutation. See Failure & Scale -> Mapping Expiry vs Stale Cache.
- Is there a race between checking expiry and serving from cache? — Tests careful reasoning about a narrow timing window and whether the candidate's fix actually closes it or just narrows it.
Scale Curveballs
The system needs to grow from 1k to 1M redirects/sec — what changes?
Not a single lever — cache hit ratio and cache-node capacity become the dominant scaling factors (most traffic should never reach the database at this point), the app tier needs to scale horizontally behind a load balancer, and hot-key protection (local caching) becomes far more important as concentration increases at higher absolute volume. See Complete Design -> Production Evolution.
- Does the database need to scale by the same factor? — Tests whether the candidate recognizes the database mainly serves cache misses and writes — its required scale-up is much smaller than the raw redirect-rate growth, if caching is effective.
90% of traffic suddenly targets one URL — what's your response?
This is the hot-key scenario, not a general capacity problem: a shared-cache hit already helps a lot, but concentrated traffic on one key benefits specifically from a local process-cache layer and possibly explicit rate-shaping if the origin (analytics, verification steps) can't absorb the volume. See Architecture Deep Dive -> Hot-Key Protection.
- Would adding more cache nodes help here? — Tests the hot-key vs. general-capacity distinction again — more nodes spread other keys' traffic, not this one key's traffic.
The product needs to deploy across multiple regions — what's the hardest part?
Not the redirect-serving path (that can be replicated read-heavy infrastructure per region relatively simply) — the hard part is write-side: where new codes are generated, how global uniqueness is guaranteed if multiple regions can create mappings, what a redirect in another region sees immediately after creation (before replication completes), and how caches are invalidated cross-region. See Complete Design -> Production Evolution.
- Could you avoid most of this complexity if only one region needs to accept writes? — Tests whether the candidate recognizes a single-write-region-plus-multi-region-read-replicas design sidesteps most of the hard distributed-writes problems.
The product needs to support editable destinations — what does that touch?
Cache invalidation on update becomes mandatory (not just TTL-based), and analytics needs to record which destination a given click was actually attributed to if a mapping's destination changes over its lifetime and that history matters to the product.
- Should an update be versioned, or should it simply overwrite the row? — Tests whether the candidate considers audit/history requirements as a distinct, separately-scoped decision.
The product needs to support private/expiring links — what does that touch?
Authentication/authorization on the redirect path (not just creation), a 3-way expiry distinction (never / TTL-based / manually revoked) implemented consistently across cache and database, and negative-cache handling that doesn't leak whether a private link exists to an unauthorized requester. See Complete Design -> Requirements.
- How would negative caching need to change if link existence itself is sensitive? — Tests a subtle privacy point: a naive negative cache could still let an unauthorized client distinguish 'doesn't exist' from 'exists but you can't see it' via timing or response shape.
The product needs to support billions of retained mappings — what changes?
Storage volume for mappings themselves grows but is usually still manageable on modern infrastructure; the more material changes are index size/lookup performance at that row count, and whether the ID-generation keyspace and code length remain comfortably sized for that occupancy.
- Would you need to increase code length at that scale? — Tests whether the candidate connects this back to the collision-math discussion — occupancy ratio, not the raw count alone, determines whether length needs to grow.
The product needs very high URL-creation throughput — what changes?
This is exactly the scenario where a central database sequence's throughput ceiling (not its correctness) becomes the real constraint, making decentralized ID generation (Snowflake-style, or pre-allocated ranges) genuinely worth adopting rather than optional. See Architecture Deep Dive -> Distributed ID Generation Mechanics.
- Would random-Base62-with-retry still work at this throughput? — Tests whether the candidate recognizes retry-rate under contention rises with concurrent creation throughput, and reasons about when that becomes a real bottleneck rather than a theoretical one.
The product needs exact global analytics across regions — what changes?
This conflicts with the earlier 'approximate analytics is fine, keep it fully decoupled' default — exact global counts require either a single global aggregation point (a consistency/latency cost) or a carefully designed idempotent, deduplicated multi-region pipeline, which is meaningfully more complex than the single-region async design.
- Is this requirement compatible with dropping events during a broker outage? — Tests whether the candidate recognizes 'exact' and 'best-effort drop-on-failure' are in direct tension, and that satisfying both requires a different, more expensive design (e.g. durable buffering instead of dropping).
Quick Reference Card
Everything to recall in 30 seconds — framed as assumptions and decision points, not universal facts.
- Read/write ratio and traffic skew are stated assumptions, not fixed universal numbers — but for a public-link product they typically justify a cache-first design
- Storage for mappings is rarely the bottleneck; analytics-event volume is a separate, larger estimate
- Random Base62 with a UNIQUE constraint + bounded retry is a reasonable simple default; Snowflake-style generation is for decentralized high-throughput needs, not mandatory once multiple app servers exist
- Redirect status (301/302/etc.) is a product trade-off between origin caching load and analytics/update freshness — never reduce it to 'one caches, one doesn't'
- Cache-aside with eager population on write, TTL bounded by the mapping's own expiry, explicit invalidation on update/delete
- Negative caching + request coalescing/stampede protection + a local hot-key cache layer round out cache correctness
- Analytics is fully decoupled and asynchronous — but the async producer still needs an explicit bounded failure policy, not an assumption of automatic safety
- Failure order under a viral spike depends on cache state and deployment specifics — reason through the scenario, don't recite a fixed component order
- Stage transitions (single-server -> cache -> LB/replicas/queue -> multi-region) are triggered by measured SLO/saturation signals, not memorized traffic thresholds
- Multi-region's hard part is the write path (global uniqueness, cross-region cache invalidation) — not redirect serving itself