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 - Deck, Hand, Dealer, Betting, Win Condition
Low-level design of an online Blackjack game focusing on card management, player hands, dealer logic, betting, and win conditions with concurrency safety.
mediumGame DesignFreq: medium
Prerequisites:OOP FundamentalsJava Concurrency BasicsDesign Patterns (Factory, Strategy, Template Method)
⚙️
Functional Requirements
  • Support multiple players per game session
  • Deck must be shuffled and cards dealt without duplication
  • Players can place bets before cards are dealt
  • Dealer automatically plays after players finish
  • Win conditions include Blackjack, bust, higher hand value
  • Betting system must update player balances atomically
  • Game state must be consistent under concurrent player actions
👤
Use Cases
  • Player joins a game and places a bet
  • Dealer shuffles and deals cards
  • Player hits or stands
  • Dealer plays according to Blackjack rules
  • Determine winner and payout bets
  • Handle player disconnects mid-game
🧑‍💻
Actors
  • Player
  • Dealer
  • Game Server
  • Betting System
❓
Clarifying Questions to Ask
  • How many concurrent players per game session are expected?
  • Should the betting system support concurrent bet placements?
  • What happens if a player disconnects during their turn?
  • Is the deck shared across multiple games or unique per game?
  • Are there any time limits on player actions?
  • Should the dealer logic be extensible for rule variations?
  • How to handle ties or pushes in betting payouts?
Out of Scope
✗ User authentication and account management
✗ Multi-table tournament management
✗ Complex betting options like splits or insurance
✗ UI/UX design
⚡
QPS Calculation
expected players per game5
actions per minute per player2
total actions per minute10
qps1
🖥️
Server Estimate
games per server100
servers required100
🔢
Enumerations
enum Suit
HEARTS | DIAMONDS | CLUBS | SPADES
Enum prevents typo bugs and enables exhaustive switch statements for card suits.
enum Rank
TWO | THREE | FOUR | FIVE | SIX | SEVEN | EIGHT | NINE | TEN | JACK | QUEEN | KING | ACE
Enum encapsulates card ranks with fixed values, enabling consistent value mapping and comparison.
enum GameState
WAITING_FOR_BETS | DEALING | PLAYER_TURN | DEALER_TURN | ROUND_COMPLETE
Enum models finite game states, enforcing valid state transitions and preventing invalid operations.
enum HandResult
WIN | LOSE | PUSH | BLACKJACK | BUST
Enum clearly defines possible hand outcomes, simplifying result evaluation and payout logic.
📦
Card
2 fields
private final Suit suitprivate final Rank rank
Represents a playing card with immutable suit and rank.
public Suit getSuit()Returns the suit of the card.
public Rank getRank()Returns the rank of the card.
public int getValue()Returns Blackjack value of the card (e.g., 10 for face cards, 1 or 11 for Ace).
📦
Deck
2 fields
private final List<Card> cardsprivate int currentCardIndex
Manages a collection of 52 cards, supports shuffling and dealing cards without duplication.
public synchronized void shuffle()Randomizes the order of cards and resets currentCardIndex atomically.
public synchronized Card dealCard()Returns the next card from the deck or null if empty, ensuring thread-safe dealing.
public synchronized int cardsRemaining()Returns the number of undealt cards remaining.
📦
Hand
2 fields
private final List<Card> cardsprivate volatile boolean isStand
Represents a player's or dealer's hand, calculates hand value and tracks if player stands.
public synchronized void addCard(Card card)Adds a card to the hand atomically.
public synchronized int getValue()Calculates the best Blackjack value of the hand considering Ace as 1 or 11.
public synchronized boolean isBust()Returns true if hand value exceeds 21.
public void stand()Marks the hand as standing; volatile ensures visibility across threads.
public boolean isStand()Returns whether the hand is standing.
📦
Player
4 fields
private final String playerIdprivate Hand handprivate volatile int balanceprivate volatile int currentBet
Represents a player with a hand, balance, and current bet; manages betting and actions.
public synchronized boolean placeBet(int amount)Atomically checks balance and places bet if sufficient funds exist.
public synchronized void adjustBalance(int amount)Atomically updates player balance by amount (positive or negative).
public Hand getHand()Returns the player's current hand.
public synchronized void resetHand()Resets the player's hand for a new round.
📦
Dealer
2 fields
private Hand handprivate Deck deck
Manages the deck, deals cards, and plays dealer hand according to Blackjack rules.
public synchronized void startNewRound()Shuffles deck and resets dealer hand atomically.
public synchronized void dealInitialCards(List<Player> players)Deals two cards to each player and dealer in thread-safe manner.
public synchronized void playDealerHand()Dealer hits until hand value >= 17, synchronized to prevent concurrent modification.
public synchronized void dealCardToHand(Hand hand)Deals one card from deck to specified hand atomically.
📦
Game
3 fields
private final List<Player> playersprivate final Dealer dealerprivate volatile GameState state
Coordinates game flow, manages state transitions, and enforces game rules.
public synchronized boolean addPlayer(Player player)Adds a player to the game if not started, synchronized to prevent concurrent adds.
public synchronized void startGame()Transitions state to DEALING and triggers dealer to start new round.
public synchronized void playerAction(Player player, String action)Processes player hit or stand actions with state validation.
public synchronized void determineWinner()Evaluates hands and updates player balances accordingly.
🗄️
Players
PK: playerId
playerId STRINGgameId STRINGbalance INTcurrentBet INThand BLOB
🗄️
Games
PK: gameId
gameId STRINGstate STRINGdealerHand BLOBplayerIds LIST<STRING>
🔌
POST
/joinGame
Request
{
  "playerId": "string",
  "gameId": "string"
}
Response
{
  "success": "boolean",
  "message": "string"
}
Allows a player to join a game session before it starts.
🔌
POST
/placeBet
Request
{
  "playerId": "string",
  "amount": "int"
}
Response
{
  "success": "boolean",
  "balance": "int",
  "message": "string"
}
Player places a bet; balance updated atomically.
🔌
POST
/playerAction
Request
{
  "playerId": "string",
  "action": "string"
}
Response
{
  "success": "boolean",
  "handValue": "int",
  "message": "string"
}
Player performs hit or stand action during their turn.

Practice

(1/5)
1. In the Car Rental System, the BookingManager class has a private constructor and a static synchronized getInstance() method. Which design pattern is applied here, and why is this the correct choice for managing bookings?
easy
A. Singleton pattern, to ensure only one BookingManager handles all bookings globally.
B. Prototype pattern, to clone BookingManager instances for concurrent booking handling.
C. Builder pattern, to construct complex BookingManager objects step-by-step.
D. Factory pattern, because it creates BookingManager instances on demand.

Solution

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

    A private constructor prevents external instantiation, and a static synchronized getInstance() method ensures a single shared instance, matching the Singleton pattern.
  2. Step 2: Reason why Singleton fits BookingManager

    BookingManager must coordinate all bookings centrally to avoid conflicts; multiple instances would cause inconsistent state, so Singleton ensures global coordination.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Private constructor + static synchronized getInstance() -> Singleton [check]
Hint: Private constructor + static getInstance() -> Singleton [check]
Common Mistakes:
2. In the Facebook OOP design, the User class has a private constructor and a static synchronized getInstance() method to ensure only one instance per user ID exists. Which design pattern is applied here and why is it the correct choice?
easy
A. Singleton Pattern, to ensure a single instance per user ID and control concurrent access.
B. Prototype Pattern, to clone existing User instances efficiently.
C. Builder Pattern, to construct complex User objects step-by-step.
D. Factory Pattern, because it creates objects without exposing the instantiation logic.

Solution

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

    The private constructor and static synchronized getInstance() method indicate Singleton usage to control instance creation.
  2. Step 2: Why Singleton fits here

    Singleton ensures only one User instance per user ID exists, preventing duplicate objects and managing concurrency safely.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Private constructor + synchronized getInstance() -> Singleton pattern [check]
Hint: Private constructor + synchronized getInstance() -> Singleton [check]
Common Mistakes:
3. In the Jigsaw Puzzle System, the Board class has a placePiece() method that first checks if a position is free and then places the piece. Without synchronizing placePiece(), what exact sequence of thread interleaving causes two threads to place different pieces at the same position (double-booking)?
medium
A. Thread 1 checks position free -> true, context switch; Thread 2 checks position free -> true; both place pieces concurrently.
B. Thread 1 places piece, Thread 2 waits, then places piece after Thread 1 finishes.
C. Thread 1 and Thread 2 both call placePiece() but on different board instances, causing no conflict.
D. Thread 1 locks the board, Thread 2 spins waiting, so no double-booking occurs.

Solution

  1. Step 1: Identify the check-then-act race condition

    Without synchronization, Thread 1 checks position free and sees true, but before placing, context switches.
  2. Step 2: Thread 2 also checks position free -> true and proceeds to place piece concurrently, causing double-booking.

  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Unsynchronized check-then-act allows both threads to place pieces at same position [check]
Hint: Check-then-act without sync -> double-booking race [check]
Common Mistakes:
4. The auction system currently calculates fees with a fixed strategy. To add a WeekendFeeStrategy without modifying existing classes, which SOLID principle is being applied and what is the minimum required change?
medium
A. Open/Closed Principle: create a new WeekendFeeStrategy class implementing FeeStrategy; inject it without changing existing code.
B. Liskov Substitution Principle: subclass Auction to WeekendAuction overriding fee calculation.
C. Single Responsibility Principle: modify Auction class to handle weekend fees directly.
D. Dependency Inversion Principle: hardcode weekend fee logic inside FeeStrategy implementation.

Solution

  1. Step 1: Identify SOLID principle

    Open/Closed Principle (OCP) states software entities should be open for extension but closed for modification.
  2. Step 2: Apply OCP by adding WeekendFeeStrategy implementing FeeStrategy interface without modifying existing classes.

  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    New strategy class + injection -> OCP compliance [check]
Hint: Add new strategy class, no existing code change -> OCP [check]
Common Mistakes:
5. You want to add a WeekendFeeStrategy to the parking lot pricing system. Which minimum change respects SOLID principles?
medium
A. Add a boolean flag isWeekend to Vehicle and adjust pricing accordingly.
B. Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without changing existing classes.
C. Modify the existing FeeCalculator class to add weekend logic inside.
D. Create a subclass WeekendFeeCalculator extending FeeCalculator and override methods.

Solution

  1. Step 1: Identify SOLID principle involved

    Open-Closed Principle (OCP) requires adding new functionality without modifying existing code.
  2. Step 2: Evaluate options

    Modify the existing FeeCalculator class to add weekend logic inside. violates OCP by modifying existing class. Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without changing existing classes. respects OCP by adding new strategy class. Add a boolean flag isWeekend to Vehicle and adjust pricing accordingly. incorrectly mixes state into Vehicle, violating SRP. Create a subclass WeekendFeeCalculator extending FeeCalculator and override methods. adds subclass but may cause class explosion and tight coupling.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    New strategy class + no existing class change -> OCP respected [check]
Hint: Add new strategy class, avoid modifying existing code [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