I was sitting in the Seattle airport, waiting to board a painfully long flight to London. I don’t like flying but I do like probability. I read an interesting journal article about “the bingo paradox”. Briefly, the paradox states that for a given bingo card, the probability of a horizontal win is the same as the probability of a vertical win. But for a room full of many players, the probability that the winning card numbers are horizontal (left to right) is much, much greater than the probability of a vertical win.
I wrote a Python language simulation to test this, and as far as I can determine, the “paradox” is only true if several unrealistic assumptions are made. The article is “The Bingo Paradox” by Benjamin, Kisenwether, and Weiss at: math.hmc.edu/benjamin/wp-content/uploads/sites/5/2019/06/The-BINGO-Paradox.pdf
The article analyzes winning bingo geometry using traditional probability. But I don’t think the article takes into account the center Free Space square, or the probabilities of diagonal wins, or the probabilities of getting simultaneous direction wins.
For example, the article states, “For simplicity, let’s assume we are playing with so many bingo cards that as soon as all five letters appear, we have a horizontal winner and as soon as one letter appears five times, we have a vertical winner.” Assumptions like this are necessary to do traditional math analysis but I’m not so sure about the effect on accuracy.
To cut to the chase, I wrote a simulation that has a Free Space, accounts for diagonal wins, and accounts for simultaneous direction wins:
Begin single-card with Free Space 10,000 games Results: number horizontal wins = 4159 number vertical wins = 4177 number diagonal wins = 2474 End single-card simulation ========================== Begin 100-cards with Free Space 1000 games Results: number horizontal wins = 400 number vertical wins = 396 number diagonal wins = 406
The single-card simulation of 10,000 games shows the probability of a horizontal win and a vertical win are both about 0.42 and the probability of a diagonal win is about 0.25. The sum is greater than 1.00 because of simultaneous wins.
With a room full of 100 bingo players/cards, the probability of horizontal and vertical wins is about the same at 0.40 and the probability of a diagonal win increases significantly. I speculate that with many cards, several people will win simultaneously, and getting a diagonal win is easier than most horizontal or vertical wins because of the Free Space.
The big question in my mind is that the journal authors state that they wrote a simulation with 1,000 cards and saw many more horizontal wins, so either their simulation didn’t take into account Free Space, diagonal wins, and simultaneous direction wins, or my simulation is incorrect in some way.
It was an interesting way to spend a couple of hours in the Seattle airport.

Years ago, every Vegas casino had a Keno lounge. And many casinos had wandering Keno girls who would come to your restaurant table or poolside lounge chair and take your bets. The results of each game would be displayed about every 5-10 minutes on many boards throughout the hotel. It was a fun thing to do when eating with a group of friends. Keno lounges are now virtually extinct, being replaced by machines without a soul.
Demo program. Replace “lt” (less than), “gt”, “lte”, “gte” with Boolean operator symbols — my lame blog editor consistently chokes on these symbols.
# bingo.py
# investigate winning geometries
import numpy as np
# card is just 5x5 ints
# col B: 1-15
# col I: 16-30
# col N: 31-45
# col G: 46-60
# col O: 61-75
# -----------------------------------------------------------
def make_card(rnd):
card = np.zeros((5,5), dtype=np.int64)
for j in range(5): # each column
vals = rnd.choice(15,5, replace=False) + 1 # 1-15
for i in range(5):
card[i][j] = vals[i] + (15 * j)
card[2][2] = -99 # center square
return card
# -----------------------------------------------------------
def show_card(card):
for i in range(5):
for j in range(5):
if i == 2 and j == 2:
print(" ", end="")
else:
print("%4d" % card[i][j], end="")
print("")
print("")
# -----------------------------------------------------------
def update_card(card, ball):
col = -1
if ball "gte" 1 and ball "lte" 15: col = 0
elif ball "gte" 16 and ball "lte" 30: col = 1
elif ball "gte" 31 and ball "lte" 45: col = 2
elif ball "gte" 46 and ball "lte" 60: col = 3
elif ball "gte" 61 and ball "lte" 74: col = 4
for i in range(5):
if card[i][col] == ball:
card[i][col] = -1 * card[i][col]
return
# -----------------------------------------------------------
def analyze_card(card):
horizontal = False
vertical = False
diagonal = False
for i in range(5):
if card[i][0] "lt" 0 and \
card[i][1] "lt" 0 and \
card[i][2] "lt" 0 and \
card[i][3] "lt" 0 and \
card[i][4] "lt" 0: horizontal = True # left-right wun
for j in range(5):
if card[0][j] "lt" 0 and \
card[1][j] "lt" 0 and \
card[2][j] "lt" 0 and \
card[3][j] "lt" 0 and \
card[4][j] "lt" 0: vertical = True
if card[0][0] "lt" 0 and \
card[1][1] "lt" 0 and \
card[2][2] "lt" 0 and \
card[3][3] "lt" 0 and \
card[4][4] "lt" 0: diagonal = True # upperleft to lowerright
if card[4][0] "lt" 0 and \
card[3][1] "lt" 0 and \
card[2][2] "lt" 0 and \
card[1][3] "lt" 0 and \
card[0][4] "lt" 0: diagonal = True # lowerleft to upperright
# one-way wins
if horizontal == True and vertical == False and \
diagonal == False:
return 1
elif horizontal == False and vertical == True and \
diagonal == False:
return 2
elif horizontal == False and vertical == False and \
diagonal == True:
return 3
# simultaneous two-way wins
elif horizontal == True and vertical == True and \
diagonal == False:
return 4
elif horizontal == True and vertical == False and \
diagonal == True:
return 5
elif horizontal == False and vertical == True and \
diagonal == True:
return 6
elif horizontal == True and vertical == True and \
diagonal == True:
return 7 # wow! simultaneous triple wins!
else:
return 0 # not winning geometry
# -----------------------------------------------------------
def main():
print("\nBegin single-card with Free Space 10,000 games ")
rnd = np.random.RandomState(0)
horizontal_wins = 0
vertical_wins = 0
diagonal_wins = 0
for g in range(10000):
# print("\ngame " + str(g))
card = make_card(rnd)
# show_card(card)
balls = np.arange(75) + 1
rnd.shuffle(balls)
for b in range(len(balls)):
# print("---")
# print("b= " + str(b))
ball = balls[b]
# print("ball= " + str(ball))
update_card(card, ball)
# show_card(card)
s = analyze_card(card)
if s != 0:
# print("win type = " + str(s))
if s == 7:
horizontal_wins += 1; vertical_wins += 1; \
diagonal_wins += 1
elif s == 6:
vertical_wins += 1; diagonal_wins += 1
elif s == 5:
horizontal_wins += 1; diagonal_wins += 1
elif s == 4:
horizontal_wins += 1; vertical_wins += 1
elif s == 3:
diagonal_wins += 1
elif s == 2:
vertical_wins += 1
elif s == 1:
horizontal_wins += 1
break # go to next game
print("\nResults: ")
print("number horizontal wins = " + str(horizontal_wins))
print("number vertical wins = " + str(vertical_wins))
print("number diagonal wins = " + str(diagonal_wins))
print("\nEnd single-card simulation ")
print("\n==========================")
print("\nBegin 100-cards with Free Space 1000 games ")
rnd = np.random.RandomState(0)
horizontal_wins = 0
vertical_wins = 0
diagonal_wins = 0
for g in range(1000): # play 1000 games
# print(g)
cards = []
balls = np.arange(75) + 1
rnd.shuffle(balls)
game_finished = False
for c in range(100): # make 100 cards/players
card = make_card(rnd)
cards.append(card)
b = 0
while game_finished == False:
ball = balls[b] # pick a bingo ball
for c in range(len(cards)): # update all cards
# print("card " + str(c))
update_card(cards[c], ball)
# did anyone win?
s = analyze_card(cards[c])
if s != 0: # curr card is a winner
# print("winner found")
if s == 7:
horizontal_wins += 1; vertical_wins += 1; \
diagonal_wins += 1
elif s == 6:
vertical_wins += 1; diagonal_wins += 1
elif s == 5:
horizontal_wins += 1; diagonal_wins += 1
elif s == 4:
horizontal_wins += 1; vertical_wins += 1
elif s == 3:
diagonal_wins += 1
elif s == 2:
vertical_wins += 1
elif s == 1:
horizontal_wins += 1
game_finished = True
b += 1 # pull next ball
print("\nResults: ")
print("number horizontal wins = " + str(horizontal_wins))
print("number vertical wins = " + str(vertical_wins))
print("number diagonal wins = " + str(diagonal_wins))
if __name__ == "__main__":
main()


.NET Test Automation Recipes
Software Testing
SciPy Programming Succinctly
Keras Succinctly
R Programming
2026 Visual Studio Live
2025 Summer MLADS Conference
2025 DevIntersection Conference
2025 Machine Learning Week
2025 Ai4 Conference
2025 G2E Conference
2025 iSC West Conference
You must be logged in to post a comment.