Bird
Raised Fist0

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

LLDmediumInterview freq: medium
Choose your preparation mode4 modes available
๐ŸŽฏ
Design an Online Blackjack Game
40 min ยท medium
medium

Design an online Blackjack game system that supports multiple players playing against a dealer, with betting, card dealing, and win condition evaluation.

  • Deck must support shuffling and drawing cards with thread-safe operations.
  • Hand holds cards and calculates Blackjack score with Ace as 1 or 11.
  • Dealer manages the game flow and must handle multiple concurrent player sessions.
  • Betting system tracks player bets and updates balances atomically.
  • Win condition must correctly determine Blackjack, bust, or dealer win.
๐Ÿ“‹
Guided Steps
1/5
Step 1
Design Card and Deck Classes
Define classes to represent a Card and a Deck. Deck should support shuffle and draw operations safely in a concurrent environment.
๐Ÿ’ก Hint
Use enum for Suit and Rank to prevent invalid values. Deck must synchronize drawCard() to avoid race conditions.
Use enum Suit and Rank for type safety; Deck uses private final List<Card> cards with synchronized drawCard() to ensure atomic removal.
Mid: Uses enum for Suit/Rank and synchronized drawCard(). Senior: Explains how synchronized prevents two threads drawing the same card by atomic check-and-remove.
โš ๏ธCommon Wrong Answer
Using String for Suit/Rank leads to runtime bugs due to typos; not synchronizing drawCard() causes race conditions where multiple threads draw the same card.
Step 2
Design Hand Class with Score Calculation
Design a Hand class that holds cards and calculates the Blackjack score considering Ace as 1 or 11.
๐Ÿ’ก Hint
Hand needs instance state (cards), so use a concrete class. Score calculation must handle multiple Aces correctly.
Hand is a concrete class holding List<Card> cards; score calculation method is public int calculateScore() with logic to treat Ace as 1 or 11.
Mid: Implements score calculation with Ace logic. Senior: Explains how multiple Aces are counted optimally to avoid busting.
โš ๏ธCommon Wrong Answer
Using interface for Hand without state leads to inability to hold cards; ignoring Ace flexibility causes incorrect scoring.
Step 3
Design Dealer Class Managing Game Flow
Design a Dealer class that manages the game flow, deals cards, and handles multiple concurrent player sessions.
๐Ÿ’ก Hint
Dealer holds Deck and manages multiple Hands; use synchronized methods or locks to protect shared Deck state.
Dealer has private final Deck deck; methods like public synchronized void dealCard(Hand hand) ensure atomic card dealing; uses composition for Hands lifecycle.
Mid: Uses synchronized methods to protect Deck access. Senior: Describes thread interleaving where without synchronization two players could get the same card.
โš ๏ธCommon Wrong Answer
Not synchronizing deck access causes race conditions; using static Deck shared globally breaks encapsulation and testability.
Step 4
Implement Betting System with Atomic Updates
Design a betting system that tracks player bets and updates balances atomically to avoid inconsistent states.
๐Ÿ’ก Hint
Use AtomicInteger or synchronized methods to update player balance safely in concurrent environment.
PlayerBalance uses private volatile int balance with public synchronized boolean placeBet(int amount) to atomically check and deduct balance.
Mid: Uses synchronized placeBet() to avoid race conditions. Senior: Explains how volatile alone is insufficient without synchronized for compound operations.
โš ๏ธCommon Wrong Answer
Using volatile int without synchronization leads to lost updates; using int without volatile or synchronization causes visibility issues.
Step 5
Define Win Condition Logic
Implement logic to determine the winner between player and dealer considering Blackjack rules.
๐Ÿ’ก Hint
Use enum for GameResult; implement method public GameResult evaluate(Hand playerHand, Hand dealerHand).
GameResult enum defines WIN, LOSE, PUSH; evaluate method uses Hand.calculateScore() and Blackjack rules to decide outcome.
Mid: Implements enum-based result and basic comparison. Senior: Explains how exhaustive switch on enum ensures all cases handled and prevents runtime bugs.
โš ๏ธCommon Wrong Answer
Using String for result leads to typo bugs; ignoring Blackjack natural causes incorrect winner determination.
๐Ÿ”Œ
API Design Challenge
Design the API endpoints and interactions for an online Blackjack game that supports multiple concurrent players, betting, and game state updates.
  • Consider RESTful endpoints for player actions like placing bets, hitting, standing, and retrieving game state.
  • Use WebSocket or server-sent events for real-time game updates to players.
  • Design APIs to handle concurrency and ensure consistency of game state across multiple players.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • Use enum for Suit and Rank to prevent invalid card values
  • Deck synchronized drawCard() to avoid race conditions
  • Hand class with calculateScore() handling Aces correctly
  • Dealer synchronized methods to manage Deck and Hands safely
  • PlayerBalance synchronized placeBet() with volatile balance field
Good to Have - 30%
  • GameResult enum with exhaustive switch in evaluate()
  • Proper composition relationships with lifecycle ownership
  • Avoid static mutable shared state
  • Clear separation of concerns between classes
  • Use of Java concurrency primitives (volatile, synchronized) explained
Bonus - 10%
  • Thread-safe shuffle implementation in Deck
  • Support for multiple concurrent games with isolated Dealer instances
  • Extensible design for adding new game rules or variants
Using String for Suit/Rank or GameResult leads to runtime typo bugs and poor IDE support, causing bugs that are hard to detect at compile time.
Not synchronizing Deck drawCard() causes race conditions where multiple threads draw the same card, leading to inconsistent game state.
Using volatile alone for compound operations like placeBet() leads to lost updates due to lack of atomicity, causing incorrect player balances.
Static mutable Deck or shared state breaks encapsulation and causes concurrency bugs, making testing and maintenance difficult.
Ignoring Ace flexibility in score calculation causes incorrect game outcomes, as Aces can count as 1 or 11 depending on hand context.
โœ…
Model Answer
โ–ผ Reveal

The design uses enums for Suit, Rank, and GameResult to ensure type safety and exhaustive handling. Deck class holds a List<Card> and synchronizes drawCard() to prevent concurrent access issues. Hand class holds cards and calculates score with correct Ace handling. Dealer composes Deck and Hands, synchronizing card dealing to avoid race conditions. PlayerBalance uses volatile balance and synchronized placeBet() to atomically update balances. The design avoids static mutable state and uses composition to manage lifecycles, ensuring thread safety and extensibility.

  • Use enum Suit and Rank for compile-time safety and to prevent invalid card values.
  • Synchronize Deck's drawCard() method to prevent race conditions in concurrent draws.
  • Implement Hand's calculateScore() to handle multiple Aces optimally as 1 or 11 to avoid busting.
  • Dealer synchronizes methods that access Deck to ensure thread-safe card dealing across multiple players.
  • PlayerBalance uses volatile int with synchronized placeBet() to atomically update balances and avoid lost updates.
  • GameResult enum with exhaustive switch in evaluation method ensures all game outcomes are handled, preventing bugs.
  • Avoid static mutable state to maintain encapsulation, testability, and prevent concurrency issues.
  • Use composition to manage lifecycles: Dealer owns Deck and Hands; Hand owns Cards; PlayerBalance associated with Player.
  • Use Java concurrency primitives (volatile, synchronized) to ensure visibility and atomicity in concurrent operations.
  • Thread-safe shuffle implementation in Deck supports concurrent games and extensibility.
  • Use enum Suit and Rank for Card to ensure compile-time safety and IDE support.
  • Deck class holds List<Card> with synchronized drawCard() to prevent concurrent draw race conditions.
  • Hand class holds List<Card> and implements calculateScore() handling multiple Aces as 1 or 11.
  • Dealer class composes Deck and manages multiple Hands with synchronized dealCard() to ensure thread-safe card dealing.
  • PlayerBalance uses volatile int balance with synchronized placeBet() to atomically check and update balance.
  • GameResult enum defines possible outcomes; evaluate method uses exhaustive switch to enforce all cases.
  • Composition relationships: Dealer owns Deck and Hands; Hand owns Cards; PlayerBalance associated with Player.
  • Use of synchronized methods and volatile fields to ensure visibility and atomicity in concurrent operations.
  • Abstract class not used here as no shared behavior with state across multiple subclasses requiring inheritance.
  • Avoid static shared mutable state to maintain testability and encapsulation.

Practice

(1/5)
1. In a Snake and Ladder game design, the Board class has a private constructor and a static synchronized getInstance() method. Which design pattern is applied here and why is it the correct choice?
easy
A. Singleton pattern, to ensure only one board instance exists during the game.
B. Factory pattern, because it controls object creation centrally.
C. Builder pattern, to incrementally construct the board with snakes and ladders.
D. Prototype pattern, to clone the board for multiple game sessions.

Solution

  1. Step 1: Identify the role of private constructor and static synchronized method

    The private constructor prevents external instantiation, and the synchronized getInstance() ensures thread-safe single instance creation.
  2. Step 2: Match to design pattern intent

    Singleton pattern ensures exactly one Board instance exists, which is critical to maintain consistent game state.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Singleton controls single instance creation with thread safety [check]
Hint: Private constructor + static getInstance() = Singleton pattern [check]
Common Mistakes:
2. In a Wallet System design, the Transaction class has a private constructor and a static synchronized getInstance() method that returns the single instance. Which design pattern is applied here and why is this the correct use case?
easy
A. Factory Method pattern, because it controls object creation with a static method.
B. Singleton pattern, ensuring only one Transaction instance exists to maintain consistency.
C. Builder pattern, to construct complex Transaction objects step-by-step.
D. Prototype pattern, to clone existing Transaction instances efficiently.

Solution

  1. Step 1: Identify the pattern from constructor and method signatures

    The private constructor prevents external instantiation, and the static synchronized getInstance() method ensures a single instance is created and accessed thread-safely.
  2. Step 2: Why Singleton fits this Wallet Transaction scenario

    Singleton is appropriate when exactly one instance is needed to coordinate actions or maintain global state, such as a ledger manager or transaction coordinator. Factory Method and Builder patterns do not enforce single instance, and Prototype is for cloning, not restricting instances.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Private constructor + synchronized static getInstance() -> Singleton pattern [check]
Hint: Private constructor + static synchronized method -> Singleton [check]
Common Mistakes:
3. In the Online Tic-Tac-Toe game design, the Board class uses a private constructor and a static synchronized getInstance() method to provide access. Which design pattern is applied here and why is this the correct choice?
easy
A. Singleton pattern, to ensure only one game board instance exists per game session.
B. Factory pattern, because it controls object creation centrally.
C. Builder pattern, to incrementally construct the board state.
D. Prototype pattern, to clone board states for undo functionality.

Solution

  1. Step 1: Identify the pattern from constructor and method

    The private constructor prevents external instantiation, and the static synchronized getInstance() ensures a single instance is created and accessed safely in concurrent contexts.
  2. Step 2: Why Singleton fits the Board class

    Since the game requires exactly one board per session to maintain consistent state, Singleton enforces this constraint. Factory and Builder patterns do not restrict instance count, and Prototype is for cloning, not single-instance control.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Private constructor + synchronized getInstance() -> Singleton pattern [check]
Hint: Private constructor + static getInstance() -> Singleton [check]
Common Mistakes:
4. In a multi-threaded parking lot system using Abstract Factory to create vehicles and spots, if parkVehicle() is not synchronized, which exact thread interleaving causes double-booking of the same spot?
medium
A. Thread 1 parks vehicle, Thread 2 parks vehicle after Thread 1 finishes.
B. Thread 1 checks spot availability -> true, context switch; Thread 2 checks spot availability -> true; both park simultaneously.
C. Thread 1 parks vehicle, Thread 2 waits for Thread 1 to release lock before parking.
D. Thread 1 and Thread 2 both fail to find any available spots and abort.

Solution

  1. Step 1: Identify race condition in parkVehicle()

    Without synchronization, two threads can both check spot availability before either marks it occupied.
  2. Step 2: Trace exact interleaving

    Thread 1 calls isAvailable() -> true, context switch before marking occupied; Thread 2 calls isAvailable() -> true; both proceed to park, causing double-booking.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Check-then-act gap without synchronization causes double-booking [check]
Hint: Check-then-act gap -> both see available, both park [check]
Common Mistakes:
5. ElectricCar requires ChargingSpots in the parking lot system. What is the minimum change to support this without modifying existing classes?
hard
A. Add ELECTRIC_CAR to VehicleType, CHARGING to SpotType, create ElectricCar extending Vehicle, and update SpotCompatibility.COMPATIBILITY_MAP accordingly.
B. Modify Vehicle class to include a chargingRequired flag and update all spot checks.
C. Create a new ChargingSpot class and modify ParkingLot to handle it specially.
D. Add a new interface Chargeable and make ElectricCar implement it, changing existing spot logic.

Solution

  1. Step 1: Identify extensibility without modifying existing classes

    Adding new enum values and a subclass extends functionality without changing existing class code, respecting OCP.
  2. Step 2: Evaluate options

    Modify Vehicle class to include a chargingRequired flag and update all spot checks. modifies Vehicle, violating OCP. Create a new ChargingSpot class and modify ParkingLot to handle it specially. requires modifying ParkingLot, violating minimal change. Add a new interface Chargeable and make ElectricCar implement it, changing existing spot logic. adds interface but requires changing spot logic, violating minimal change.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    New enums + subclass + compatibility map update -> minimal change [check]
Hint: Add enums + subclass + compatibility map, no existing class 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