An exact upper bound on the number of chess positions, and a general formula for counting the piece combinations it is built on, with an interactive visualization of every part of the formula.
Live app: https://aaronliftig.github.io/ChessUpperBound/
Upper bound: 2.394 × 1049 positions, exactly
23937533792747905898433845980097921846050276105440
A position here is an arrangement of the men on the board.
For comparison, John Tromp estimates the number of legal positions at about 4.8 × 1044. That figure is a statistical estimate, made by sampling random positions and checking which are legal, not a proven count. The bound here is proven but much larger, because it keeps many arrangements that could never arise in a game, such as both kings in check.
This started as an undergraduate project at Florida Gulf Coast University in 2011, "Creating an Upper Bound for the Total Number of Possible Chess Positions", done by hand rather than by computer search. The bound breaks a position into a sequence of choices: how many pawns each side has and where they stand, which pieces are left, where those pieces stand, and in what order.
One part of that bound, the count of which pieces can still be on the board, turned out to generalize neatly. What if chess had five bishops a side, or a dozen piece types? The general version is the first section below. The chess bound is the second.
A kind of piece is a type and colour together, such as "white rook". Each kind has some number of identical copies. How many different collections of pieces can be left on the board, when each kind keeps anywhere from none to all of its copies?
Ignoring kings, pawns and promotions, standard chess has six kinds with two copies (white and black rooks, bishops and knights) and two kinds with one copy (the queens).
The original chess bound chose the pieces with this factor:
Here
That suggests a way to generalize, by writing everything in terms of the total number of pieces
The generalization below drops
-
$a_j$ is the number of kinds with exactly$j$ copies. Standard chess has$a_2 = 6$ and$a_1 = 2$ . -
$A_j = a_j + a_{j+1} + \cdots + a_M$ is the number of kinds with at least$j$ copies, where$M$ is the largest number of copies. In chess,$A_2 = 6$ and$A_1 = 8$ . -
$i_j$ is the number of kinds that keep exactly$j$ copies in a given combination.
The cumulative counts
Work down from the largest size. At level
For chess this is
The empty collection, with nothing left, counts as one combination.
Take both white rooks and the white queen off the board. White keeps its two bishops and two knights; Black keeps everything.
| Level | Kinds available | Chosen | Factor |
|---|---|---|---|
|
|
|||
|
|
So 18 combinations share this pattern (five kinds kept as full pairs and one kept as a single piece, here the black queen), and this is one of them. The app's "Choosing which pieces are left" section lets you build any combination and shows its term.
The nested sum collapses into a single product:
The first form says each kind with
To see why the sum equals the product, collapse the innermost sum with the binomial theorem,
The chess bound needs more than the total: the number of squares to choose depends on how many pieces there are. The generating polynomial keeps that information:
where the coefficient of
pieces: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
count: 1 8 34 98 211 356 483 534 483 356 211 98 34 8 1
The corrected bound sums over every material each side could have, and that set is built from the same counts. Leave promotions out for a moment and look at one side: its queen, rooks, bishops and knights are three kinds with two copies and one kind with one copy, so the general expression gives
Promotions enlarge each side's choices from 486 to 8,694. Each extra queen, rook, bishop or knight uses up one of that side's missing pawns, so the material sum is the piece-combination count extended by a budget rule: pawns plus promoted pieces is at most 8.
The same reasoning works for armies other than the standard one, which is the question the generalization started from. See Other games below.
Every arrangement of men on the board such that
- each side has exactly one king;
- no pawn stands on the first or last rank;
- each side's material can be reached from the starting army by captures and promotions. A side with
$p$ pawns can have at most$8 - p$ promoted pieces, where a promoted piece is any copy beyond the starting count of queens (1), rooks (2), bishops (2) or knights (2).
Every legal position satisfies all three, so the count is an upper bound on the number of legal positions. There are 8,694 possible materials per side, so the sum covers 75,585,636 pairs of materials.
For White's material
flowchart LR
M["Materials<br/>which pieces each side has"] --> P["Pawn squares<br/>choose from the 48 legal squares"]
P --> C["Pawn colours<br/>which pawns are White's"]
C --> S["Piece squares<br/>choose from what is left"]
S --> A["Arrangement<br/>put the men on those squares"]
Read left to right:
- Pawn squares. Pawns can only stand on ranks 2 to 7, which is 48 squares. Choose one square for each pawn.
- Pawn colours. Of those squares, choose which ones hold White's pawns.
- Piece squares. From the squares left anywhere on the board, choose one for each king and piece.
- Arrangement. Put the men on those squares in every order, then divide by the factorial of each group of identical pieces, because two white rooks trading places gives the same position.
For example, with both full starting armies the term, the number of positions with exactly that material, is
The bound is far above Tromp's estimate of legal positions because it does not rule out:
- positions where the side not to move is in check, or both kings are in check;
- kings on adjacent squares;
- pawn structures that captures could never produce (for example, a side with eight promoted pieces while the opponent still has all its pawns);
- bishops on squares of the wrong colour for the pawns that would have had to promote into them.
Each of these is a possible refinement.
The same counting works for any board and army, which is the question the generalization started from: what if chess had more piece types, more copies of each, or a different board? The rules generalize directly. Pawns may stand on any rank except the first and last, so a board with
| Game | Board | Army per side, besides the king | Materials per side | Positions at most |
|---|---|---|---|---|
| Standard chess | 8 × 8 | 8 pawns, Q, 2 R, 2 B, 2 N | 8,694 | 2.39 × 1049 |
| Five bishops a side | 8 × 8 | 8 pawns, Q, 2 R, 5 B, 2 N | 13,842 | 1.60 × 1054 |
| Capablanca chess | 10 files × 8 ranks | 10 pawns, Q, 2 R, 2 B, 2 N, archbishop, chancellor | 275,022 | 3.66 × 1067 |
| Los Alamos chess | 6 × 6 | 6 pawns, Q, 2 R, 2 N | 882 | 1.01 × 1030 |
| Gardner minichess | 5 × 5 | 5 pawns, Q, R, B, N | 1,182 | 7.66 × 1023 |
These follow the counting rules above, not every detail of each variant. For example, Los Alamos chess forbids promoting to a bishop, which happens automatically here since the army has none.
In the app, the "Design a different game" section has these as presets and lets you set the board size, the number of pawns, and any list of piece types with how many each side has and whether pawns can promote to them. Everything on the page recalculates for that game. In code, pass the same settings to placementsUpperBound:
placementsUpperBound({
rows: 8, cols: 10, pawns: 10,
army: { Q: 1, R: 2, B: 2, N: 2, A: 1, C: 1 },
promotions: ["Q", "R", "B", "N", "A", "C"],
});The app runs entirely in the browser with no build step, and all arithmetic is exact (JavaScript BigInt).
- The formula. Hover over any part, tap it, or tab to it with the keyboard, and a short description appears with its value for the current material. The board highlights what that part counts in the same colour. Click a part to keep it selected.
- One material at a time. Set each side's pawns and pieces and see the value of each factor and their product. The controls only allow materials that captures and promotions can reach, and an unavailable button explains why when you press it. The board shows a random position that the term counts.
- Design a different game. Switch games from the menu at the top, or set the board size, the number of pawns, and the list of piece types yourself. Everything on the page recalculates for that game.
- Piece combinations. Each kind of piece in the current game is a column; click cells to choose how many remain, and see which term of the nested sum your choice belongs to.
index.html the app
styles.css
src/counting.js all of the mathematics, exact BigInt arithmetic
src/app.js the interface and visualizations
tests/counting.test.js tests, including brute-force checks
package.json lets `npm test` run the tests
.github/workflows/ runs the tests on every push
.nojekyll tells GitHub Pages to serve the files as they are
The original Python programs are in the git history.
The app uses JavaScript modules, which browsers will not load from a file:// address, so serve the folder locally:
python3 -m http.server 8000
# then open http://localhost:8000- Push these files to the
masterbranch. - In the repository, go to Settings → Pages.
- Under Build and deployment, choose Deploy from a branch, then select
masterand/ (root). - The app appears at
https://aaronliftig.github.io/ChessUpperBound/after a minute or two.
Requires Node.js 18 or later; no packages need to be installed.
npm testThe tests check that
- the formula agrees exactly with brute-force enumeration on small boards, where every arrangement of pieces on every square is generated and filtered by the three rules directly;
- the grouped sum equals the sum over every individual pair of materials, for standard chess and for variants with other boards and unpromotable pieces;
- the fast material count agrees with listing every material one by one;
- the nested sum, the closed form and the generating polynomial agree on hundreds of random armies, and match direct enumeration.
- J. Tromp, ChessPositionRanking, https://github.com/tromp/ChessPositionRanking, which estimates (4.79 ± 0.04) × 1044 legal positions at 95% confidence by random sampling.