Riadh Mnasri
← Back to blog
4 min read

Swiss pairings: the algorithm behind a tournament's fairness

Running a tournament with 40 players poses a pairing problem before it even poses a chess problem. A round-robin (everyone plays everyone) needs 39 rounds. A single-elimination bracket knocks out half the field after a single game, often lost to bad luck more than skill level. The Swiss system, implemented in kotlin-chess-tournament and used by EloChessPlanner, solves that exact problem: keep everyone playing, in a reasonable number of rounds, while pairing players of comparable strength.

The principle: pairing by score group

After round one, players are split into groups by their cumulative score (players with 1 point, those with 0.5, those with 0). Pairing happens within each group, never between players with very different scores: that guarantees that mid-tournament, every game pits players with a comparable track record so far.

 Score 3   ─┬─ Player A ── Player D
            └─ Player B ── Player C

 Score 2.5 ─┬─ Player E ── Player H
            └─ Player F ── Player G

The Dutch method, in detail

Within each score group, the Dutch method (the most widespread, implemented by bbpPairings, which kotlin-chess-tournament references as a C++/Pascal alternative) follows a precise sequence:

  1. Sort the group by score, then by Elo rating (initial tiebreak).
  2. Split into two halves: the upper half (the group's top-rated players) and the lower half.
  3. Pair position by position: the first player in the upper half faces the first player in the lower half, the second faces the second, and so on.
  4. Avoid repeats: if a pairing would recreate a game already played in the tournament, swap with the next candidate in the lower half instead of forcing the repeat.
 Upper half     Lower half
   Player A  ──── Player C
   Player B  ──── Player D

If Player A has already faced Player C in an earlier round, the algorithm tries Player A against Player D instead, and readjusts the rest of the group's pairings accordingly.

What happens when a group has an odd number of players

A score group with an odd number of players can't be fully paired internally: one player must float down into the next lower score group to find an opponent. The FIDE rule specifies who floats down first (usually the lowest-rated player in the group), so it's never arbitrary and never always the same player type paying the price round after round.

The bye: a simple rule, a common trap

With an odd total number of players, someone doesn't play that round and receives a bye (often counted as a win, sometimes a half-point win depending on the regulations). The rule that seems obvious but that many homegrown implementations get wrong: the bye must rotate, the same player can't receive two byes while another eligible player hasn't received one yet. Without that explicit tracking, a player can end up receiving a bye twice while another never gets one, silently skewing the tournament's fairness without any pairing rule technically being violated.

Color balancing, the constraint that gets forgotten

A good Swiss pairing isn't limited to score and avoiding repeats: it also has to balance colors, avoiding a player getting white (or black) three times in a row. This constraint stacks on top of the previous ones, which means a naive pairing algorithm that only optimizes for score can produce a pairing that's valid on paper but unfair in how it plays out, one player consistently stuck with black while their opponent of the day strings together whites.

Tiebreaks: three metrics, not one

At equal score by the end of the tournament, the final ranking can't stop at the raw score. Three tiebreaks, applied in order, settle most ties:

TiebreakCalculationWhat it rewards
BuchholzSum of the final scores of every opponent facedHaving faced a tough field
Sonneborn-BergerSum of the scores of opponents beaten, plus half the scores of opponents drawn againstBeating strong opponents, not just accumulating points against weak ones
Average opponent EloSimple average of the ratings facedA more direct measure, less sensitive to opponents' one-off performances

Buchholz and Sonneborn-Berger answer different questions: the first rewards the difficulty of the schedule regardless of the outcome, the second specifically rewards wins against opponents who performed well. A player with a very tough schedule but few wins can have a good Buchholz and a weak Sonneborn-Berger; that's why a robust final ranking applies both in a defined order, never just one in isolation.

Why the ranking order must be deterministic

Once score and tiebreaks are applied, there's always a non-zero chance of a perfect tie across every criterion. The final ranking must still produce a stable, reproducible order, not one dependent on database insertion order or an unstable sort. It's a detail that seems minor, but becomes a real problem the day two runs of the same calculation, on the same data, produce a different ranking: in an officially rated tournament, that kind of inconsistency isn't acceptable, even to settle a position that has no impact on the podium anyway.