Skip to content
Jarviix
LLD7 min read

URL Shortener (Object model)

Class-level design for a Bit.ly clone — stores, generators, and the rate-limit decorator.

llddesign

Intro

We've covered URL Shortener as a system in the HLD track. Here we design the application-layer classes: how URLShortener wires up an ID generator, a key-value store, and policy decorators (rate limiter, audit log) without anyone knowing about anyone else.

Functional

  • shorten(longUrl, optional alias, optional expiry) → shortUrl.
  • expand(shortUrl) → longUrl, or 404.
  • Click-tracking optional, swappable.

Non-functional

  • ID generator and store must be replaceable (in-memory, Redis, MySQL, …).
  • Rate limiting per user added without touching core flow.

Components

  • UrlShortener

    Public façade. Composes id-gen + store + (optional) tracker.

  • IdGenerator

    Interface: Base62Counter, ULID, MurmurHash3.

  • UrlStore

    Interface: InMemoryUrlStore, RedisUrlStore.

  • RateLimitedShortener

    Decorator. Wraps a UrlShortener and rejects on over-limit.

  • AuditingShortener

    Decorator. Emits domain events.

Code

Snippetjava
interface IdGenerator { String next(); }

interface UrlStore {
    void put(String shortId, String longUrl, Optional<Instant> expiry);
    Optional<String> get(String shortId);
}

class UrlShortener {
    private final IdGenerator ids;
    private final UrlStore store;
    public UrlShortener(IdGenerator ids, UrlStore store) { this.ids = ids; this.store = store; }
    public String shorten(String longUrl, Optional<String> alias, Optional<Duration> ttl) {
        String id = alias.orElseGet(ids::next);
        store.put(id, longUrl, ttl.map(d -> Instant.now().plus(d)));
        return id;
    }
    public Optional<String> expand(String id) { return store.get(id); }
}

Pitfalls

  • Letting the shortener know which store backs it.
  • Putting rate-limit logic inline — should be a decorator.

Related reads