The Data Scientist

Simulating 10 Million Blackjack Hands in Python

Published 21 September 2026

Blackjack is simple enough to simulate in about 150 lines of Python and complicated enough that the answer is not obvious. We wrote a small simulator, ran ten million hands with basic strategy and ten million with a Hi-Lo count, and published the code so you can check our numbers.

The rules we simulated

These are common, but slightly less generous than the best six-deck games: most real tables allow re-splitting pairs, and some allow surrender. Both would move the numbers below a little in the player's favour.

How the simulator works

The whole thing is three parts:

  1. A shoe. A shuffled list of 312 card values, with aces stored as 11. Drawing a card also updates the Hi-Lo running count.
  2. A strategy. Basic strategy written as three small functions: one for hard totals, one for soft totals, and one that decides whether to split a pair.
  3. A round. Deal, check for blackjacks, play the player's hand or hands, play the dealer, and settle every bet.

Hand totals use the usual trick: add everything up with aces as 11, then subtract 10 for each ace while the total is over 21.

def hand_value(cards):
    total = sum(cards)
    aces = cards.count(11)
    while total > 21 and aces:
        total -= 10
        aces -= 1
    return total, aces > 0   # (total, is_soft)

For the counting run, the bet is chosen from the true count (running count divided by decks remaining, rounded down) just before each round:

true_count = floor(running_count / decks_remaining)
bet = 1 if true_count <= 1 else [2, 4, 6, 8, 10, 12][min(true_count, 7) - 2]

That is 1 unit up to a true count of +1, 2 units at +2, and 2 more for each point after that, up to 12 units at +7 and above. The counting run uses no playing deviations, so the gain comes only from bet sizing.

The full script is here: blackjack_sim.txt (save it as a .py file to run it). It uses nothing but Python's standard library and runs about 200,000 hands a second on an ordinary machine.

Results

Each strategy was run as four independent batches of 2.5 million hands with different random seeds.

Basic strategy, flat betHi-Lo, 1–12 spread
Hands10,000,00010,000,000
Average bet (units)1.001.50
Net result (units)−52,299+50,027
Result per unit bet−0.52%+0.33%
Standard error±0.04%±0.05%
Standard deviation per hand (units)1.152.51

Reading the numbers

The basic-strategy figure is about right. A house edge of around half a percent is what published calculators give for a six-deck, stand-on-soft-17 game with no re-splitting and no surrender. If a simulator disagrees wildly with published figures, the bug is almost always in the strategy tables or in how split hands are settled.

The counting edge is real but small. A modest 1-to-12 spread turns a −0.52% game into a +0.33% game, measured against the total amount bet. That works out to half a unit won per hundred hands.

Variance more than doubles. Bigger bets at high counts mean bigger swings. A standard deviation of 2.5 units a hand against an expected win of 0.005 units a hand is why counters need large bankrolls. See variance and risk of ruin.

Ten million hands is a lot, and still not infinite. The standard errors in the table are what you get after ten million hands. A real player sees perhaps 100 hands an hour. Ten million hands is about 50 years of full-time play.

Things worth trying with the code

Sources and further reading