Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • About
  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

👥10,050visitors

Built for engineers who want to go deep.


← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight

Behavioral Design Patterns

  • Strategy Pattern
  • Observer Pattern
  • Command Pattern
  • Template Method
  • State Pattern
  • Chain of Responsibility
  • Iterator & Mediator
  • Visitor Pattern

Machine Coding Exercises

  • Parking Lot — Requirements & Class Design
  • Parking Lot — Implementation
  • Elevator System — Requirements & Class Design
  • Elevator System — Implementation & Scheduling
  • Library Management System
  • Tic-Tac-Toe / Board Game Design
  • Chess Engine Design
  • Vending Machine Design
  • ATM System Design
  • In-Memory Rate Limiter
  • Thread-Safe LRU Cache
  • Connection Pool Design
  • Producer-Consumer Class Design
  • Movie Ticket Booking System
  • Hotel Reservation System
  • Airline Booking / Seat Selection System
  • Splitwise / Expense Sharing System
  • Design a Logging Framework
  • Design a Notification/Observer-Based Pub-Sub
  • Design a Cache with Pluggable Eviction Policy
  • Design Snake and Ladder
  • Design a Card Game Framework
  • Design an Inventory Management System
  • Restaurant Management System
  • Food Delivery Order Matching
  • Design a Shopping Cart & Checkout Flow
  • Design a Recommendation Engine
  • Design a Job Scheduler
  • Design a Circuit Breaker
  • Design a Distributed ID Generator
  • Design a Pub-Sub Message Broker
  • Design Twitter/X
  • Multi-Pattern Problem: Design a Ride Booking Class Model
  • Multi-Pattern Problem: Design a Food Ordering Class Model
  • Design an Authentication/Authorization Module
  • Design a Form/Survey Builder
  • Design a Meeting Room / Calendar Booking System
  • Design a Search/Filter Engine for an E-Commerce Catalog
  • Design a Voting/Polling System
  • Design a Retry Mechanism with Backoff
  • Design a Health Check Aggregator
  • Design a Feature Flag System
  • Design a Config Management Client
  • Design a Leader Election Algorithm
  • Design a Distributed Counter
  • Design a Bloom Filter
  • Design a Consistent Hashing Ring
  • Design a Distributed Tracing Library
  • Design a Metrics Collection Library
  • Design an Audit Logging Framework
  • Design a Plugin/Extension System
  • Design a Workflow Engine
  • Multi-Pattern Capstone: Design a Food Delivery App
Chaturmind
← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight

Behavioral Design Patterns

  • Strategy Pattern
  • Observer Pattern
  • Command Pattern
  • Template Method
  • State Pattern
  • Chain of Responsibility
  • Iterator & Mediator
  • Visitor Pattern

Machine Coding Exercises

  • Parking Lot — Requirements & Class Design
  • Parking Lot — Implementation
  • Elevator System — Requirements & Class Design
  • Elevator System — Implementation & Scheduling
  • Library Management System
  • Tic-Tac-Toe / Board Game Design
  • Chess Engine Design
  • Vending Machine Design
  • ATM System Design
  • In-Memory Rate Limiter
  • Thread-Safe LRU Cache
  • Connection Pool Design
  • Producer-Consumer Class Design
  • Movie Ticket Booking System
  • Hotel Reservation System
  • Airline Booking / Seat Selection System
  • Splitwise / Expense Sharing System
  • Design a Logging Framework
  • Design a Notification/Observer-Based Pub-Sub
  • Design a Cache with Pluggable Eviction Policy
  • Design Snake and Ladder
  • Design a Card Game Framework
  • Design an Inventory Management System
  • Restaurant Management System
  • Food Delivery Order Matching
  • Design a Shopping Cart & Checkout Flow
  • Design a Recommendation Engine
  • Design a Job Scheduler
  • Design a Circuit Breaker
  • Design a Distributed ID Generator
  • Design a Pub-Sub Message Broker
  • Design Twitter/X
  • Multi-Pattern Problem: Design a Ride Booking Class Model
  • Multi-Pattern Problem: Design a Food Ordering Class Model
  • Design an Authentication/Authorization Module
  • Design a Form/Survey Builder
  • Design a Meeting Room / Calendar Booking System
  • Design a Search/Filter Engine for an E-Commerce Catalog
  • Design a Voting/Polling System
  • Design a Retry Mechanism with Backoff
  • Design a Health Check Aggregator
  • Design a Feature Flag System
  • Design a Config Management Client
  • Design a Leader Election Algorithm
  • Design a Distributed Counter
  • Design a Bloom Filter
  • Design a Consistent Hashing Ring
  • Design a Distributed Tracing Library
  • Design a Metrics Collection Library
  • Design an Audit Logging Framework
  • Design a Plugin/Extension System
  • Design a Workflow Engine
  • Multi-Pattern Capstone: Design a Food Delivery App
HomeLearnSystem DesignLow-Level Design & Design PatternsMachine Coding Exercises
✓ FreeIntermediate· 7 min read

Design a Recommendation Engine

RecommendationStrategy interface with collaborative-filtering/content-based implementations, a rule-based placeholder for where ML plugs in later, and one more Strategy pattern rep.

Published September 23, 2026


Design a Recommendation Engine

Core classes

interface RecommendationStrategy {
    List<Product> recommend(User user, int count);
}

class CollaborativeFilteringStrategy implements RecommendationStrategy {
    // recommends based on what SIMILAR USERS bought/liked
    public List<Product> recommend(User user, int count) {
        List<User> similarUsers = findSimilarUsers(user);
        return aggregateTopProducts(similarUsers, count);
    }
}

class ContentBasedStrategy implements RecommendationStrategy {
    // recommends based on ATTRIBUTES of what this specific user has liked before
    public List<Product> recommend(User user, int count) {
        List<Product> userHistory = user.getPurchaseHistory();
        return findSimilarProducts(userHistory, count);
    }
}

class RecommendationEngine {
    private final RecommendationStrategy strategy; // swappable
    List<Product> getRecommendations(User user) { return strategy.recommend(user, 10); }
}

Collaborative filtering ("people similar to you also liked X") and content-based filtering ("you liked Y, and Z shares Y's attributes") are genuinely different algorithms with different data requirements (the former needs a population of other users' behavior; the latter can work from one user's own history plus product metadata alone) — modeling them as interchangeable RecommendationStrategy implementations lets RecommendationEngine stay agnostic to which approach (or combination) is active.

Advertisement

A rule-based recommender as an ML placeholder

class RuleBasedStrategy implements RecommendationStrategy {
    public List<Product> recommend(User user, int count) {
        // simple, explainable placeholder: "most popular in categories this user has purchased from"
        Set<Category> userCategories = user.getPurchaseHistory().stream().map(Product::getCategory).collect(Collectors.toSet());
        return productRepository.findTopSellingIn(userCategories, count);
    }
}

This is worth naming explicitly as a deliberate, simple starting implementation — real recommendation systems eventually reach for ML models (embeddings, learned ranking), but the interface stays the same: a future MLRankingStrategy implements RecommendationStrategy slots into RecommendationEngine exactly like RuleBasedStrategy does today, with zero changes to any calling code. This is a genuinely common real-world pattern — ship a simple rule-based version first, behind an abstraction that doesn't need to change when the ML version eventually replaces it.

Making collaborative filtering concrete

"Find similar users" is hand-waving until you define similarity. A compact, interview-sized item-based collaborative filter works like this: two products are similar if the same users interact with both. Item-based filtering is usually preferred to user-based, because item relationships change more slowly and can be precomputed.

class ItemSimilarityIndex {
    // productId -> (other productId -> similarity score)
    private final Map<String, Map<String, Double>> similar = new HashMap<>();

    /** Offline job: build co-occurrence counts from purchase histories. */
    void rebuild(Map<String, Set<String>> purchasesByUser) {
        Map<String, Integer> itemCount = new HashMap<>();
        Map<String, Map<String, Integer>> pairCount = new HashMap<>();
        for (Set<String> items : purchasesByUser.values()) {
            for (String a : items) {
                itemCount.merge(a, 1, Integer::sum);
                for (String b : items) if (!a.equals(b))
                    pairCount.computeIfAbsent(a, k -> new HashMap<>()).merge(b, 1, Integer::sum);
            }
        }
        similar.clear();
        pairCount.forEach((a, others) -> others.forEach((b, both) -> {
            double cosine = both / Math.sqrt((double) itemCount.get(a) * itemCount.get(b));  // normalises for popularity
            similar.computeIfAbsent(a, k -> new HashMap<>()).put(b, cosine);
        }));
    }

    Map<String, Double> neighbours(String productId) { return similar.getOrDefault(productId, Map.of()); }
}

class ItemBasedCfStrategy implements RecommendationStrategy {
    private final ItemSimilarityIndex index; private final ProductCatalog catalog;
    ItemBasedCfStrategy(ItemSimilarityIndex index, ProductCatalog catalog) { this.index = index; this.catalog = catalog; }

    public List<Product> recommend(User user, int count) {
        Set<String> owned = user.purchasedProductIds();
        Map<String, Double> scores = new HashMap<>();
        for (String p : owned)
            index.neighbours(p).forEach((q, sim) -> { if (!owned.contains(q)) scores.merge(q, sim, Double::sum); });
        return scores.entrySet().stream()
                .sorted(Map.Entry.<String, Double>comparingByValue().reversed())
                .limit(count)
                .map(e -> catalog.byId(e.getKey()))
                .toList();
    }
}

The points worth explaining:

  • Cosine normalization: dividing by sqrt(count(a) × count(b)) stops bestsellers from being "similar" to everything just because everyone buys them.
  • Offline vs online split: rebuild is a batch job (nightly, or hourly). recommend only does lookups and a small aggregation, so it's fast enough for a page load. This split mirrors how production recommenders work.
  • Filtering already-owned items is easy to forget, and very visible when wrong.
  • The pairwise loop is O(k²) per user, where k is the number of items that user owns. Cap k (for example, recent purchases only) for heavy users.

A pipeline instead of a single strategy

Production recommenders are usually a pipeline:

  1. Candidate generation: several strategies (CF neighbours, same category, trending) each propose a few hundred items.
  2. Filtering: remove out-of-stock, already-purchased or blocked items.
  3. Ranking: score all candidates with one model or rule.
  4. Re-ranking for diversity and business rules: don't show ten phone cases, pin promoted items.
class RecommendationPipeline implements RecommendationStrategy {
    private final List<RecommendationStrategy> candidateSources;
    private final List<Predicate<Product>> filters;
    private final Ranker ranker;
    private final Diversifier diversifier;

    public List<Product> recommend(User user, int count) {
        Set<Product> candidates = new LinkedHashSet<>();
        candidateSources.forEach(s -> candidates.addAll(s.recommend(user, 200)));
        List<Product> kept = candidates.stream().filter(p -> filters.stream().allMatch(f -> f.test(p))).toList();
        return diversifier.apply(ranker.rank(user, kept), count);
    }
}

The pipeline is itself a RecommendationStrategy (Composite), so the engine and its callers don't change. Every stage is independently testable and replaceable, which is exactly what makes swapping in an ML ranker later painless.

Follow-up questions this topic invites — and their answers

Q: How would you A/B test two different RecommendationStrategy implementations in production? A: A wrapping strategy (or the engine itself) that routes a percentage of users to each underlying strategy based on a consistent hash of user ID (ensuring the same user always sees the same variant for the test's duration) — the Strategy abstraction makes this a routing decision at the injection point, not a change to either strategy's own logic.

Q: What's a cold-start problem, and how does it affect the choice between these strategies? A: A new user with no purchase history breaks ContentBasedStrategy (nothing to base similarity on) and weakens CollaborativeFilteringStrategy (no signal for 'similar users' to match against) — RuleBasedStrategy's simple 'popular in general' fallback is often specifically valuable for exactly this case, which is a real argument for combining strategies (fall back to rule-based when history is empty) rather than picking one exclusively.

Q: Could RecommendationEngine hold multiple strategies and combine their outputs, rather than just one? A: Yes — a CompositeStrategy implementing the same interface, internally calling several strategies and merging/deduplicating/re-ranking their results, is a natural extension that stays consistent with the same interface, rather than requiring RecommendationEngine to know about combining logic itself.

Q: Why does this design pattern-match to Strategy specifically, rather than, say, Factory? A: The choice being made here is 'which algorithm computes recommendations' (interchangeable behavior behind one interface) — Factory would be relevant if the question were instead 'which concrete RecommendationStrategy object should be CONSTRUCTED given some condition,' a related but distinct concern (see Factory & Abstract Factory) that could reasonably pair with this design (a factory choosing which strategy to instantiate) without being the core pattern itself.

Q: Why is item-based collaborative filtering usually preferred over user-based? A: There are usually fewer items than users, and item-to-item relationships change slowly, so they can be precomputed offline and reused for every user. User-to-user similarity changes with each action, and is expensive to compute at request time.

Q: How do you stop popular items from dominating every recommendation list? A: Normalize similarity by item popularity (cosine or Jaccard instead of raw co-occurrence counts), cap how many candidates any one source contributes, and apply diversity re-ranking so the final list covers several categories.

Q: How would you evaluate whether a new strategy is better? A: Offline, replay historical data and measure metrics such as precision@k or recall@k on held-out interactions. Then run an online A/B test measuring click-through, conversion and long-term engagement, because offline gains don't always translate to user behaviour.

Advertisement

Previous

Design a Shopping Cart & Checkout Flow

Next

Design a Job Scheduler

AI Tutor

Lesson: Design a Recommendation Engine

Quick actions

AI responses can be inaccurate. Verify critical information.