CASE STUDY

Blackjack Game (Low-Level Design)

3 min read·434 words·Beginner

Asked at

1 candidate report in Feb 2026

How to use this case study

SDE-2 / Mid

Model cards, deck/shoe, hands, players and dealer, and compute a hand's best value with Aces counting as 1 or 11.

SDE-3 / Senior

Implement the game flow (deal, hit/stand, dealer rules, settlement), detect blackjack and busts, and keep classes extensible.

Staff / Principal

Discuss extensions (split, double down, insurance, multiple players and decks), testability with a seeded shuffle, and separating rules from the game engine.


0) Problem Restatement

Design Blackjack in an object-oriented way (asked at Goldman Sachs). The tricky part candidates often miss: an Ace counts as 1 or 11, whichever is better for the hand. Also detect a blackjack (Ace + a 10-value card as the first two cards) and a bust (over 21).

Rules (simplified): each player and the dealer get 2 cards. Players choose hit (take a card) or stand. Going over 21 = bust (you lose). Then the dealer draws until reaching at least 17. Closest to 21 without busting wins, and a natural blackjack beats a regular 21.

1) Classes

Architecture Diagram

classDiagram
    class Card { +Rank rank +Suit suit +value() int }
    class Shoe { -List cards +draw() Card +shuffle(seed) void }
    class Hand { +List cards +add(card) +bestValue() int +isBlackjack() bool +isBust() bool }
    class Participant { +Hand hand }
    class Player { +String name +int bet +decide(dealerUpCard) Action }
    class Dealer { +shouldHit() bool }
    class Game { -Shoe shoe -List players -Dealer dealer +playRound() Map }
    Participant <|-- Player
    Participant <|-- Dealer
    Game --> Shoe
    Game --> Player
    Game --> Dealer
    Participant --> Hand
    Hand --> Card

2) The Ace Logic (core algorithm)

Count every Ace as 1 first, then turn one Ace into 11 (adding 10) if that doesn't exceed 21. Two Aces as 11 would be 22, so at most one Ace can ever count as 11.

import random

RANKS = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
SUITS = ['♠', '♥', '♦', '♣']

class Card:
    def __init__(self, rank, suit): self.rank, self.suit = rank, suit
    def base_value(self):
        if self.rank == 'A': return 1
        if self.rank in ('J', 'Q', 'K'): return 10
        return int(self.rank)

class Hand:
    def __init__(self): self.cards = []
    def add(self, card): self.cards.append(card)
    def best_value(self):
        total = sum(c.base_value() for c in self.cards)
        if any(c.rank == 'A' for c in self.cards) and total + 10 <= 21:
            total += 10                      # one Ace counts as 11
        return total
    def is_soft(self):                       # an Ace is currently counted as 11
        hard = sum(c.base_value() for c in self.cards)
        return any(c.rank == 'A' for c in self.cards) and hard + 10 <= 21
    def is_blackjack(self): return len(self.cards) == 2 and self.best_value() == 21
    def is_bust(self): return self.best_value() > 21

class Shoe:
    def __init__(self, decks=6, seed=None):
        self.cards = [Card(r, s) for _ in range(decks) for s in SUITS for r in RANKS]
        random.Random(seed).shuffle(self.cards)
    def draw(self): return self.cards.pop()

def settle(player, dealer):
    if player.is_bust(): return "lose"
    if player.is_blackjack() and not dealer.is_blackjack(): return "blackjack"   # usually pays 3:2
    if dealer.is_bust(): return "win"
    p, d = player.best_value(), dealer.best_value()
    return "win" if p > d else "lose" if p < d else "push"

def dealer_play(shoe, dealer_hand, hit_soft_17=False):
    while dealer_hand.best_value() < 17 or (hit_soft_17 and dealer_hand.best_value() == 17 and dealer_hand.is_soft()):
        dealer_hand.add(shoe.draw())

Examples: A + K = 21 (blackjack). A + A + 9 = 1 + 1 + 9 = 11, then +10 = 21. A + 7 + 9 = 17 (the Ace must be 1, since 27 would bust).


3) Game Flow

  1. Players place bets. Deal 2 cards to each player and the dealer (one dealer card face down).
  2. If the dealer shows an Ace, optionally offer insurance. Check for dealer blackjack.
  3. Each player acts: hit or stand (later: double down, split) until they stand or bust.
  4. The dealer reveals and draws by rule (hit below 17; house rule for "soft 17").
  5. Settle each player (win, lose, push, blackjack 3:2) and update chips.
  6. Reshuffle when the shoe falls below a cut card (e.g., 25% remaining).


4) Design Notes

  • Rules as configuration (number of decks, dealer hits soft 17, blackjack payout) keep the engine reusable.
  • Player decisions go through a strategy interface: a human (UI input) or a bot (basic strategy table). That's great for testing.
  • Seeded shuffle makes games reproducible in tests.
  • Extensions: split creates two hands for one player (so a Player has a list of Hands), and double doubles the bet and draws exactly one card.


5) Wrap-Up

Model Card, Shoe, Hand, Player, Dealer and Game. The key algorithm is best_value: count Aces as 1, then add 10 once if it doesn't bust, which also defines soft hands. Detect blackjack (2 cards totaling 21) and busts, let the dealer draw to 17 by configurable rules, and settle outcomes, keeping rules and player strategies pluggable for extensions like split and double down.

More Case Studies

Practice with a Mock Interview

Apply what you learned in a live system design mock interview with our AI interviewer.

Start System Design Interview →