Build a URL Shortener

Difficulty: Hard · Category: URL Shortener · Languages: Java 17, Python 3.11, Go 1.22, Node.js 20, TypeScript 5.x

URL Shortener

Background

URL shortening is a classic system design problem and a fundamental service used across the internet. Services like bit.ly and tinyurl.com convert long, unwieldy URLs into short, shareable links while tracking click analytics.

Building a URL shortener requires thinking about:

Your Task

Implement the URLShortenerContract interface. Your implementation must:

  1. Generate unique short codes of 6–8 alphanumeric characters.
  2. Be thread-safe: concurrent shortening must never produce duplicate codes.
  3. Increment click counts atomically on each resolve.
  4. Persist all data — state must survive process restarts.
  5. Shortening the same URL multiple times creates separate mappings (each with its own short code and click counter).

Recommended infrastructure: postgres — relational lookups suit this problem well, but you may use any available system.

How It Works

The platform injects your chosen infrastructure SDK(s) via constructor. For example, if you select PostgreSQL:

public class MyURLShortener implements URLShortenerContract {
    private final PostgresClient db;

    public MyURLShortener(PostgresClient db) {
        this.db = db;
    }

    // ... implement methods using db.execute(), db.query(), etc.
}

See the SDK Docs tab for the full API reference of your chosen system.

Interface

public interface URLShortenerContract {
    String shorten(String longUrl);
    String resolve(String shortCode);
    boolean delete(String shortCode);
    int getClickCount(String shortCode);
    Map<String, Object> getStats(String shortCode);
}

Method Details

MethodDescription
shortenStores the URL and returns a unique 6–8 character alphanumeric code. Throws IllegalArgumentException for null input.
resolveReturns the original URL and increments the click counter atomically. Returns null if the code does not exist.
deleteRemoves the mapping. Returns true if it existed, false otherwise.
getClickCountReturns the number of times resolve was called for this code. Returns 0 for unknown codes.
getStatsReturns a map with keys: shortCode, longUrl, clickCount, createdAt (epoch millis). Returns an empty map for unknown codes.

Constraints

Example Walkthrough

// 1. Shorten two URLs (same URL → different codes each time)
shorten("https://example.com/long/path")   → "aBc12D"
shorten("https://example.com/long/path")   → "xYz98Q"
shorten("https://other.com/page")          → "Mn3pRs"

// 2. Resolve — returns original URL and increments click count
resolve("aBc12D")        → "https://example.com/long/path"
resolve("aBc12D")        → "https://example.com/long/path"
getClickCount("aBc12D")  → 2
getClickCount("xYz98Q")  → 0   (never resolved)

// 3. Stats — full details for a code
getStats("aBc12D")       → { "shortCode": "aBc12D",
                              "longUrl": "https://example.com/long/path",
                              "clickCount": 2,
                              "createdAt": 1712582400000 }
getStats("unknown")      → {}  (empty map)

// 4. Delete — removes mapping, future resolves return null
delete("aBc12D")         → true
resolve("aBc12D")        → null
getClickCount("aBc12D")  → 0
delete("aBc12D")         → false  (already deleted)

Hints

Infrastructure your code runs against

How your solution is graded

Your score combines functional correctness (40%), performance (40%) and code quality (20%).

Practice this problem

Related LLD problems