Skip to main content

535. Encode and Decode TinyURL

mediumAsked at Shopify

Shopify uses the TinyURL design to grade whether you can pick a reasonable encoding scheme and reason about collisions, scale, and persistence — the same concerns that drive Shopify's checkout permalink and short-link systems.

By Alex Chen, Founder, InterviewChamp.AI · Last verified

Source citations

Public interview reports confirming this problem appears in Shopify loops.

  • Glassdoor (2026-Q1)Shopify Senior Backend + Production Engineer onsites cite TinyURL design as a recurring system-design-lite round.
  • Reddit r/cscareerquestions (2025-12)Shopify interview reports include this with a follow-up about scale (10^9 URLs) and persistence.

Problem

Design a TinyURL service. Implement two functions: encode(longUrl) returns a short URL, and decode(shortUrl) returns the original long URL. The implementation does not need to persist data across instances; both methods must be self-consistent.

Constraints

  • 1 <= url.length <= 10^4
  • url is guaranteed to be a valid URL.

Examples

Example 1

Input
url = "https://example.com/some/very/long/path"
Output
encode(url) -> "http://tinyurl.com/4e9iAk"; decode(...) -> original url

Approaches

1. Use the long URL as its own hash key (no shortening)

Store the input verbatim; return the same string from decode. Trivially correct, but defeats the point.

Time
O(1)
Space
O(n)
class CodecTrivial {
  encode(longUrl) { return longUrl; }
  decode(shortUrl) { return shortUrl; }
}

Tradeoff: Passes the API contract but doesn't actually shorten. Useful only to motivate the discussion: the interviewer wants the next version.

2. Random 6-char base62 with collision check (optimal)

Generate a random 6-character string from [A-Za-z0-9]. Check the forward map for collisions; regenerate if hit. Store both forward (short -> long) and inverse (long -> short) maps for idempotent encode.

Time
O(1) average for both
Space
O(n)
class Codec {
  constructor() {
    this.alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    this.shortToLong = new Map();
    this.longToShort = new Map();
    this.base = 'http://tinyurl.com/';
  }
  _generate() {
    let s = '';
    for (let i = 0; i < 6; i++) {
      s += this.alphabet[Math.floor(Math.random() * 62)];
    }
    return s;
  }
  encode(longUrl) {
    if (this.longToShort.has(longUrl)) return this.base + this.longToShort.get(longUrl);
    let key;
    do { key = this._generate(); } while (this.shortToLong.has(key));
    this.shortToLong.set(key, longUrl);
    this.longToShort.set(longUrl, key);
    return this.base + key;
  }
  decode(shortUrl) {
    const key = shortUrl.replace(this.base, '');
    return this.shortToLong.get(key);
  }
}

Tradeoff: 62^6 = 56.8 billion keys, more than enough headroom. Idempotent encode (same long URL -> same short URL) saves storage. Random generation avoids guessable URLs. The interview-ready answer.

3. Monotonic counter + base62 encoding

Increment a global counter on each encode; base62-encode the integer to get the short key. Zero collisions by construction.

Time
O(1)
Space
O(n)
class CodecCounter {
  constructor() {
    this.alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    this.counter = 0;
    this.shortToLong = new Map();
    this.longToShort = new Map();
    this.base = 'http://tinyurl.com/';
  }
  _encode62(n) {
    if (n === 0) return this.alphabet[0];
    let s = '';
    while (n > 0) {
      s = this.alphabet[n % 62] + s;
      n = Math.floor(n / 62);
    }
    return s;
  }
  encode(longUrl) {
    if (this.longToShort.has(longUrl)) return this.base + this.longToShort.get(longUrl);
    const key = this._encode62(this.counter++);
    this.shortToLong.set(key, longUrl);
    this.longToShort.set(longUrl, key);
    return this.base + key;
  }
  decode(shortUrl) {
    return this.shortToLong.get(shortUrl.replace(this.base, ''));
  }
}

Tradeoff: Zero collisions and sequential keys are tiny early on (1-2 chars). Drawback: guessable URLs leak ordering and roughly the total link count — a security smell for some products. Distributed counter is the natural follow-up (snowflake IDs, Redis INCR).

Shopify-specific tips

Shopify's follow-ups always probe scale: 'now imagine 10^9 URLs across 100 machines — how do you avoid collisions?' (sharded counter, snowflake IDs, or a UUID-namespaced hash). They also ask about persistence (key-value store, the short key as the primary key) and analytics (click tracking via a redirect handler that logs before 302). Volunteer both the random and counter approaches and discuss the security/scale tradeoff explicitly.

Common mistakes

  • Using crypto.randomUUID — works but produces a 36-char key, defeating the shortening goal.
  • Forgetting to make encode idempotent — the same long URL should map to the same short URL.
  • Using Math.random for cryptographic use cases (security-sensitive deployments).
  • Returning a key without the base URL prefix — decode then has to guess which prefix to strip.

Follow-up questions

An interviewer at Shopify may pivot to one of these next:

  • Make it work across multiple instances (distributed counter or namespaced hash).
  • Add expiration — short URLs expire after 30 days.
  • Add click analytics without affecting redirect latency.
  • Custom short URLs (vanity codes) — handle collisions with user-chosen keys.
  • How would you A/B-test different short URL lengths?

Solve it now

Free. No sign-up. Python and JavaScript run instantly in your browser.

Output

Press Run or Cmd+Enter to execute

FAQ

Why 6 characters specifically?

62^6 = ~56.8 billion combinations — comfortable headroom for most products. 4 chars gives 14.7M (small), 8 chars gives 218 trillion (overkill and longer). 6 is the industry default for a reason.

Random vs counter — which one would you actually ship?

Depends on the product. Random + collision check for user-facing URLs where unguessability matters (passwordless login links, document shares). Counter for internal short links or where sequential ordering is acceptable. For Shopify product permalinks, random is the right answer.

Free learning resources

Curated free links for this problem.

Practice these live with InterviewChamp.AI

Drill Encode and Decode TinyURL and other Shopify interview questions under real-loop conditions with instant feedback on your reasoning, complexity claims, and code.

Practice these live with InterviewChamp.AI →