See It Flow
System Design · Trade-offs · ~11 min read

URL Shortener — Trade-offs & Decisions

Every significant architectural decision in the URL shortener involves a real trade-off, workload-dependent rather than universally correct. This page revisits the core decisions with explicit alternatives, consequences, and selection criteria — the format expected in a Staff Engineer interview.

Mangesh MandlikLast updated:
Decision 01

ID Generation Strategy

How short codes are generated determines uniqueness handling, predictability, and coordination complexity. The right choice is workload-dependent — there is no single universally correct answer.

ApproachAdvantagesTrade-offs
Sequence + Base62Zero random-collision probability; simple; compact, predictably-growing code lengthSequential values are trivially enumerable; central sequence is a single allocation dependency (though a sequence can sustain substantial concurrent allocation — for example PostgreSQL's CACHE option lets each session pre-allocate a block of values, at the cost of gaps and out-of-order values across sessions; no throughput figure is claimed here)
Random Base62Non-enumerable; no cross-node coordination needed; large keyspace at 7+ charactersNon-zero per-attempt collision probability requiring bounded retry; retry rate rises as namespace occupancy grows
Hash-derived codeDeterministic — same input always produces the same code, if that is a desired product propertyTruncated-hash collisions are meaningful and need conflict detection; determinism is unwanted if the same URL should be allowed different codes for different owners/campaigns
Snowflake-style + Base62Decentralized unique generation without a DB round-trip; rough time-ordering useful for debuggingRequires worker-ID allocation and clock-rollback handling; Base62-encoding does not make the value unpredictable — see Architecture Deep Dive
ID generation approaches

Selection criteria

Weigh: uniqueness guarantee strength required, predictability/enumerability requirements, coordination overhead the team is willing to operate, write throughput, whether generation must happen independently across multiple regions, resulting code length, collision/retry complexity, and general operational complexity.

Decision 02

Permanent vs Temporary Redirect

The redirect status is a product decision with infrastructure consequences — not a fixed 'correct' choice, and not simply 'one caches, one doesn't.'

ChoiceFavorsCosts
301 Moved PermanentlyLower repeat-request server load if the client/intermediary actually caches it (status + caching headers + client behavior together determine this)Reduced visibility into repeat clicks from a caching client; risky if destinations can change after creation, since a caching client may not re-check
302 Found (temporary)Every request can reach the service — full analytics visibility and safe support for destinations that can changeMore requests reach the server for popular/repeat-visited links, compared to a client that fully caches a 301
307 Temporary RedirectSame intent as 302, but explicitly preserves the original HTTP method — relevant if the original request was not a GETSame considerations as 302
301 vs 302/307

What actually drives the decision

Compare: repeat-request server load, client/intermediary caching behavior (governed by status plus explicit Cache-Control/Expires headers, not status alone), analytics visibility needs, whether destinations are mutable after creation, and how rollback/change behavior should work. A design that needs exact click analytics or supports editable destinations should lean toward a temporary-redirect posture; a design with genuinely immutable, high-traffic destinations can lean toward permanent-redirect caching to reduce load. Avoid stating either as universally correct — see Architecture Deep Dive → Redirect Semantics and HTTP Caching.

Decision 03

Cache Architecture

How much caching to add, and where, trades latency and database offload against consistency and operational complexity.

OptionFavorsCosts
No cacheSimplest possible system; one source of truth to reason aboutDatabase absorbs full read volume directly; does not scale past a comparatively modest read rate
Distributed cache onlyLarge database-offload with a single shared, consistent cache tierEvery distributed-cache round-trip still adds a network hop; a single very hot key still concentrates load on one cache node/shard
Local (process) + distributed cacheLowest possible latency for the hottest keys; spreads hot-key load across app instances instead of one shared cache nodeAdds a second staleness layer that needs its own expiry-awareness; more moving parts operationally
Cache architecture options

Consistency needs defensive correctness, not just a long TTL

A long TTL with explicit invalidation on mutation is a strong, efficient default — but it does not achieve perfect consistency. Failed/delayed invalidation, a local cache layer that wasn't invalidated, races between a write and a concurrent read repopulating the cache, and ordinary expiry all still require defensive correctness (bounding cache TTL by mapping expiry, treating the database as authoritative on any doubt). See Architecture Deep Dive → Cache Hierarchy and Cache Expiry Correctness.

Decision 04

Analytics Collection

Analytics collection adds work on every redirect. How and where that work happens has real consequences for redirect latency and database health, and the design must state explicitly whether analytics loss is acceptable.

StrategyFavorsCosts
Synchronous write on redirectSimple single code path; no additional infrastructure; no event lossAdds write latency directly to the redirect path; a high redirect rate becomes a proportional write rate against the same database serving lookups — this competes with, and can starve, the read path
Asynchronous durable queueRedirect path is not affected by analytics write latency; queue absorbs traffic bursts; failures in the analytics pipeline don't affect redirect availabilityAnalytics becomes eventually consistent; queue/worker infrastructure to operate; requires an explicit policy for producer failure
Best-effort async / sampled emissionLowest overhead on the redirect path; simplest failure behavior (drop and move on)Not suitable if the product requires exact, lossless click counts
Analytics write strategies
Decision 05

Database Choice

The URL mappings table has a simple schema and a single dominant access pattern — primary-key lookup by short code — which narrows, but does not eliminate, the database decision.

OptionFavorsCosts
Relational (e.g. PostgreSQL/MySQL)Simple key lookup and uniqueness constraints map directly onto the workload; transactions; mature replication tooling; modest mapping volume under the assumed scaleRequires schema management; horizontal write scaling needs more deliberate design than a natively-partitioned store
Distributed key-value / NoSQLHorizontal partitioning as a first-class capability; strong fit for very large global datasets or multi-region active-active writesConditional-write-based uniqueness/collision detection is often more complex to implement correctly than a native UNIQUE constraint; for a comparatively modest dataset with a good cache, provides no proportionate advantage over a well-tuned relational database
URL mapping storage options
Decision 06

Deterministic Deduplication vs Multiple Codes per URL

Whether the same long URL should always produce the same short code is a genuine product decision, not an automatic optimization — and it is missing from a purely technical design discussion.

OptionAdvantagesCosts
Same long URL → same short codeFewer stored mappings; creation looks idempotent to the callerOwnership, campaign, and expiry semantics become complicated when multiple owners/campaigns want the same destination; concurrent creation of the 'same' URL needs explicit uniqueness/dedup handling to avoid a race
Every creation can produce a new codeIndependent analytics/campaign attribution per code; independent TTL/owner/policy per mapping; simpler creation semantics (no dedup lookup needed)Duplicate destinations consume additional namespace and storage
Deduplication options

If global deduplication is required, define precisely which fields participate in the identity check (e.g. long_url alone, or long_url plus owner_id) rather than blindly enforcing uniqueness on a raw URL hash — different owners, campaigns, or expiry policies for the same destination are common, legitimate product requirements that a naive dedup rule would break.

Decision 07

Random Codes vs Enumerable Codes

Uniqueness and predictability are separate concerns and deserve a separate decision — treating Snowflake-vs-sequence as the answer to enumeration conflates the two.

SchemePredictabilityCost
Sequential / Base62-encoded counterSimple and compact, but the sequence is directly enumerable — anyone can iterate through codesNone beyond the enumeration risk itself; uniqueness is free
Random codesNot enumerable by iterationRequires collision handling (UNIQUE constraint + bounded retry) since uniqueness is no longer structurally guaranteed
Obfuscated/permuted sequential IDsUnique like a sequence, without exposing the obvious underlying sequence to an observerIntroduces a permutation/key-management scheme that must itself be operated correctly and kept effectively secret
Predictability trade-offs by code scheme
Decision 08

Read from Primary vs Read Replica on Cache Miss

Once a request misses the cache, it must read from somewhere authoritative — and whether that read goes to the primary or a replica is its own trade-off.

TargetFavorsCosts
PrimaryStrongest freshness — always sees the latest committed writeCompetes directly with the write path for the same database capacity
Read replicaIsolates cache-miss read load from the write path entirelyMay be momentarily stale if replication is asynchronous, which can surface as a false-negative for a very recently created mapping
Cache-miss read target

Eager cache population immediately after a successful write makes the replica-staleness window rare in practice — most reads for a brand-new mapping are served from cache before they would ever reach a replica. The residual risk is a cache miss (eviction, cold start, or population failure) landing on a replica that has not yet replayed a very recent write. See database scaling for the general read-replica mechanics, Architecture Deep Dive → Database Access & Indexing, and Failure & Scale → Read Replica Lag for how this is detected and mitigated.