Review: Bitly
Create and resolve unique short links with bounded revocation, safe retries, and optional analytics.
Clarify this prompt before choosing components
Ask the highest-impact questions first, agree on priorities, and record the interviewer’s answers. If a detail is unspecified, state an assumption and explain which decision depends on it. The numbers in this worked lesson are exercise assumptions, not company facts.
| Ask the interviewer | Why the answer changes the design |
|---|---|
| Public redirect or authenticated private links? | Private links require authorization on every serving/cache path. |
| Can links change, expire or be revoked? | Define cache staleness and a revocation boundary. |
| Custom aliases and analytics accuracy? | Uniqueness belongs to the allocator; click accounting has a separate loss/duplicate contract. |
Use the Interview Playbook for the shared 45-minute sequence and fresher, intermediate, senior and staff evidence. Start with one complete operation. Extend the answer with the failure/recovery traces below, then defend a workload-specific alternative and a measurable rollout. Scope and evidence matter more than a large component list.
Bitly: Understand the building blocks
Choose a mechanism because it satisfies a requirement. These explanations describe the exercise design, not undisclosed implementation choices at the named company.
Bitly: Quick vocabulary
| Term | Meaning and role in this exercise |
|---|---|
| API: Application Programming Interface | The contract for requests, responses and errors between components. An API route does not itself enforce every business invariant. |
| SQL: Structured Query Language | A language for querying/manipulating relational data. Correctness still depends on constraints, transactions and isolation choices. |
| JSON: JavaScript Object Notation | A structured text interchange format. Bound payload size and parsing cost; it is not a database ownership protocol. |
| HTTP: Hypertext Transfer Protocol | The request/response protocol used by web APIs and media fetching. Application retries still need safe operation identities. |
| DNS: Domain Name System | Resolves names to network addresses. An untrusted fetch policy must also validate resolved targets and redirects. |
| TLS: Transport Layer Security | Protects a network connection’s confidentiality/integrity and peer authentication under its configuration; it is distinct from endpoint-only message encryption. |
| HTTPS: HTTP over TLS | Protects the HTTP connection. It does not authorize every referenced object or make external operations atomic. |
| URL: Uniform Resource Locator | Names a resource and access location. Normalize carefully and do not trust an imported address as a safe server fetch destination. |
Bitly: Idempotency key and stored operation result
What it means: Idempotency means replaying the same logical operation produces no additional business effect. A key identifies that operation under a defined scope.
Why this problem needs it: A lost creation response must not allocate another alias.
How to use it: Atomically associate a request key and payload fingerprint with its effect and result. Return the stored result on replay; reject incompatible payload reuse.
Example in this design: Retry opA after K7 commits returns K7.
When it fits: Needed when a timeout can hide a successful commit or a queue can redeliver.
Alternative and limits: Unique business identities can enforce selected effects. A cache of recent requests alone may expire too early or race; local keys cannot control an uncooperative external provider.
Bitly: Cache-aside, Redis and time to live (TTL)
What it means: A cache keeps reusable copies for faster reads. Cache-aside checks the cache, loads an authoritative source on a miss, then fills it. TTL is the declared entry lifetime.
Why this problem needs it: Popular redirects should reuse eligible destinations without resetting expiry.
How to use it: Key by the correct tenant/object/version, retain an absolute freshness deadline, bound entry size, and apply the stated invalidation or version-fence policy. Redis is one possible in-memory implementation.
Example in this design: An old fill arriving after disablement must not extend the original deadline.
When it fits: Useful when repeated eligible reads justify memory and the tolerated staleness is explicit.
Alternative and limits: Direct indexed database reads may suffice. A cache is not automatically durable authority; shorter TTL does not prove immediate revocation.
Bitly: Relational database transaction and constraints
What it means: A relational store keeps structured rows; a transaction commits a bounded group of changes together. Constraints reject specified invalid states.
Why this problem needs it: Two creators must not own the same alias.
How to use it: Use primary/unique keys and a chosen isolation/locking protocol for the actual invariant. Atomically persist related rows and the operation result before acknowledgement.
Example in this design: A unique alias constraint rejects the second K7 insert.
When it fits: Useful for bounded cross-row correctness, joins and access paths that fit the relational model.
Alternative and limits: Key/value stores can fit simpler keyed operations. “SQL” alone does not prevent lost updates, validate every cross-row rule or coordinate an external provider.
Bitly: Server-Side Request Forgery (SSRF)
What it means: SSRF occurs when an attacker causes a server to fetch a destination it should not reach, such as internal or metadata services.
Why this problem needs it: Destination preview fetches introduce a separate server-side risk.
How to use it: Validate normalized destinations and resolved addresses, enforce outbound network restrictions, and recheck redirects/resolution under the chosen policy. Bound bytes, time and decompression.
Example in this design: A preview URL redirecting to a private service is rejected; a simple client redirect is a different boundary.
When it fits: Required for crawlers, imported URLs or any untrusted server-side fetch target.
Alternative and limits: Fetching only a trusted allowlist is simpler. Blocking one URL string does not protect against redirects, alternative address forms or resolution changes.
Understand the product before drawing boxes
A URL shortener turns a long destination such as https://example.org/events/october?school=42 into a link such as https://go.example/aB7xK2mQ. Alice creates the link and puts it in a school newsletter. A parent opens it; the shortener returns an HTTP redirect and the browser visits the destination. The shortener does not fetch the destination. That distinction keeps website latency and destination failures outside the redirect service.
The central tension is fast repeated reads versus control over an already distributed link. Alice may revoke a phishing link after thousands of readers have cached it. A mapping in a database is straightforward; making retries safe and making the revocation promise true across every serving layer is the actual design problem. This is an original interview design, not a description of Bitly's production implementation. The reference article was marked fetch-blocked in the supplied plan; exact source coverage remains unverified.
Requirements and scope
| ID | Requirement | Owner and check |
|---|---|---|
| R1 | An authenticated owner creates an immutable destination and receives a globally unique code. | The mapping database decides uniqueness; V1 and V2 test collisions and retries. |
| R2 | A visitor resolves an active code; missing and expired links never redirect. | Resolver checks state and deadline on every request, including hits; V3 and V4. |
| R3 | An owner revokes their link. Controlled serving layers stop redirecting within 60 seconds after commit. | Database state, cache deadlines, and invalidation; V5 and V6. |
| R4 | Custom aliases and expiration are supported extensions with the same ownership rules. | Unique namespace and logical expiry; V1, V3, V7. |
| R5 | Optional approximate click analytics never blocks a redirect. | Bounded event admission, recorded drops, and asynchronous aggregation; V8. |
Accounts, billing, previews, editable destinations, and exact billing-grade analytics are excluded. A code is a public identifier, not a secret or an authorization token. If the destination needs access control, that destination must enforce it. Revocation cannot erase a URL already learned by a user or stop a client from visiting it directly.
| Operation | Illustrative target | Measurement conditions |
|---|---|---|
| Redirect | p99 under 100 ms; 99.95% successful resolution for valid active codes | From regional service ingress to response headers, 57,870 requests/s peak, normal healthy dependency conditions; report cache-hit and miss histograms separately. Excludes destination loading and visitor network latency. |
| Create | p99 under 300 ms | In the writer region at 579 requests/s; includes unique insert and transaction commit. Rate-limited or invalid inputs are separately counted. |
| Revoke | No controlled redirect more than 60 seconds after the revoke commit | Invalidator outage included; authoritative reads available, clock offset bounded to two seconds, and refresh requests have a one-second timeout. No stale-on-error serving. |
These are design targets to load-test, not measured results. Creation and revocation favor consistency when the writer is unavailable. Redirects can use unexpired cached records during a short outage; after their freshness deadline they fail with a retryable 503 rather than violate the revocation bound.
Capacity worksheet
Assume 10 million creations and one billion redirects per day, a fivefold peak, and 500 bytes per stored mapping. Dividing by 86,400 seconds gives approximately 116 creates/s and 11,574 redirects/s on average; peaks are approximately 579 and 57,870/s. One year adds 3.65 billion mappings, or 1.825 TB before indexes, replicas, and idempotency records. These invented workloads justify separating the read-heavy resolver from the write API; they do not require microservices for the initial low-traffic version.
At a 95% cache hit rate, about 2,894 origin reads/s remain at peak. A cold cache exposes all 57,870 requests/s, so the miss path needs an admission limit even if average performance is excellent. At an assumed 1 KB per cached entry including overhead, ten million entries use about 10 GB per copy. Size from a sampled working set and eviction measurements rather than assuming every created link must stay in memory. A viral code stresses one key; adding database shards distributes different keys but does not divide that one key's load.
Eight case-sensitive base62 characters provide 62^8 = 218,340,105,584,896 possibilities. With one billion occupied codes, a fresh uniform draw collides with probability about 0.00000458. This is the next-draw probability, not the probability of any collision in the entire history. Collision handling remains mandatory. Using all eight-character codes does not make links confidential.
Data model and access paths
| Record | Key and fields | Query or invariant |
|---|---|---|
| Link | code primary key; owner_id, destination, status, expires_at, version, created_at | Exact code lookup; unique constraint includes generated codes and custom aliases. ACTIVE -> REVOKED is irreversible. Never recycle codes. |
| CreationRequest | (owner_id, idempotency_key) unique; normalized request fingerprint, result code, retention deadline | Replay committed results for at least 24 hours. Same key with a different request returns 409. |
| ChangeOutbox | event_id primary key; code, version, committed change | Written in the revoke transaction; retryable invalidation delivery and a monotonic consumer watermark. |
| ClickEvent, optional | (producer_id, event_id); code, event time, coarse geography if permitted | Retry identity for approximate analytics, not a proof of a unique human click. |
Keep a composite index on (owner_id, created_at, code) for account management. An opaque signed cursor contains the last creation timestamp and code; the server binds it to the authenticated owner. Query in a stable order with a maximum page size of 100. Never return another owner's rows because a cursor supplied another owner ID. New rows may appear on a fresh traversal; this endpoint is not a snapshot export.
Use one logical writer authority for each code namespace. A single SQL writer with synchronous durable replication is a workable starting point for the assumed write rate, subject to measurement. If storage later requires partitioning, hash the code to its authoritative partition and route every insert for that code there. The (owner, request key) and code records cannot silently become an atomic cross-shard transaction: retain a transactional create service or introduce a documented reservation workflow before sharding those records apart.
Concrete API contract
All management requests use authenticated TLS sessions. The service derives owner_id from the principal, never from trusted-looking JSON. Cookie sessions require CSRF protection. Public redirects accept GET and HEAD; HEAD returns the same status and headers without generating a click event.
| Operation | Request and response | Retry and failure contract |
|---|---|---|
| Create | POST /v1/links, Idempotency-Key: req-42, body below; 201 with code, short URL, and expiry | Same owner/key/body replays the stored result. 409 for alias conflict or key reuse with different body; 422 invalid destination/deadline; 429 admission limit; 503 allocation or database unavailable. |
| Resolve | GET /aB7xK2mQ; 302 with Location and Cache-Control: no-store | 404 unknown; 410 expired or revoked; 503 when a safe authoritative result is unavailable. No destination in error responses. |
| Revoke | DELETE /v1/links/aB7xK2mQ; 204 after durable commit | Repeated owner revoke returns 204 without another version bump. 404 for missing or another owner's link. An unknown timeout is retried. |
| List | GET /v1/links?limit=20&cursor=opaque; 200 with items and nextCursor | Owner-scoped keyset pagination; 400 malformed cursor or invalid limit; no unbounded scans. |
{
"destination": "https://example.org/events/october?school=42",
"customAlias": "school42",
"expiresAt": "2026-11-01T00:00:00Z"
}{
"code": "school42",
"shortUrl": "https://go.example/school42",
"expiresAt": "2026-11-01T00:00:00Z"
}Example error: {"error":{"code":"ALIAS_TAKEN","retryable":false}}. Define aliases as 4–32 case-sensitive ASCII letters/digits, reserve routing prefixes such as v1, and reject reserved names. Normalize only the alias validation rules, not the destination's query parameters or path casing. Allow parsed absolute HTTP and HTTPS URLs, cap their length at 2,048 bytes, reject user-info and control characters, and use the framework's header API for Location. Do not build raw HTTP headers from input.
Step 1 — Persist intent, then redirect
The smallest working system is one application and a durable mapping table. On create, validate the request, claim the owner's idempotency key, insert the mapping, and store the result in one transaction. Only return 201 after commit. If the process dies after commit but before responding, Alice retries the same key and gets the same code. A same-key request with a different normalized fingerprint is a conflict. Retain the original result for 24 hours; a retry after retention may create another link and must not be represented as an unlimited guarantee.
On redirect, load the exact code, check status == ACTIVE and now < expires_at if a deadline exists, and return 302. At the expiry instant the link is no longer valid, regardless of when a cleanup job runs. Unknown codes return 404 and revoked/expired ones return 410. A redirect checked just before a concurrent revoke may succeed within the documented stale window. Do not claim global linearizability for this cached read path.
Read the diagram
Creation and redirect are separate requests that share one unique durable mapping. The owner submits a URL, optional alias, and request identity; the management API validates and commits the mapping before acknowledgment. Later, the visitor requests a short code, and the resolver reads current mapping status and logical expiry before returning a temporary redirect or an error. The resolver never fetches the destination.
Read the numbered arrows as two requests. Steps 1–2 commit the create and return its identity. Steps 3–4 check a visitor's code and return a redirect or a terminal error. This works at small scale, but every repeated redirect still consumes a database read. The diagram deliberately contains no request from the shortener to the destination website.
Step 2 — Let uniqueness decide allocation
Generate a uniformly random base62 candidate with a cryptographically secure generator and unbiased character sampling. Attempt the unique insert, rather than querying availability and inserting later. Two callers may both observe an apparently free alias; only the unique constraint can select a winner. Retry generated-code collisions at most five times with new candidates. An explicit custom alias has no alternative meaning, so return 409 on its first collision. Roll back the whole transaction on failure, including the claimed request key.
# Design pseudocode. Each iteration starts a new transaction.
for attempt in range(5):
candidate = requested_alias or unbiased_random_base62(8)
try:
with transaction():
result = claim_or_replay(owner_id, key, request_fingerprint)
if result.committed:
return result.response
insert_link_unique(candidate, owner_id, destination, expires_at)
record_create_response(owner_id, key, candidate)
return created(candidate) # only after commit
except CodeConflict:
if requested_alias:
return conflict("ALIAS_TAKEN")
return unavailable("ALLOCATION_RETRY_LIMIT")Read the diagram
The transaction claims the request identity, attempts the unique mapping insert, and records the response. Generated-code conflicts roll back and retry at most five times; alias conflicts return 409. The successful unique insert is authoritative.
The idempotency claim also uses a unique constraint. A concurrent uncommitted same-key claim waits within the request deadline or returns a documented retryable response; it cannot invent a second successful result. Alternatively, allocate disjoint numeric ranges durably and base62-encode the numbers. A crashed range holder wastes unused numbers but never returns them to the pool. Range allocation reduces random collision work but requires durable allocator recovery and exposes predictable IDs. After restoring old data, stop creation until issuance history is reconciled; never reuse acknowledged codes from a lost database suffix.
Step 3 — Cache with a deadline that cannot renew itself
Introduce a cache because visitors repeatedly resolve the same code. A cached value includes destination, status, version, logical expiry, and an absolute freshness deadline established by the authoritative read. Each cache layer passes that deadline through unchanged. A local cache may not take a nearly expired regional value and give it another full TTL. Neither a cache hit nor a delayed insertion extends its life.
For the 60-second revocation promise, use a 55-second maximum freshness interval, bounded clock offset of two seconds, and at most one second for a refresh request. Record the authoritative snapshot's observation time, or conservatively the request start time; discard a response arriving past its original deadline. Clamp logical expiry by the same clock-error allowance so expiration is conservative. Do not refill from a lagging replica under this promise unless its maximum lag is included in the budget. Monitor clock offset; bypass the cache or fail closed when the assumption breaks.
Read the diagram
A read starts at second zero and sets freshness deadline 55. Revocation commits version eight at second one. A late version-seven fill at second two retains deadline 55; a newer version fence rejects it when available. At the deadline a current authoritative result is required. Clock error is covered by the safety budget.
Revocation changes the row to REVOKED, increments its version, and inserts an outbox event in the same transaction. A consumer invalidates all controlled layers. Consumers ignore older versions. A per-code version fence can reject a late stale fill; if that fence is lost or evicted, the original deadline is still the safety backstop. Retain the fence at least through outstanding refresh deadlines. Never rely on an evictable cache tombstone for an unbounded correctness guarantee.
Suppose a read starts at second 0 and sees version 7. A revoke commits at second 1 as version 8. The old read finishes at second 2 after invalidation. Its freshness deadline remains second 55, not second 57, and a retained version-8 fence rejects it altogether. If the invalidator is down, a resolver eventually reaches that original deadline, reads the writer, and sees REVOKED. If the writer is also down, it returns 503 instead of serving stale indefinitely. Brief negative caching is allowed for unknown codes, but clear it on create or include its maximum delay in the new-link usability target.
Use 302 plus Cache-Control: no-store for visitor responses here; configure the CDN to bypass caching redirect responses. Application-controlled caches retain the explicit records above. A 301 or cacheable browser redirect can persist outside your invalidation system, so it is incompatible with this takedown contract. If a product explicitly wants permanent immutable links with no revocation, a separate 301 policy is possible. An immediate-revocation requirement instead needs an online authoritative deny check and accepts the additional latency and dependency.
Step 4 — Isolate hot reads and optional analytics
A viral link should be replicated in local resolver caches; sharding a single key cannot spread its reads. Coalesce concurrent misses within each resolver process, but remember that 100 processes can still issue 100 refreshes. Add per-instance origin semaphores whose aggregate capacity is below the database's measured safe limit, a bounded wait queue, and a timeout. Use jittered early refresh while preserving the authoritative deadline. If the origin budget is exhausted, return a retryable error rather than opening unlimited database connections.
Read the diagram
Resolver replicas serve the hot mapping locally. Misses coalesce per process and pass through an aggregate origin budget before reaching the writer. GET analytics use a finite buffer; HEAD produces no click. Buffer failure or overload does not change the redirect result.
For optional analytics, enqueue a small event without awaiting a remote analytics acknowledgment. At the peak above, assumed 100-byte events are about 5.8 MB/s of raw data, or 10 GB/day at the average rate before replication and indexes. Cap local buffered events, for example at 10,000 per resolver, and cap queue retention and consumer concurrency. The buffer can be lost on crash, and overload can drop events; measure both available drop counters and observed producer gaps. Do not promise exact totals. Stable event IDs deduplicate retries within a documented retention interval but cannot recover events never durably accepted.
If billing requires exact accepted-click records, acknowledge a durable event before returning a redirect or change the accounting definition. This deliberately couples redirect availability to the log. Compare that cost with best-effort analytics instead of adding an outbox to every redirect without discussing the extra authoritative write. Minimize analytics fields, avoid raw destination query strings and persistent visitor identifiers by default, and define retention and deletion policy.
Deep dive — Abuse and takedown
An authenticated user can still create abusive links. Apply per-owner and per-IP creation budgets, URL validation, reserved aliases, and an operational takedown endpoint with an audited privileged principal. A revoked code never changes owner and is never recycled. Otherwise, an old newsletter could suddenly point at another owner's site. External reputation services may be slow or unavailable; decide whether new links remain pending review or are rejected, rather than letting a slow check block every redirect.
The browser follows the Location value; the resolver does not fetch it. A future link-preview worker introduces SSRF risk and needs its own isolated egress policy, DNS and redirect revalidation, response-size limits, and timeouts. Scheme filtering on the create API alone is insufficient for a preview fetcher. Track abuse-report-to-revoke latency separately from the commit-to-serving propagation bound: the 60-second promise starts at revoke commit, not at the first user report.
Failure and recovery analysis
Consider the combined failure that makes a cache-based design dangerous: an old read, a revoke, a lost invalidation, and then a cache outage. Before revoke commit, version 7 is ACTIVE. After commit, the database contains version 8 and a pending outbox event. The cache may temporarily retain version 7, but its absolute deadline cannot move. A failed cache lookup must go through the bounded origin path; it is not permission to return a guessed destination or skip state checks.
Read the diagram
An old active response can arrive after the durable revoke while its outbox is delayed. Filling a cache does not establish a new freshness period. Known version fences reject older responses. After the original deadline, the resolver reads current writer state or returns a retryable error, never an unauthorized destination.
Retry the outbox with exponential backoff and jitter, cap concurrent invalidations, and alert on oldest pending age before the contractual bound. A failed message remains recorded for repair; a dead-letter queue is an operational handoff, not successful delivery. Rebuild caches lazily from active authoritative rows under the same origin budget. On full cache loss, do not preload every historical link or allow every resolver to issue unbounded warm-up reads.
Durability depends on the database commit and recovery policy. Define synchronous replica acknowledgment for acknowledged management writes within the chosen deployment, and regularly restore backups plus logs in isolation. A backup alone may omit recently acknowledged creations and revocations. During regional disaster recovery, fence old writers, reconcile the lost suffix, and keep affected operations unavailable until safety is established. Do not advertise zero cross-region data loss merely because there are multiple regions.
Final architecture and requirement trace
Read the diagram
The management API owns authentication and commits mapping changes with outbox intent. The resolver accepts public codes and uses deadline-bounded cached values or bounded authoritative reads. Consumers invalidate versions from the durable outbox; separate best-effort click ingestion may lose events. No transaction spans the cache or browser.
| Requirement | Mechanism in the final design | Verification |
|---|---|---|
| R1 create once | Management API transaction: request identity plus unique mapping | V1 collisions, V2 committed-response replay |
| R2 safe resolution | Resolver validates state, logical expiry, and cache deadline | V3 exact expiry, V4 missing/error cases |
| R3 revoke | Owner-checked durable row and outbox; version fence plus nonrenewing freshness | V5 lost invalidation and late fill, V6 cache/writer outage |
| R4 extensions | Shared code namespace, no recycling, owner-scoped reads | V1 alias conflict, V7 unauthorized mutation and pagination |
| R5 optional analytics | Nonblocking bounded buffer and observed drop policy | V8 queue full or unavailable |
The final diagram's numbered edges identify responsibilities, not one transaction spanning every box. The outbox and mapping update share a database transaction. Cache invalidation and analytics consumption happen later. There is no distributed atomic commit with the cache, queue, or visitor's browser.
Operate and verify the design
| Signal | What it distinguishes | Response |
|---|---|---|
| Redirect p50/p95/p99, valid-code availability, 503 rate | Hit latency versus origin latency and admission rejection | Shed excess misses within a fixed concurrency budget; never disable deadline checks. |
| Oldest outbox age, revoke-to-last-controlled-redirect delay, clock offset | Delayed acceleration versus an actual stale-serving contract breach | Repair consumers; bypass unsafe caches when clock assumptions fail. |
| Cache hit rate, per-key concentration, origin queue age | Viral hot key versus whole-cache loss | Replicate values, coalesce misses, ramp warm-up under budget. |
| Unique conflicts, retry-cap exhaustion, idempotency conflicts | Normal random collisions versus broken generator or client misuse | Alert on abnormal change; preserve uniqueness even when rejecting work. |
| Analytics accepted, dropped, lag, retention utilization | Best-effort data loss versus redirect failure | Apply the published sampling/drop policy and expose incomplete periods. |
Storage retention, cache replication, and analytics ingestion are the dominant cost knobs. A shorter cache freshness interval tightens takedown but increases reads. Longer code/idempotency retention protects retries and historical links but consumes storage. Estimate cost from measured workload and current provider rates separately; no price claims are needed to choose the correctness boundary.
Check the edge cases
The repository includes a deterministic teaching model for these cases. It checks the stated state transitions; it does not simulate an actual SQL cluster, CDN, clock service, or load test. Real capacity and durability targets remain deployment tests.
| Case | Injection | Expected result |
|---|---|---|
| V1 | Two creates propose the same code; repeat with an explicit alias; exhaust five candidates | No overwrite; generated code retries; alias returns 409; exhausted budget returns retryable failure. |
| V2 | Lose the response after create commit; replay same and changed bodies under the key | Same body returns original code; changed body returns 409. |
| V3 | Cache an expiring link, then request exactly at expiry | No Location is returned; logical expiration does not wait for TTL cleanup. |
| V4 | Request unknown code, invalid scheme, or malformed destination | Missing returns 404; invalid creates are rejected; no redirect destination is fabricated. |
| V5 | Complete an old read after revoke and drop invalidation; copy it through another cache | Original freshness deadline survives; a known newer version rejects the fill; past the deadline revoked state wins. |
| V6 | Disable cache, then writer; fill origin admission budget | Bounded fallback validates state; expired cache plus unavailable writer yields 503. |
| V7 | Revoke another owner's code; reuse a revoked alias; replay foreign pagination cursor | Ownership is preserved, no code recycling, and no cross-owner list access. |
| V8 | Fill analytics buffer or disable consumer | Redirect result is unchanged; explicit drops or backlog metrics identify the degradation. |
Practice before revealing the answer
Design prompt: Ten percent of all traffic moves to one new code while every cache is cold. Explain how your design remains bounded, then calculate the origin admission budget if load testing establishes 3,000 safe reads/s. Which requests may fail during warm-up?
Show answer and explanation
Answer: Spread the hot value across resolver caches, coalesce requests per process, and ensure the sum of local origin budgets stays at or below the measured safe capacity with headroom. A per-process semaphore alone is insufficient unless deployment count is included. Requests beyond the finite queue and timeout can receive 503 during warm-up; say so explicitly. A new database shard does not divide one code's traffic.
Failure follow-up: A revoke commits at second 1, the invalidator fails, and a version-7 read begun at second 0 arrives at second 2. Can another cache keep it until second 110 by resetting its TTL at second 55?
Show answer and explanation
Answer: No. The absolute deadline derived from the original authoritative observation remains second 55 before clock safety adjustments. A child cache inherits it. Past it, the resolver must read current authority or return 503. A version fence can reject the late fill earlier, but the deadline remains the backstop when fences are lost.
Score each answer 0 if absent, 1 if it names a technique, or 2 if it states the authority, bounds, and observable failure. Full credit requires uniqueness at insertion, transactional replay identity, logical expiry, a nonrenewing cache deadline, an explicit origin budget, and honest analytics loss. Explain the availability cost of each guarantee rather than claiming every request always succeeds.
Related concepts and technical references
Caching and Database Indexing explain the read path and access patterns. HTTP semantics defines redirect status codes; HTTP caching defines caching directives. PostgreSQL unique constraints and transaction isolation explain the database mechanisms used here. These sources support mechanisms, not a claim about a named company's private architecture.
Operational contract
| Area | Decision and signals |
|---|---|
| SLIs | Redirect p99 and cache-miss rate; collision/retry rate; revoke-to-invalidation lag; analytics drop rate. |
| Overload limits | Cap link creation per principal, resolver origin misses, and analytics buffer; shed analytics before redirect work. |
| Recovery | Rebuild cache from durable mappings and replay the revoke outbox; fence stale fills by version/deadline. |
| Access controls | Authenticate link management; validate HTTPS/HTTP destinations; never treat public codes as authorization. |
| Dominant cost | Redirect egress, hot-key cache memory, and retained click events. |
Inspect the code that owns one redirect
For an illustrative alphabet 0-9A-Za-z, counter 62 encodes as 10: 62=1*62+0. Another alphabet convention can produce another spelling; declare it. A random candidate generator instead needs an atomic unique-code insert and bounded collision retries. Two clients choosing the same candidate cannot both own it because the authority compares/inserts, not because a preliminary lookup usually works.
candidate ab7 -> already owned by link L1 -> collision
candidate xy8 -> atomically reserved by L2 -> accepted
retry creation key K2 -> returns L2 / xy8Read the diagram
candidate ab7 -> already owned by link L1 -> collision; candidate xy8 -> atomically reserved by L2 -> accepted; retry creation key K2 -> returns L2 / xy8
Resolve the authoritative mapping, then return the declared redirect status and cache controls. Permanent cached redirects improve repeated access but make immediate destination changes or revocation hard at clients that cached them. If links are mutable, choose a compatible temporary/freshness policy and accept the cost. Validate URL scheme and abuse policy; a redirect service is not an unrestricted server-side URL fetcher.
Show answer and explanation
Answer: Code collision and creation retry are different identities. A retry of K2 returns L2, while a new colliding code candidate obtains another code or fails. A stale edge entry cannot be assumed to respect immediate takedown without an explicit invalidation/lifetime contract.
Decision review: Bitly
Bitly: What it is
A short-link service creates a stable alias and resolves it to an eligible destination; ownership, expiry and takedown are part of that contract.
Bitly: Why it is needed
A small identifier is useful for sharing and indirection. Its popularity does not remove the need to prevent collisions or enforce disablement.
Bitly: When to use it
Use generated aliases when compactness matters; require an authoritative eligibility path when urgent abuse removal has a strict bound.
Bitly: How to use it
Atomically couple the client operation identity with the created link. Let a unique alias constraint decide collisions. Cache using the original expiry deadline, not a newly renewed lifetime on each fill.
Bitly: Compare an alternative
Random allocation distributes creation but retries collisions; allocated ranges avoid random collisions but introduce allocation ownership. Compare predictability, operational cost and your abuse policy.
Bitly: Worked decision exercise
The following inputs and policies are original exercise assumptions; they are not measurements or undisclosed company behavior.
Two creators draw alias K7. Creator A commits; B conflicts and allocates K8. A loses its response and retries operation opA: it receives K7, not a third link. Later a cached K7 is disabled while an old fill remains in flight.
Read the diagram
A commits alias K7 for operation opA; B collides on K7 and allocates K8; Retry opA returns K7; no new link is created; Late cache fill cannot silently renew expiry
Can a shorter cache TTL guarantee immediate takedown if a delayed fill restores the old value?
Show answer and explanation
Answer: No. A bounded stale window may meet a declared policy, but a strict current deny decision needs an enforceable authority or version-aware protocol. Preserve the absolute expiry and operation result; do not treat a timeout as proof creation failed.
Bitly: Check the design
Measure alias conflicts, accepted-operation retries, redirect latency and takedown propagation separately. Test a late cache fill after disablement.