đź“‚
Interview Prep
LLD Questions
LLD Questions
  • LLD Introduction
  • Meeting Scheduler
  • Distributed Cache System
  • Rate limiter
  • Multi-Tier Elevator System
  • MyGate System
  • Uber Sytem LLD
  • Parking Lot System
  • Online book store
  • Library Management System
  • Movie Ticket Booking System
  • Hotel Management System
  • File Storage System
    • Solution 2
  • Chat Application
  • Social Media Platform
  • Notification System
  • Airline Reservation System
  • ATM System
  • E-commerce Website
  • Food Delivery System
  • Shopping Cart System
  • URL Shortener system
  • Chess Tournament System
  • Threading therory
  • OOP questions
Powered by GitBook
On this page
  • 1. System Overview
  • 2. Detailed Java Code
  • 3. Detailed Explanation and Pros/Cons
  • 4. Conclusion

URL Shortener system

Below is a comprehensive Low-Level Design (LLD) for a URL Shortener system implemented in Java. This design models the core entities and uses multiple design patterns to achieve a modular, scalable, and maintainable solution. In this design we use:

  • Singleton Pattern: To ensure that key services (like the URLShortenerService and URLRepository) have only one instance.

  • Factory Pattern: To encapsulate the creation of URLMapping objects.

  • Strategy Pattern: To enable different algorithms for generating short URLs (for example, using Base62 encoding or hashing).

  • DAO/Repository Pattern: To abstract persistence for URL mappings (here simulated as an in‑memory store).

  • Observer Pattern (Optional): To notify other components when a new URL mapping is created (for example, for analytics or logging).


1. System Overview

Entities

  • URLMapping: Represents a mapping between the original URL and its shortened version. It contains attributes like original URL, short URL, creation timestamp, and usage count.

Core Services

  • URLShortenerService (Singleton): Provides APIs to shorten URLs and retrieve the original URL.

  • URLRepository (DAO Pattern): Abstracts the persistence of URL mappings (in‑memory for simplicity, can be extended to a database).

  • ShortUrlGeneratorStrategy (Strategy Pattern): Defines how to generate a unique short URL from the original URL. A default implementation uses Base62 encoding of a sequence or hash.

  • URLMappingFactory (Factory Pattern): Centralizes the creation of URLMapping objects.

Optional Observers

  • URLCreationObserver (Observer Pattern): Notifies observers when a new URL mapping is created (for logging, analytics, etc.).


2. Detailed Java Code

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

// --------------------- ENTITY ---------------------

// URLMapping represents the mapping between original and shortened URLs.
class URLMapping {
    private String originalUrl;
    private String shortUrl;
    private LocalDateTime createdAt;
    private int hitCount;

    public URLMapping(String originalUrl, String shortUrl) {
        this.originalUrl = originalUrl;
        this.shortUrl = shortUrl;
        this.createdAt = LocalDateTime.now();
        this.hitCount = 0;
    }

    public String getOriginalUrl() { return originalUrl; }
    public String getShortUrl() { return shortUrl; }
    public LocalDateTime getCreatedAt() { return createdAt; }
    public int getHitCount() { return hitCount; }
    
    public void incrementHitCount() {
        hitCount++;
    }
    
    @Override
    public String toString() {
        return "URLMapping{" +
                "originalUrl='" + originalUrl + '\'' +
                ", shortUrl='" + shortUrl + '\'' +
                ", createdAt=" + createdAt +
                ", hitCount=" + hitCount +
                '}';
    }
}

// --------------------- OBSERVER PATTERN ---------------------

// Observer interface for URL mapping creation events.
interface URLCreationObserver {
    void onURLCreated(URLMapping mapping);
}

// A concrete observer that logs the URL creation.
class LoggingURLObserver implements URLCreationObserver {
    @Override
    public void onURLCreated(URLMapping mapping) {
        System.out.println("Observer Log: New URL mapping created: " + mapping);
    }
}

// --------------------- REPOSITORY (DAO Pattern) ---------------------

// URLRepository interface abstracts persistence of URLMapping objects.
interface URLRepository {
    void save(URLMapping mapping);
    URLMapping findByShortUrl(String shortUrl);
    URLMapping findByOriginalUrl(String originalUrl);
}

// In-memory implementation of URLRepository.
class InMemoryURLRepository implements URLRepository {
    private Map<String, URLMapping> shortUrlToMapping = new HashMap<>();
    private Map<String, URLMapping> originalUrlToMapping = new HashMap<>();

    @Override
    public synchronized void save(URLMapping mapping) {
        shortUrlToMapping.put(mapping.getShortUrl(), mapping);
        originalUrlToMapping.put(mapping.getOriginalUrl(), mapping);
    }

    @Override
    public synchronized URLMapping findByShortUrl(String shortUrl) {
        return shortUrlToMapping.get(shortUrl);
    }

    @Override
    public synchronized URLMapping findByOriginalUrl(String originalUrl) {
        return originalUrlToMapping.get(originalUrl);
    }
}

// --------------------- STRATEGY PATTERN ---------------------

// Strategy Pattern: Defines a contract for generating short URLs.
interface ShortUrlGeneratorStrategy {
    String generateShortUrl(String originalUrl);
}

// A default implementation that uses an atomic counter and Base62 encoding.
class Base62UrlGenerator implements ShortUrlGeneratorStrategy {
    // Atomic counter to ensure uniqueness.
    private AtomicLong counter = new AtomicLong(100000); // starting number
    private final String ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private final int BASE = ALPHABET.length();
    
    @Override
    public String generateShortUrl(String originalUrl) {
        long id = counter.getAndIncrement();
        return encodeBase62(id);
    }
    
    // Encodes a number to a Base62 string.
    private String encodeBase62(long num) {
        StringBuilder sb = new StringBuilder();
        while (num > 0) {
            int rem = (int)(num % BASE);
            sb.append(ALPHABET.charAt(rem));
            num /= BASE;
        }
        return sb.reverse().toString();
    }
}

// --------------------- FACTORY PATTERN ---------------------

// Factory Pattern: URLMappingFactory centralizes creation of URLMapping objects.
class URLMappingFactory {
    public static URLMapping createURLMapping(String originalUrl, ShortUrlGeneratorStrategy generator) {
        String shortUrl = generator.generateShortUrl(originalUrl);
        return new URLMapping(originalUrl, shortUrl);
    }
}

// --------------------- SINGLETON: URL SHORTENER SERVICE ---------------------

/*
 * Singleton Pattern: URLShortenerService provides the main API for the URL shortener.
 * It uses the repository to persist mappings and the generator strategy to create short URLs.
 */
class URLShortenerService {
    private static URLShortenerService instance;
    
    private URLRepository repository;
    private ShortUrlGeneratorStrategy generatorStrategy;
    private List<URLCreationObserver> observers;
    
    // Private constructor to enforce singleton.
    private URLShortenerService() {
        this.repository = new InMemoryURLRepository();
        this.generatorStrategy = new Base62UrlGenerator();
        this.observers = new ArrayList<>();
    }
    
    public static synchronized URLShortenerService getInstance() {
        if (instance == null) {
            instance = new URLShortenerService();
        }
        return instance;
    }
    
    // Optionally allow setting a custom strategy.
    public void setGeneratorStrategy(ShortUrlGeneratorStrategy strategy) {
        this.generatorStrategy = strategy;
    }
    
    // Register an observer for URL creation events.
    public void registerObserver(URLCreationObserver observer) {
        observers.add(observer);
    }
    
    // Create a short URL for a given original URL.
    public String shortenURL(String originalUrl) {
        // Check if mapping already exists.
        URLMapping mapping = repository.findByOriginalUrl(originalUrl);
        if (mapping != null) {
            return mapping.getShortUrl();
        }
        // Create a new mapping using the factory.
        mapping = URLMappingFactory.createURLMapping(originalUrl, generatorStrategy);
        repository.save(mapping);
        // Notify observers.
        notifyObservers(mapping);
        return mapping.getShortUrl();
    }
    
    // Retrieve the original URL from a short URL.
    public String getOriginalURL(String shortUrl) {
        URLMapping mapping = repository.findByShortUrl(shortUrl);
        if (mapping != null) {
            mapping.incrementHitCount();
            return mapping.getOriginalUrl();
        }
        return null;
    }
    
    private void notifyObservers(URLMapping mapping) {
        for (URLCreationObserver observer : observers) {
            observer.onURLCreated(mapping);
        }
    }
}

// --------------------- MAIN APPLICATION ---------------------

public class URLShortenerApp {
    public static void main(String[] args) {
        URLShortenerService service = URLShortenerService.getInstance();
        // Register an observer (e.g., for logging)
        service.registerObserver(new LoggingURLObserver());
        
        // Shorten some URLs.
        String originalUrl1 = "https://www.example.com/very/long/url/that/needs/to/be/shortened";
        String shortUrl1 = service.shortenURL(originalUrl1);
        System.out.println("Short URL for " + originalUrl1 + " is " + shortUrl1);
        
        String originalUrl2 = "https://www.anotherexample.com/some/other/long/path";
        String shortUrl2 = service.shortenURL(originalUrl2);
        System.out.println("Short URL for " + originalUrl2 + " is " + shortUrl2);
        
        // Retrieve original URL.
        String retrievedOriginal = service.getOriginalURL(shortUrl1);
        System.out.println("Retrieved original URL for " + shortUrl1 + " is " + retrievedOriginal);
    }
}

3. Detailed Explanation and Pros/Cons

Entities and Their Interactions

  • URLMapping: Encapsulates the relationship between an original URL and its shortened version. Additional attributes like creation time and hit count provide useful metadata.

  • Repository (DAO Pattern): The URLRepository interface and its in‑memory implementation abstract away data storage details, making the system more modular and testable.

Key Design Patterns

  1. Singleton Pattern (URLShortenerService):

    • Pros:

      • Centralized management and global access to the URL shortener functionality.

      • Ensures consistent state across the application.

    • Cons:

      • Can introduce global state that is hard to test and debug if misused.

  2. Factory Pattern (URLMappingFactory):

    • Pros:

      • Centralizes object creation, enabling flexible changes to instantiation logic without impacting client code.

    • Cons:

      • Adds an extra layer of abstraction which may be overkill for simple objects.

  3. Strategy Pattern (ShortUrlGeneratorStrategy):

    • Pros:

      • Allows easy substitution of different short URL generation algorithms (e.g., Base62 encoding vs. hash‑based).

      • Promotes open/closed principle; new strategies can be added without modifying existing code.

    • Cons:

      • Increases the number of classes and can complicate the design if too many strategies are introduced.

  4. Observer Pattern (URLCreationObserver):

    • Pros:

      • Decouples the core URL creation logic from auxiliary processes (such as logging, analytics, or notifications).

      • Enables dynamic subscription and notification.

    • Cons:

      • Requires careful management to avoid memory leaks (e.g., unregistered observers).

      • Can lead to unpredictable update order if multiple observers are involved.

Overall Pros and Cons for the URL Shortener LLD

Pros:

  • Modularity: Using design patterns keeps the code clean, modular, and easier to extend (e.g., switching out the URL generation strategy).

  • Testability: Abstraction via the repository and factory patterns allows for unit testing each component separately.

  • Scalability: Although the example uses in‑memory storage, the DAO pattern makes it straightforward to switch to a database-backed implementation for higher scalability.

Cons:

  • Complexity: Introducing multiple design patterns increases the number of classes and interfaces, which may add complexity for small-scale projects.

  • Overhead: For very simple URL shortening requirements, this layered approach might be more than what is needed.


4. Conclusion

This detailed LLD for a URL Shortener system leverages several well‑known design patterns—Singleton, Factory, Strategy, DAO, and Observer—to create a robust, flexible, and extensible solution. It clearly separates responsibilities: generating short URLs, managing persistence, and notifying external components of changes. Such a design is well suited for technical interviews, demonstrating a deep understanding of OOP principles and design patterns in Java.

Feel free to ask for any further clarifications or additional enhancements!

PreviousShopping Cart SystemNextChess Tournament System

Last updated 2 months ago