| Algorithm Deep Dives |
|---|
| 1. Z Algorithm |
| 2. Manacher’s Algorithm |
| 3. Finite-State Machine |
| 4. Gosper’s Hack Algorithm |
| 5. Glicko Rating System |
| 6. Lamport Timestamp |
This post introduces the Glicko rating system.
Open a fresh account on lichess, play one game, and the rating shows up as 1500?.
It is easy to read that question mark as “provisional, ignore this for now” and move on. It is far more specific than that.
In the lichess source, the question mark is a single comparison. A rating is treated as provisional when the player’s deviation is 110 or above.
So it is not a friendly UI hint. It is an internal number leaking through the interface, and the Glicko rating system exists to manage that number.
This post starts from the question Elo cannot answer, moves on to how Glicko handles it, and ends with what changes between the paper and a real service.
What Elo actually gives you
Arpad Elo was a physics professor and a chess master. The system he designed for the USCF in 1960 does one job very well, and FIDE adopted it in 1970.
That job is turning a sequence of wins and losses into a single comparable number. You compute an expected result from the rating gap, compare it with what happened, and move both players by the difference.
There is only one knob: K, the maximum a single game can move a rating.
FIDE currently uses K = 40 for new players, 20 after 30 games, and 10 past 2400. Why build a staircase like that?
Because K is doing two unrelated jobs at once. It sets how fast the system learns, and it sets how much a single result matters.
Those are not the same thing. Elo has one dial for both, so tuning one drags the other along.
The staircase exists to fake something the model does not have. A new player needs a large K because the system knows nothing about them, and a 2400 player needs a small K because the system already knows a lot.
Both statements are about confidence, and confidence is a value Elo stores nowhere.
Everything awkward about Elo comes out of that blank.
- Two players rated 1500, one with 5 games and one with 5,000, are treated as identical opponents.
- Someone who has not played for three years keeps a rating the system has no grounds to stand behind.
- Provisional periods, rating floors, and games-played thresholds are all patches applied afterwards to cover holes the model cannot express.
Elo also assumed performance was normally distributed, and the data turned out to fit a logistic distribution better. Most implementations use the latter today, so the system everyone calls “Elo” is already a modified Elo.
A rating that carries its own error bars
Mark Glickman worked his answer out in his 1993 Harvard dissertation and published it through the nineties as Glicko. It is a simple answer: store the confidence alongside the rating.
Every player gets a second number, the rating deviation, or RD.
RD is a standard deviation on the rating estimate. Pair it with the rating and you get an interval instead of a point.
A 1500 player with an RD of 50 is a claim that their strength is very likely between 1400 and 1600. The same 1500 with an RD of 350, the default for a brand new account, is barely a claim at all.
The same rating, three different claims. The interval is roughly the rating plus or minus two RDs.
RD moves in two directions. It shrinks when you play, because results are evidence.
It grows while you sit idle, because the system’s belief drifts toward “who knows, it has been a year.”
So is RD just an annotation on the rating? No. RD feeds into the update itself, on both sides of the board.
Your own RD sets how far a single result can move you. A large RD means the system does not trust your number, so it moves freely.
A small RD means the rating was built over hundreds of games, and one loss should not shake it much. That is the same conclusion as the FIDE staircase, except derived rather than declared.
Your opponent’s RD sets how much information the result carries. Beating someone whose rating is a shrug tells the system almost nothing, so your rating barely moves.
Beating an established 1700 is real information, and it moves you accordingly. Elo had no way to say “this result is less informative than that one.”
Attaching confidence to a rating and weighting evidence by that confidence are the same mechanism used twice. That is why the system stays small; the original description of Glicko is three steps long.
Rating periods, the assumption everyone breaks
This is where implementations start to drift.
Glicko does not process games one at a time. It processes a rating period.
A rating period is a batch of games the math treats as simultaneous. Everyone enters with a rating, RD, and volatility, the results are observed, and new values come out at the close.
Glickman is explicit about the size of that batch in the Glicko-2 paper. The system works best when a period holds a moderate to large number of games, at least 10 to 15 per player on average, and the wall-clock length of a period is left to the administrator.
An online ladder wants the opposite. Nobody finishes a match and waits until Sunday to see what it did to their rating.
So nearly every implementation runs one rating period per game, which is exactly the regime the paper warns about.
Why is that not a small deviation? Because it changes what the system is estimating.
Batched, “how did this player do against this field” is one question answered once. Per game, it becomes a chain of updates, each re-estimating a player’s strength from a single observation.
Lichess takes a middle path. It updates after every game, in the order played, but keeps a notion of elapsed time by making rating periods fractional.
The constant in their source is 0.21436 rating periods per day, and the comment says exactly why that number.
// Chosen so a typical player's RD goes from 60 -> 110 in 1 year
val ratingPeriodsPerDay = 0.21436d
110 is the provisional threshold we started with. The constant is tuned so a typical active player who walks away comes back wearing a question mark after exactly one year.
The paper gives you a decay mechanism. It does not tell you when a rating should stop being believed, and that call belongs to whoever runs the ladder.
If you take one thing from this post, take this. Decide the rating period deliberately and write down why. Period length sets learning speed, inactivity decay, and how gameable the system is, all at once.
Glicko-2 and the volatility knob
Glicko-2, published by Glickman in 2001, adds a third number per player: volatility.
Where RD asks “how sure are we,” volatility asks “how erratic have this player’s results been.”
Is that distinction worth a whole parameter? Consider two players with identical ratings and identical RDs.
One produces results exactly in line with expectation, week after week. The other beats a 2000 on Tuesday and loses to an 1100 on Thursday.
The first player’s rating is a decent predictor. The second player’s rating is a number the results keep contradicting, and volatility is how the system notices.
A player with high volatility gets larger rating swings, on the theory that a rating which keeps being wrong should be allowed to move faster. New players start at 0.06.
Volatility is constrained by a system constant, τ, set once for the whole ladder rather than per player.
The paper recommends a range between 0.3 and 1.2, and adds that you should test which value gives the best predictive accuracy for your data. A smaller τ keeps volatility from swinging hard, which in turn keeps very improbable results from blowing up ratings.
That “you should test it” clause is carrying a lot of weight, and almost nobody tests it. τ usually ends up frozen at whatever value the library README used in its example.
There is a piece of implementation history worth knowing. The volatility step is the only part of Glicko-2 that needs iterative root-finding, and the original procedure occasionally failed to converge from a bad starting value. Glickman replaced it in February 2012 with a procedure based on the Illinois algorithm, a variant of regula falsi. The new version took a median of 5 iterations, with a maximum of 19 across 10,000 simulations. If you are porting an implementation written before 2012, check which version of Step 5 it inherited.
Farming volatility
Volatility has one obvious problem once you say it out loud. It is an amplifier, and the player controls it.
The attack was documented against Pokémon GO’s Battle League leaderboard in 2020 in a write-up called Farming Volatility. The procedure looks like this.
The matchmaker holds the key here, not the formula. Once you have dropped far enough, your opponents are people you can beat at will, and producing any win/loss pattern you like costs nothing but time.
At that point you are not gaming the rating. You are feeding the volatility estimator a sequence you constructed.
The Elo-MMR paper (Ebtekar and Liu, WWW 2021) put numbers on it with a simulated TopCoder ladder. The volatility-farming player finished 523 rating points ahead of an otherwise identical honest player, with nearly 1,000 of those points coming in the final 15 contests.
Chess did not escape the question either. Lichess issue #7862, opened on 5 January 2021, is titled “Glicko2 may be flawed” and describes exactly this.
Glicko-2’s volatility assumes players are trying to win. That assumption holds in the rated chess tournaments it was built for, and it collapses on a ladder with anonymous accounts, free rematches, and no entry cost. Before adopting Glicko-2, check whether losing is cheap in your system.
The mitigations all work by removing one leg of that loop.
Cap volatility so the amplifier saturates, or put a floor under rating so tanking is expensive. You can also keep matchmaking wide enough that a deranked account still faces uncertain opponents.
That last one breaks the “results are free” precondition itself. The other route is to use a system designed to resist strategic manipulation in the first place, which is what Elo-MMR argues for.
What a production implementation looks like
Open the lichess rating module and you find Glicko-2 as published, wrapped in about a dozen guard rails that appear in no paper.
val minRating = IntRating(400)
val maxRating = IntRating(4000)
val minDeviation = 45
val variantRankableDeviation = 65
val standardRankableDeviation = 75
val maxDeviation = 500d
// past this, it might not stabilize ever again
val maxVolatility = 0.1d
val defaultVolatility = 0.09d
// rating that can be lost or gained with a single game
val maxRatingDelta = 700
val tau = 0.75d
Put next to the paper’s defaults, almost nothing matches.
| Glicko-2 paper | lichess, May 2024 | |
|---|---|---|
| Starting rating | 1500 | 1500 (1450 for pairing, 800 for class students) |
| Starting / max RD | 350 | 500 |
| Minimum RD | not specified | 45 |
| Starting volatility | 0.06 | 0.09, hard-capped at 0.1 |
| System constant τ | 0.3 – 1.2, test it | 0.75 |
| Single-game swing | unbounded | 700 max |
| Rating period | batch, 10-15 games/player | per game, 0.21436 periods/day for decay |
The comment on maxVolatility says that past this value the system might never stabilize again. It is the trace of someone watching a live system walk into a state it could not recover from and nailing a board over it. You will not find that in a journal, and you will need it in production.
The display rules are the other half, and they are pure product.
| RD | Treatment |
|---|---|
| 230 and above | "clueless" — the system does not meaningfully know this player |
| 110 and above | provisional, rendered with a ? |
| 75 or below (65 for variants) | rankable — allowed onto leaderboards |
| 45 | floor, never goes lower |
The leaderboard gate is the part worth noting. A ranking that ignores RD tends to become a list of whoever got the luckiest recent sample.
It is the same problem TrueSkill avoids by showing a conservative lower bound instead of the mean. Requiring an RD of 75 or better keeps the board open only to players the system is actually sure about.
The RD floor matters for a similar reason. Without one, a veteran’s RD drifts so low that the system effectively stops updating them, having decided it knows their strength and stopped listening.
Put a floor under RD and the system always keeps a minimum willingness to be surprised.
Where Glicko stops fitting
Glicko is a paired-comparison model. Two competitors, one outcome, repeat.
If your problem is not that shape, no amount of parameter tuning will make it fit.
Teams are the common case. A 5v5 result is one bit of information about ten people, and attributing it to individuals is a modelling question Glicko does not answer.
The usual hack is to rate each player against the average enemy rating, and it performs about as well as it sounds. Fine in the aggregate, unfair in every specific case that generates a complaint.
Free-for-all is worse. A 100-player match that ends in a full ranking is not a paired comparison at all, and decomposing it into thousands of pairwise results is both wrong and expensive.
TrueSkill was built for exactly this problem. Microsoft trained it on Halo 2 beta data and deployed it on Xbox Live in 2005.
It models each player’s skill as a Gaussian, treats team performance as the sum of its members, and handles multiway rankings directly. Leaderboards show a conservative estimate rather than the mean. TrueSkill 2, in 2018, added experience, quitting behaviour, and individual stats on top.
So why is Glicko the one you find everywhere? Lichess, Chess.com, Pokémon Showdown, Online-Go, TETR.IO, Guild Wars 2, Splatoon, and a long tail of smaller ladders.
There are two reasons, and neither is mathematical.
First, it is public domain. Glickman placed both Glicko and Glicko-2 there deliberately, and TrueSkill is patented. For a small team, “can I use this without a lawyer” comes before a few percentage points of predictive accuracy.
Second, it is small enough to implement in a day. Three numbers per player, a handful of steps, one iterative loop. When ratings look wrong in production, you can trace the reason yourself.
For 1v1 with honest players, Glicko-2 is the right default. For teams or free-for-all, look at TrueSkill or OpenSkill, a permissively licensed implementation of the Weng-Lin models, first.
What to check before shipping a rating system
Applying all of the above to a real service, here is what needs attention.
1. Store the uncertainty, display something else
Rating and RD are what the system reasons with. What a player sees can be a tier, a rank name, or a conservative lower bound, as long as it is something you can change later without a migration.
The moment the raw number is visible, players optimise against it. From then on you have lost the freedom to fix it.
2. Gate the leaderboard on confidence
An RD of 75 or better, or whatever the equivalent is in your numbers.
It costs nothing, and it removes the entire category of “who is this person with 6 games at the top of the board.”
3. Cap everything that can run away
Volatility, single-game delta, rating floor, RD floor and ceiling.
Each cap is an admission that the model has regimes where it misbehaves. All of them together are cheaper than finding out which regime at 2am.
4. Decide the rating period on purpose
One period per game is a legitimate choice. It is also a different system from the one in the paper.
That difference should be something you chose, not something inherited from a library default.
5. Assume someone will try to lose
Work out what a player who wants to lose can extract from your system.
If the answer involves an amplifier you control, bound it before launch rather than after the leaderboard is already wrong.
Practice problems
1. Reproduce the worked example from the paper
The Glicko-2 paper carries one worked example: a player rated 1500 with an RD of 200 and volatility 0.06 plays opponents rated 1400 (RD 30), 1550 (RD 100), and 1700 (RD 300), winning the first game and losing the other two. Implement it and check your output against the final values printed in the paper.
2. Compare batched and per-game updates
Run the same set of games twice, once batched into a single rating period and once one period per game. Comparing the resulting rating, RD, and volatility shows exactly why the period setting matters.
3. Reproduce volatility farming
Build an input with a losing stretch, then an alternating stretch, then honest play, and compute the final rating. Compare it against a player who was honest throughout, and see how much the gap shrinks once you cap volatility.
References
- Glicko rating system — Wikipedia
- Mark Glickman’s Glicko page: Glicko, Glicko-2 and Glicko-Boost papers
- Glickman (1999), Parameter Estimation in Large Dynamic Paired Comparison Experiments, JRSS-C 48(3)
- The Glicko-2 system (PDF)
- lichess-org/lila — modules/rating/src/main/Glicko.scala
- lichess issue #7862, “Glicko2 may be flawed”
- Ebtekar & Liu, Elo-MMR: A Rating System for Massive Multiplayer Competitions (WWW 2021)
- TrueSkill — Wikipedia
- Elo rating system — Wikipedia