Course Content
Object-Oriented Design Interview
14 sections · 29 lessons
Blackjack: rules, requirements and the hand-centred model
Tic tac toe has one rule; blackjack has twenty, and they vary from casino to casino. That makes this a problem about two things — where rules live, and where scoring lives. This lesson covers the rules of the game, the questions and requirements, and the model, whose central decision is proved right by one word: split.
The rules, in six lines
Not everyone has played, so here is the whole game. It is invented-casino generic; specific rules vary, which is itself part of the problem.
- Players bet, then everyone including the dealer gets two cards.
- Number cards score their face value, picture cards score 10, and an ace scores 1 or 11, whichever is better for the hand.
- A player takes actions — hit (take a card), stand (stop), double (double the bet, take exactly one more card), split (turn a pair into two hands).
- Going over 21 is a bust and loses immediately.
- The dealer then plays by a fixed rule, typically "draw until 17 or more, then stop".
- Hands closer to 21 than the dealer win; equal is a push (bet returned).
Why this problem is different from tic tac toe
Tic tac toe has one rule. Blackjack has twenty, and they vary by casino: whether the dealer draws on a soft 17, how many times you may split, whether you may double after splitting, whether blackjack pays 3:2 or 6:5. Rule encapsulation is the point. A design where those rules are constants scattered through the game loop cannot represent a second casino, and the interviewer will ask about exactly that.
The four questions that change the model
1. "How many decks, and how many players?" Casinos deal from a shoe of several decks, reshuffled at a marker rather than after every hand. That detail matters for the card-counting extension and costs one class. Assume six decks and up to five players.
2. "Which actions are allowed — hit, stand, double, split, insurance, surrender?" Splitting is the one that tests your model, because it turns one player's single hand into several independent hands. Ask specifically. Assume hit, stand, double, and split; treat insurance and surrender as extensions.
3. "Are betting and chips in scope?" Settlement — who gets paid what — is where blackjack has real logic (blackjack pays 3:2, a push returns the bet, a double stakes twice). Assume bets are in scope, and chip inventory and table limits are not.
4. "What exactly are the dealer's rules?" Ask the soft-17 question explicitly: "Does the dealer draw on a soft 17 — that is, an ace-six? It changes the house edge and I want it to be configuration rather than a constant." This question is a strong signal, because it shows you know rules vary and you have decided where variation lives.
Assumptions this lesson makes
- Six decks in a shoe, reshuffled at a penetration marker.
- Up to five players plus a dealer.
- Actions: hit, stand, double down, split. Insurance and surrender are extensions.
- Bets in scope; blackjack pays 3:2; dealer stands on all 17s — all configurable.
- Single table, single thread. Concurrency is not this problem's question.
Requirements
Functional requirements
1. Place a bet for each player before the deal2. Deal two cards to each player and to the dealer (one dealer card face up)3. Score a hand, handling aces as 1 or 114. Take player actions in turn: hit, stand, double, split5. Play the dealer's hand by a fixed rule6. Settle every hand against the dealer and pay or collectRequirement 3 gets its own treatment (the ace problem, in code) because it is a small piece of code that separates careful candidates from hasty ones.
Non-functional requirements
N1. Rule variations must be configuration, not code. Casinos differ. A design that can be pointed at a rules object and behave like a different casino is the target.
N2. A human player and a bot must be interchangeable. Same reason as tic tac toe: the game loop should not know which it is talking to.
N3. The shoe must not be reshuffled per hand. Cards dealt stay dealt until the reshuffle marker, which is what makes card counting possible and the simulation realistic.
What is cut
CUT (named, not forgotten)- Chip denominations and physical chip trays- Table limits, side bets beyond insurance- Multi-table casino management, player accounts- Any user interface or animation- Shuffle randomness quality (a seeded shuffle is enough for the design)The rules object, stated early
Requirement N1 deserves a concrete shape in the requirements phase, because everything else refers to it:
1public class GameRules {2 private final int decksInShoe; // 63 private final boolean dealerHitsSoft17; // false4 private final Ratio blackjackPayout; // 3:25 private final int maxSplits; // 36 private final boolean doubleAfterSplitAllowed;7 private final boolean surrenderAllowed;8}Six fields, and every one of them is a rule that a real casino varies. Putting this class on the board in minute eight and saying "every rule that differs between casinos lives here, and nothing else reads a constant" is a compact way to satisfy N1 before writing any other class.
Finding the objects
The candidate nouns
Game, round, table, deck, shoe, card, rank, suit, hand, player, dealer, bet, chip, action, score, outcome.
The decision that carries the problem: Hand owns scoring
The instinct is to put score() on Player — a player has cards, a player has a total.
It is wrong, and splitting is what proves it. When a player splits a pair of eights, they now have two hands, each with its own cards, its own bet, its own actions, and its own outcome. One can bust while the other wins. A score() on Player has nothing coherent to return.
1public class Hand {2 private final List<Card> cards = new ArrayList<>();3 private Bet bet;4 private HandStatus status; // ACTIVE, STOOD, BUST, BLACKJACK, SURRENDERED56 public int bestScore() { … } // see the ace problem lesson7 public boolean isSoft() { … } // has an ace counted as 118 public boolean isBust() { return bestScore() > 21; }9 public boolean isBlackjack() { return cards.size() == 2 && bestScore() == 21; }10 public boolean canSplit() {11 return cards.size() == 2 && cards.get(0).getRank() == cards.get(1).getRank();12 }13}1415public class Player {16 private final String name;17 private Money chips;18 private final List<Hand> hands = new ArrayList<>(); // usually one; more after a split19 private final MoveStrategy strategy;20}Say it out loud: "Scoring goes on Hand, not Player, because a split gives one player two hands that win or lose independently. The bet goes on the hand too, for the same reason — each split hand carries its own stake."
That sentence answers a question the interviewer was going to ask in the extensions, twenty minutes early. This is the same lesson as the tic tac toe objects (win detection belongs to the board): put behaviour with the data it needs, and the data here is the cards of one hand.
Deck and shoe
Two classes, and the distinction is real:
1public class Deck { // 52 cards, a factory really2 public static List<Card> standardDeck() { … }3}45public class Shoe {6 private final Deque<Card> cards;7 private final int reshuffleThreshold; // reshuffle when fewer remain8 public Card draw() {9 if (cards.size() < reshuffleThreshold) refillAndShuffle();10 return cards.pop();11 }12}Shoe is where the interesting behaviour is — drawing, depletion, and the reshuffle rule. Deck is close to a static factory. Say that: "Deck is nearly a factory method; the real object is Shoe, which is what gets drawn from and reshuffled."
Dealer: a player, or not?
A genuine modelling question with two defensible answers, and the interviewer may push on it.
Dealer extends Player. Tempting — the dealer has a hand and takes actions. But the dealer has no chips, never bets, never splits, never doubles, and does not choose anything. Most of Player is inapplicable, and inherited methods that make no sense are the Liskov violation from the SOLID reference.
Dealer as its own class. It holds one hand and a rule for when to draw:
1public class Dealer {2 private Hand hand;3 private final GameRules rules;45 public boolean shouldDraw() {6 int score = hand.bestScore();7 if (score < 17) return true;8 return score == 17 && hand.isSoft() && rules.dealerHitsSoft17();9 }10}Recommend the second, and give the reason: "I'll keep Dealer separate rather than subclassing Player, because the dealer has no chips and makes no decisions — inheriting a placeBet method the dealer must refuse is a Liskov problem. What they share is a Hand, and they both hold one."
The rest of the model
| Class | Its one-sentence job |
|---|---|
Game / Round | Runs one round from bets to settlement |
Shoe | Supplies cards and reshuffles at the marker |
Card | A rank and a suit; immutable |
Hand | Holds cards, scores them, and knows its own status |
Player | A person with chips, a strategy, and one or more hands |
Dealer | Holds a hand and knows when to draw |
Bet | An amount staked on one hand, and its payout |
GameRules | Every rule that differs between casinos |
MoveStrategy | Chooses an action given a hand and the dealer's up-card |