> For the complete documentation index, see [llms.txt](https://mayanktyagi3111.gitbook.io/interview-prep/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mayanktyagi3111.gitbook.io/interview-prep/lld-questions/url-shortener-system.md).

# 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

```java
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!
