Home › Multiplayer & game networking › Rating & matchmaking
algorithm low

Glicko-2

Problem A rating system must cope with newcomers, returning players and veterans alike: a single number gives no sign of how reliable it is, so ratings converge slowly and pairing cannot judge its confidence. Competitors can be people, teams or AI models.

Rating plus rating deviation (RD) and volatility per player, so new or inactive players move fast and established ones move slowly.

O(games in period) per player per rating period, plus a ~10-iteration root find for volatility
ratingglickochessskilluncertaintymatchmaking
Overview

Elo treats a newcomer and a veteran at 1500 the same. Glicko attaches a rating deviation (RD): the standard deviation of belief about the rating. Results move a player more when their own RD is high and less when the opponent's is; playing shrinks RD, inactivity grows it. Glicko-2 adds volatility σ, how erratic the player's true strength is, updated from how surprising recent results were.

Key facts

Use when

  • Online chess and game sites with players joining, leaving and returning: ratings converge in order of 10-20 games instead of 50+.
  • Competitive programming contests and other skill ladders with uneven participation (Toph).
  • Sports forecasting where team strength drifts and reliability varies with recent activity (adaptive Glicko-2 for football).
  • Ranking AI models in head-to-head games, where the RD says when a model needs more matches (ChessArena).
  • Matchmaking that needs confidence (place uncertain players widely, trust confident ratings).

Avoid when

  • Team or multi-player games: use TrueSkill/OpenSkill-style models.
  • You need a hand-computable federation rating with fixed rules: Elo.

Deep dive

How it works

In: a player's (rating, RD, σ) and the results against opponents' (rating, RD) in one period · Out: new (rating, RD, σ) · State: three floats per player plus the time of the last period · Cost: O(games) per player per period plus a ~10-iteration root find.

Work on the Glicko-2 scale: μ = (r − 1500)/173.7178, φ = RD/173.7178.

  1. For each opponent j: g(φ_j) = 1/√(1 + 3φ_j²/π²) (discounts uncertain opponents), E_j = 1/(1 + exp(−g(φ_j)(μ − μ_j))).
  2. Estimated variance from games: v = [Σ g(φ_j)² E_j (1 − E_j)]⁻¹.
  3. Estimated improvement: Δ = v Σ g(φ_j)(s_j − E_j).
  4. New volatility σ′: solve f(x) = 0 for x = ln σ′² with the Illinois (regula falsi) method, where f(x) = eˣ(Δ² − φ² − v − eˣ) / (2(φ² + v + eˣ)²) − (x − ln σ²)/τ². Tolerance ε = 10⁻⁶.
  5. Pre-period deviation φ* = √(φ² + σ′²) (uncertainty grows with time/volatility).
  6. φ′ = 1/√(1/φ*² + 1/v); μ′ = μ + φ′² Σ g(φ_j)(s_j − E_j).
  7. Convert back: r′ = 173.7178 μ′ + 1500, RD′ = 173.7178 φ′.
  8. A player with no games in the period: μ and σ unchanged, φ′ = √(φ² + σ²).

Code

use std::f64::consts::PI;

const SCALE: f64 = 173.7178;

#[derive(Clone, Copy, Debug)]
pub struct Rating { pub r: f64, pub rd: f64, pub vol: f64 }

impl Default for Rating { fn default() -> Self { Rating { r: 1500.0, rd: 350.0, vol: 0.06 } } }

fn g(phi: f64) -> f64 { 1.0 / (1.0 + 3.0 * phi * phi / (PI * PI)).sqrt() }

/// One rating period. `games` = (opponent pre-period rating, score 1 / 0.5 / 0).
pub fn update(p: Rating, games: &[(Rating, f64)], tau: f64) -> Rating {
    let mu = (p.r - 1500.0) / SCALE;
    let phi = p.rd / SCALE;
    if games.is_empty() {
        let phi_star = (phi * phi + p.vol * p.vol).sqrt();
        return Rating { rd: (phi_star * SCALE).min(350.0), ..p };
    }
    let (mut v_inv, mut sum) = (0.0, 0.0);
    for (o, s) in games {
        let (mu_j, phi_j) = ((o.r - 1500.0) / SCALE, o.rd / SCALE);
        let gj = g(phi_j);
        let e = 1.0 / (1.0 + (-gj * (mu - mu_j)).exp());
        v_inv += gj * gj * e * (1.0 - e);
        sum += gj * (s - e);
    }
    let v = 1.0 / v_inv;
    let delta = v * sum;

    // Volatility: Illinois algorithm on f(x) = 0, x = ln(sigma'^2).
    let a = (p.vol * p.vol).ln();
    let (d2, p2) = (delta * delta, phi * phi);
    let f = |x: f64| {
        let ex = x.exp();
        ex * (d2 - p2 - v - ex) / (2.0 * (p2 + v + ex).powi(2)) - (x - a) / (tau * tau)
    };
    let mut big_a = a;
    let mut big_b = if d2 > p2 + v {
        (d2 - p2 - v).ln()
    } else {
        let mut k = 1.0;
        while f(a - k * tau) < 0.0 { k += 1.0; }
        a - k * tau
    };
    let (mut fa, mut fb) = (f(big_a), f(big_b));
    while (big_b - big_a).abs() > 1e-6 {
        let c = big_a + (big_a - big_b) * fa / (fb - fa);
        let fc = f(c);
        if fc * fb <= 0.0 { big_a = big_b; fa = fb; } else { fa /= 2.0; } // Illinois step
        big_b = c;
        fb = fc;
    }
    let vol = (big_a / 2.0).exp();

    let phi_star = (p2 + vol * vol).sqrt();
    let phi_new = 1.0 / (1.0 / (phi_star * phi_star) + 1.0 / v).sqrt();
    let mu_new = mu + phi_new * phi_new * sum;
    Rating { r: SCALE * mu_new + 1500.0, rd: SCALE * phi_new, vol }
}

/// Win probability of a over b, accounting for both deviations (useful for matchmaking).
pub fn expected(a: Rating, b: Rating) -> f64 {
    let phi = ((a.rd * a.rd + b.rd * b.rd).sqrt()) / SCALE;
    1.0 / (1.0 + (-g(phi) * (a.r - b.r) / SCALE).exp())
}

#[test]
fn glickman_example() {
    let p = Rating { r: 1500.0, rd: 200.0, vol: 0.06 };
    let opp = |r, rd| Rating { r, rd, vol: 0.06 };
    let games = [(opp(1400.0, 30.0), 1.0), (opp(1550.0, 100.0), 0.0), (opp(1700.0, 300.0), 0.0)];
    let n = update(p, &games, 0.5);
    assert!((n.r - 1464.06).abs() < 0.01, "{n:?}");
    assert!((n.rd - 151.52).abs() < 0.01, "{n:?}");
    assert!((n.vol - 0.05999).abs() < 1e-5, "{n:?}");
}

Complexity and constants

  • O(games) per player per period plus the volatility root find: Illinois converges in order of 5-10 iterations of a few exp calls. Rating 1M players per period takes order of a second on one core.
  • RD dynamics (one game per period, opponent RD 50, measured with the code below): RD 350 → 248 after 1 game, 147 after 5, 109 after 10 (about the Lichess provisional line), 82 after 20, 71 after 30. Inactivity regrows it by √(φ² + σ²) per period.
  • τ: smaller (0.3) = volatility barely changes, ratings stable; larger (1.2) = quicker reaction to sudden strength changes, noisier. Glickman suggests testing values on your data by predictive accuracy.

Efficiency

Cost: one player-period is a loop over the player's games with an exp per game plus about 5-10 Illinois iterations; order of 1 s for 1M players per period on one core. Memory is three f64 per player (24 B) plus a timestamp. The bound is the exp calls and reading the game log.

Levers:

  • Update inactive players in closed form φ′ = √(φ² + n·σ²) after n empty periods — skips n loop passes — sites that rate per game, not per period.
  • Looser root-find tolerance than 10⁻⁶ — fewer iterations (order of) — large batch jobs where rating digits beyond the first decimal do not matter.
  • Rate each player independently from pre-period ratings — embarrassingly parallel — millions of players.
  • Cache g(φ_j) per opponent per period — saves a sqrt per game — when many players share opponents.

Naive → standard → state of the art:

  • Elo with a fixed K · O(1) per game, no uncertainty
  • Glicko-2 per rating period · O(games) plus ~10 iterations, converges in order of 10-20 games
  • Bayesian team models (TrueSkill, OpenSkill) · cost grows with team size, handles multi-player outcomes

Implementation notes

  • Rating period: batch all games of a period using every player's pre-period rating; Lichess-style per-game updates still use the opponent's rating from before the game.
  • Real-time sites grow RD with elapsed time, not with periods: compute periods elapsed = Δt / period_length and apply step 8 that many times (or the closed form φ² + n·σ²) before the next game.
  • Cap RD at the initial 350 so long-inactive players don't get absurd uncertainty.
  • Show "?" or hide ratings while RD is high, and exclude provisional players from leaderboards (Lichess: RD > 110; leaderboards need more).
  • Matchmaking: use expected with both RDs, or match on rating while allowing wider windows for high-RD players (they need informative games).
  • Store σ with enough precision (f64); tiny volatility changes compound over thousands of games.

Pitfalls

  • Using post-update opponent ratings within the same period: results depend on processing order.
  • Mixing scales: forgetting the 173.7178 conversion (it is 400/ln 10) produces wildly wrong updates.
  • Treating Glicko-2 as Elo with a variable K and dropping the volatility step: players whose strength changes fast (juniors) lag behind.
  • Rating periods with a single game everywhere: the volatility estimate becomes noisy; the paper assumes moderate game counts per period.
  • Pool separation: ratings from different time controls or variants are different populations; keep one rating per pool.

Variants and alternatives

Glicko (1)rating + RD, constant c for RD growth; simpler, used by Chess.com.
Elono uncertainty, one parameter K.
TrueSkill / TrueSkill 2Bayesian factor graphs, teams and free-for-all, skill μ and σ per player.
OpenSkill (Weng-Lin)open, fast Bayesian approximation for teams, similar in spirit to TrueSkill.

In production

  • Chess and game sites: Lichess (Glicko-2; a rating is provisional while the deviation is above 110, rankable below 75 in standard chess), Chess.com (Glicko).
  • Programming contests: Toph uses Glicko-2 for contest ranks.
  • Sports: an arXiv preprint adapts Glicko-2 to football match forecasting and season simulation.
  • AI evaluation: ChessArena ranks LLMs playing each other with Glicko and samples matches to shrink rating deviation.

Implementations

namelangrepoinstallverdictlicencehealthas_ofnotes
skillratingsRustatomflunder/skillratingscargo add skillratings————Elo, Glicko, Glicko-2, TrueSkill and more
glicko_2RustReagentX/glicko2cargo add glicko_2————Standalone Glicko-2 crate
glicko2Pythondeepy/glicko2pip install glicko2————Glicko-2 implementation
openskillPythonvivekjoshy/openskill.pypip install openskill————Weng-Lin ratings for teams and free-for-all

Sources

  • Glickman, Example of the Glicko-2 System, glicko.net. pdf
  • Glickman, Dynamic Paired Comparison Models with Stochastic Variances, Journal of Applied Statistics, 2001. paper
  • Glickman, Parameter Estimation in Large Dynamic Paired Comparison Experiments, Applied Statistics (JRSS C), 1999. paper
  • Herbrich, Minka, Graepel, TrueSkill: A Bayesian Skill Rating System, NIPS, 2006. paper
  • Weng, Lin, A Bayesian Approximation Method for Online Ranking, JMLR, 2011. paper
  • Glickman, The Glicko system, glicko.net. pdf
  • Lichess, FAQ: provisional ratings. page
  • Toph, Rating System. page
  • Nguyen, Tran, An Adaptive Glicko-2 Rating Framework for Probabilistic Football Forecasting and Season Simulation, arXiv 2607.01722. arXiv
  • ChessArena: A Chess Testbed for Evaluating Strategic Reasoning Capabilities of Large Language Models, arXiv 2509.24239. arXiv
  • Chess.com Support, How do ratings work on Chess.com?. page

Used by

pairing queue · plannedPair waiting players into fair matches by rating and latency, widening tolerances the longer each ticket waits

Systems (4):

Alternatives and related