Bird
Raised Fist0

Design an Online Blackjack Game - Deck, Hand, Dealer, Betting, Win Condition

LLDmediumInterview freq: medium
Choose your preparation mode4 modes available
▶
Class Trace
Step 1/4
Card and Deck composition

Deck composes multiple Card instances, managing their lifecycle tightly. Deck holds mutable state like currentIndex and cards list.

🎯 Tests understanding that Deck must be a concrete class with mutable state and synchronized methods to safely manage concurrent draws and shuffles.
public class Deck {
    private final List<Card> cards = new ArrayList<>();
    private int currentIndex = 0;
    public synchronized void shuffle() {
        Collections.shuffle(cards);
        currentIndex = 0;
    }
    public synchronized Card drawCard() {
        if (currentIndex >= cards.size()) return null;
        return cards.get(currentIndex++);
    }
}
Hand as a container for Cards

Hand aggregates Cards but does not own their lifecycle independently; Cards belong to Deck primarily.

🎯 Tests if candidate distinguishes aggregation (Hand uses Cards) from composition (Deck owns Cards) and understands Hand's role in calculating hand value.
public class Hand {
    private final List<Card> cards = new ArrayList<>();
    public void addCard(Card card) {
        cards.add(card);
    }
    public int getValue() {
        // Calculate blackjack hand value
        return 0; // simplified
    }
}
Player and Dealer inheritance from Hand

Player and Dealer both have a Hand, but Player adds betting state and synchronized placeBet method for thread safety.

🎯 Tests if candidate knows inheritance is used to share Hand behavior and state, and synchronized methods protect balance updates in Player.
public class Player {
    private final Hand hand = new Hand();
    private int balance;
    public synchronized boolean placeBet(int amount) {
        if (balance < amount) return false;
        balance -= amount;
        return true;
    }
    public Hand getHand() { return hand; }
}
Game composition and concurrency

Game composes Deck, Dealer, and Players. start() is synchronized to prevent concurrent game state corruption.

🎯 Tests if candidate understands composition lifecycle and why synchronized start() method is needed to avoid race conditions in game state.
public class Game {
    private final Deck deck = new Deck();
    private final Dealer dealer = new Dealer();
    private final List<Player> players = new ArrayList<>();
    public synchronized void start() {
        deck.shuffle();
        // deal cards, manage turns
    }
}
📐
Class Diagram
Class Diagram
       +----------------+
       |    <<Abstract>>|
       |      Card      |
       +----------------+
       | - rank: String |
       | - suit: String |
       +----------------+
       | + getRank():String |
       | + getSuit():String |
       +----------------+
              │
              │ composition
              │
       +----------------+
       |      Deck      |
       +----------------+
       | - cards: List<Card> |
       | - currentIndex: int |
       +----------------+
       | + shuffle():void [sync] |
       | + drawCard():Card [sync] |
       +----------------+
              │ composition
              │
       +----------------+
       |      Hand      |
       +----------------+
       | - cards: List<Card> |
       +----------------+
       | + addCard(Card):void |
       | + getValue():int |
       +----------------+
              │ inheritance
              │
       +----------------+
       |     Player     |
       +----------------+
       | - hand: Hand   |
       | - balance: int |
       +----------------+
       | + placeBet(int):boolean [sync] |
       | + getHand():Hand |
       +----------------+
              │ inheritance
              │
       +----------------+
       |     Dealer    |
       +----------------+
       | - hand: Hand   |
       +----------------+
       | + dealCard(Player):void [sync] |
       | + getHand():Hand |
       +----------------+
              │ composition
              │
       +----------------+
       |      Game      |
       +----------------+
       | - deck: Deck   |
       | - dealer: Dealer |
       | - players: List<Player> |
       +----------------+
       | + start():void [sync] |
       | + evaluateWin():void |
       +----------------+

This class diagram illustrates key Java design decisions for an online Blackjack game. Card is an abstract class representing rank and suit, which Deck composes to manage card lifecycle and state with synchronized methods to ensure thread safety during shuffle and draw. Hand aggregates Cards to calculate hand value but does not own card lifecycle, enforcing aggregation semantics. Player and Dealer both have Hand instances; Player adds betting state with synchronized placeBet to protect balance updates, demonstrating inheritance for shared behavior and state. Game composes Deck, Dealer, and Players, with synchronized start() method to prevent concurrent state corruption, enforcing strong lifecycle ownership and concurrency safety. These decisions reflect SOLID principles, Java concurrency mechanisms, and lifecycle ownership clarity.

Primary Focus

✓ Abstract class vs Interface state + Composition lifecycle + Singleton concurrency + Method synchronization

Practice

(1/5)
1. In designing the Reputation system for Stack Overflow, should Reputation be an abstract class or an interface, and why?
easy
A. Abstract class, because Reputation holds mutable state like points and needs default behavior
B. Interface, because Reputation only defines behavior without state, allowing multiple implementations
C. Abstract class, because interfaces cannot have methods with parameters
D. Interface, because it allows storing state variables directly

Solution

  1. Step 1: Analyze Reputation's need for state and behavior

    Reputation tracks mutable points and provides default methods like addPoints(), so it requires state and behavior.
  2. Step 2: Abstract class vs interface capabilities

    Abstract classes can hold state and provide default implementations; interfaces cannot hold instance state (prior to Java 8 default methods, but still no fields).
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Mutable state + default behavior -> Abstract class [check]
Hint: Stateful behavior -> abstract class; stateless behavior -> interface [check]
Common Mistakes:
2. The Car Rental System currently uses a FeeStrategy interface for pricing. To add a WeekendFeeStrategy without modifying existing classes, which SOLID principle is being applied, and what is the minimum required change?
medium
A. Single Responsibility Principle; modify the existing FeeStrategy implementations to include weekend logic.
B. Open/Closed Principle; create a new WeekendFeeStrategy class implementing FeeStrategy without changing existing code.
C. Liskov Substitution Principle; override existing FeeStrategy methods to handle weekend fees.
D. Dependency Inversion Principle; inject WeekendFeeStrategy into existing classes by modifying their constructors.

Solution

  1. Step 1: Identify the SOLID principle for extensibility

    Open/Closed Principle (OCP) states software entities should be open for extension but closed for modification.
  2. Step 2: Determine minimal change to add WeekendFeeStrategy

    To avoid modifying existing classes, inject the new WeekendFeeStrategy via dependency injection, applying Dependency Inversion Principle (DIP) to depend on abstractions.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Inject new strategy without modifying existing classes -> DIP [check]
Hint: Add new strategy via injection, not modifying existing code [check]
Common Mistakes:
3. To add a WeekendFeeStrategy for charging players extra fees on weekends in the current Chess game design, which minimal change respects SOLID principles?
medium
A. Add a boolean isWeekend flag to the Player class and check it during fee calculation.
B. Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without changing existing classes.
C. Extend the FeeCalculator class with WeekendFeeCalculator subclass overriding fee logic.
D. Modify the existing FeeCalculator class to add weekend fee logic inside the calculate method.

Solution

  1. Step 1: Identify SOLID principle involved

    The Open-Closed Principle (OCP) requires the system to be open for extension but closed for modification.
  2. Step 2: Evaluate options against OCP

    Add a boolean isWeekend flag to the Player class and check it during fee calculation. adds a flag to Player, allowing fee calculation to adapt without modifying FeeCalculator or creating new classes, thus minimally invasive.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Adding flag avoids modifying existing fee classes [check]
Hint: OCP: extend behavior without modifying existing classes [check]
Common Mistakes:
4. You want to add a WeekendFeeStrategy to calculate plugin usage fees on weekends without modifying existing plugin classes. Which SOLID principle guides the minimum change, and what is the correct approach?
medium
A. Open-Closed Principle: create a new WeekendFeeStrategy class implementing FeeStrategy; existing classes remain unchanged.
B. Dependency Inversion Principle: inject WeekendFeeStrategy into Plugin constructor, modifying Plugin to accept it.
C. Single Responsibility Principle: modify Plugin class to add weekend fee logic.
D. Liskov Substitution Principle: subclass Plugin to override fee calculation for weekends.

Solution

  1. Step 1: Identify the SOLID principle for adding new behavior without modifying existing code

    The Open-Closed Principle (OCP) states software entities should be open for extension but closed for modification.
  2. Step 2: Apply OCP by creating a new WeekendFeeStrategy implementing FeeStrategy interface

    This allows adding weekend fee logic without changing existing plugin or fee classes.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    OCP: extend with new strategy class, no existing class changes [check]
Hint: OCP: add new class, don't modify existing ones [check]
Common Mistakes:
5. ElectricCar requires ChargingSpots in the parking system using Prototype pattern. What is the minimum change to support this without modifying existing classes?
hard
A. Modify Vehicle base class to include charging logic and add ChargingSpot as a subclass of Spot
B. Add charging logic inside Vehicle clone() and modify SpotCompatibility map inside existing classes
C. Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update SpotCompatibility map externally
D. Create a god-class that handles both ElectricCar and ChargingSpot logic to avoid modifying existing classes

Solution

  1. Step 1: Identify extensibility requirements

    Adding ElectricCar and ChargingSpot must avoid modifying existing classes to respect OCP.
  2. Step 2: Evaluate options for minimal invasive change

    Add ELECTRIC_CAR to VehicleType enum, CHARGING to SpotType enum, create ElectricCar subclass, and update SpotCompatibility map externally adds new enum values and subclasses externally, updating compatibility map without changing existing code. Options B and C modify base classes, violating OCP. Create a god-class that handles both ElectricCar and ChargingSpot logic to avoid modifying existing classes creates god-class, violating SRP and maintainability.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    New enums + subclass + external map update -> minimal change, OCP respected [check]
Hint: New enums + subclass + external map -> minimal invasive change [check]
Common Mistakes:
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard