Design TinyURL

Share
Design TinyURL

Problem statement

Short description

Design a URL shortener service, similar to TinyURL or Bitly.

Given a long URL, such as https://www.example.com/articles/system-design-interview-guide-for-beginners, the system should return a short URL, such as https://tiny.ly/aB92xKq.

When a user opens the short URL, the system should redirect them to the original long URL.

Understanding the problem

From the problem statement, we can understand that this system mainly does two things:

  1. It creates a short, easy-to-share URL for a given long URL.
  2. It resolves the short URL back to the original long URL when someone visits it.

In other words, the core of the system is a mapping: short_code → long_url.

For example, the short code aB92xKq maps to the original long URL. The create flow writes this mapping, and the redirect flow reads this mapping.

What we need to discuss with the interviewer

Before jumping into the design, clarify the boundary of the problem with the interviewer.

Pick three to four questions:

  1. Scale and latency — How many creates and redirects do we expect? What is the p99 redirect latency target?
  2. Availability — How available does the redirect service need to be?
  3. Consistency — Should newly created short URLs be immediately available? How strict is custom alias uniqueness?
  4. Scope — Are expiration, analytics, abuse prevention, authentication, or custom domains required in the first version?

For this discussion, we will focus on URL creation, redirect, optional custom alias, and optional expiration. Other topics can be follow-ups.

Requirements

Functional requirements

  1. Shorten URL — User submits a long URL and receives a short URL.
  2. Redirect — User visits the short URL and gets redirected to the original long URL.
  3. Custom alias — User can choose a short code if it is available.
  4. Expiration — Short URL can expire after a certain date.

Non-functional requirements

  1. Low latency — Redirect p99 < 50 ms (hot read path).
  2. High availability — 99.99% for redirects.
  3. Scale — ~100M writes/day, ~10B reads/day → ~100:1, read-heavy.
  4. Uniqueness — No duplicate codes; one short code → one long URL.
  5. Consistency — Strong consistency on create, especially custom aliases.

A good interview phrase:

I will optimize the redirect path first because reads are much more frequent than writes.

Core entities

We only need a few core entities.

  1. URL Mappingshort_code, long_url, custom_alias, user_id, created_at, expires_at
  2. Useruser_id, account info

For the first version, the most important table is URL Mapping.

API design

Create a short URL

POST /urls

{
  "long_url": "<https://www.example.com/some/very/long/url>",
  "custom_alias": "my-link",
  "expires_at": "2026-12-31T00:00:00Z"
}

Response:

{
  "short_url": "<https://tiny.ly/aB92xKq>",
  "short_code": "aB92xKq"
}

Redirect

GET /{short_code}

Response:

HTTP 302 Redirect
Location: <https://www.example.com/some/very/long/url>

Why 302?

  • 301 means permanent redirect. Browsers may cache it aggressively.
  • 302 means temporary redirect. It gives us more control if the destination changes or expires.

For an interview, using 302 is a safe default.

High-level design

The high-level design should match the functional requirements exactly.

Since we have four functional requirements, we can design the system in four steps.

1. Shorten URL

Flow: Client → API Gateway → URL Service → Database.

The URL Service receives a long URL, generates a new short code internally (the code generator is a module inside the URL Service, not a separate service), stores the mapping, and returns the short URL.

At this step, we only need one table: url_mapping(short_code, long_url, created_at).

2. Redirect

Flow: Client → API Gateway → URL Service → Database → HTTP 302 Redirect.

The URL Service reads short_code from the request, looks up the original long_url, and returns an HTTP 302 redirect.

For this step, the same table is enough: url_mapping(short_code, long_url, created_at).

3. Custom alias

Instead of letting a custom alias take over the generated short_code, we add a single optional custom_alias column to the mapping table:

url_mapping(short_code PRIMARY KEY, long_url, custom_alias UNIQUE, created_at).

  • Every URL still gets a system-generated short_code as usual.
  • If the user provides a custom alias, we store it in custom_alias and enforce uniqueness with a unique constraint.
  • On redirect, we look up by either short_code or custom_alias.
  • If the requested alias is already taken, return 409 Conflict.

This keeps generated codes and custom aliases in separate namespaces, so they never collide and the code generation logic stays simple.

4. Expiration

For expiration, we add one more field to the URL mapping table: url_mapping(short_code PRIMARY KEY, long_url, custom_alias UNIQUE, created_at, expires_at).

During redirect, the URL Service checks expires_at.

If the short URL has expired, return 404.

Otherwise, return the HTTP 302 redirect.

Deep dives

The high-level design solves the functional requirements.

The purpose of the deep dive is to satisfy the non-functional requirements.

Each deep dive should clearly map back to one or more non-functional requirements:

  1. Unique short code generation → solves Uniqueness and Consistency.
  2. Fast redirect path → solves Low latency and Scale.
  3. System scaling → solves Scale and High availability.

Deep Dive 1: How do we guarantee uniqueness and consistency?

This deep dive addresses Uniqueness and Consistency.

The key idea is simple: each short_code must map to exactly one long_url.

The ranges come from a small, separate ID Allocation Service (a counter service) that owns a single global counter and hands out non-overlapping ID ranges to URL Service instances.

How it works:

  • When an instance starts up, or when its current range is about to run out, it requests the next range from the Allocation Service.
  • The Allocation Service atomically advances the global counter and returns a block, e.g. [1,000,000 – 1,000,999]. The counter can be an auto-increment row in a small dedicated DB, or INCRBY on Redis.
  • The instance then generates short codes locally by converting each ID in its range to Base62 — no network call per create.

Why this is a strong interview answer:

  • Unique: different instances own different, non-overlapping ID ranges, so generated codes can never collide.
  • Scalable: the global counter is hit only once per range (e.g. every 1,000 creates), not on every request, so it never becomes a hot-path bottleneck.
  • Efficient: no database collision check is needed for normal generated codes.
  • Simple tradeoff: some IDs may be skipped if an instance crashes before using its full range, which is acceptable.
  • Availability note: the Allocation Service is off the request hot path (only called per range), so even brief allocator downtime does not block creates as long as instances still have IDs left in their current range.

Alternatives

  • Random string: simple, but collisions require database check and retry.
  • Global counter: guarantees uniqueness, but can become a bottleneck and may expose sequential IDs.

Deep Dive 2: How do we make redirects fast?

This deep dive addresses Low latency and Scale.

The redirect path is the hottest path, so the key idea is simple: avoid hitting the database for every redirect.

Use Redis between the URL Service and the database.

  • Cache hit: return the redirect immediately.
  • Cache miss: read from the database, populate Redis, then return the redirect.
  • Cache key: url:{short_code}.
  • Cache value: long_url and expires_at.

Why this works well:

  • URL mappings are mostly immutable.
  • Redirects are much more frequent than URL creation.
  • Popular links benefit heavily from caching.
  • If the URL has expires_at, set the Redis TTL to match it.
  • For extremely hot links, add CDN edge caching to reduce origin traffic further.

Deep Dive 3: How do we scale the system?

This deep dive addresses Scale and High availability.

At scale, we should keep the core services simple and horizontally scalable.

  1. Stateless URL Service — Put URL Service behind a load balancer so any instance can handle any request.
  2. Shard URL mapping by short code — Use something like hash(short_code) % N so data is distributed across database shards.
  3. Use read replicas when needed — Reads can go to replicas, while writes still go to the primary shard.
  4. Serve hot links from a CDN — Cache the redirect response for extremely popular short codes at CDN edge locations, so most reads are absorbed near the user before they ever reach the origin.

Why this is a strong interview answer:

  • Stateless services are easy to scale horizontally.
  • Sharding handles storage growth.
  • Read replicas improve read capacity.
  • CDN edge caching absorbs hot-link traffic and lowers global redirect latency.

Wrap up and final design

At the end of the interview, we should briefly summarize the final architecture and confirm that it satisfies both functional and non-functional requirements.

Final design

  • Create path: Client → API Gateway → URL Service → URL Mapping Database
  • Redirect path: Client → API Gateway → URL Service → Redis Cache → URL Mapping Database → HTTP 302 Redirect
  • Hot links: CDN edge caching can be added for extremely popular short URLs.

Requirements coverage

All four functional requirements — shorten, redirect, custom alias, and expiration — are handled by the create and redirect paths above.

The non-functional requirements map directly to the three deep dives: uniqueness and consistency to short code generation, low latency to the Redis cache, and scale and availability to stateless services with sharding and read replicas.

Interview expectations by level

Different levels are expected to go into different depths. The goal is not to say everything, but to cover the right depth for the target level.

Junior to Mid-level

Expected focus:

  • Understand the problem and clarify the main requirements.
  • Design the core create and redirect flows with the URL Mapping table.
  • Explain why the system is read-heavy and why Redis cache helps.
  • Explain basic short code generation with Base62.

Strong signal:

The candidate can produce a clean, working design and explain why cache is needed for the redirect path.

Senior

Expected focus:

  • Explain range-based allocation (backed by a separate ID allocation / counter service) and why it is the recommended short code generation strategy.
  • Handle custom alias uniqueness with a separate custom_alias column and a unique constraint.
  • Discuss Redis TTL, cache miss, hot links, and sharding by short_code.
  • Use a CDN to absorb hot-link traffic and keep redirects fast at scale.

Strong signal:

The candidate can reason about tradeoffs, bottlenecks, and failure modes instead of only listing components.

Staff+

Expected focus:

  • If the interviewer asks, go deeper into product scope and traffic assumptions.
  • Mention multi-region, abuse prevention, and observability as possible follow-ups.
  • Explain the most important consistency boundary: short code creation must be strongly consistent.

Strong signal:

The candidate can identify the right production concerns without overcomplicating the core interview answer.