Problem

In chess, the Elo rating system is used to calculate player strengths based on game results.

A simplified description of the Elo system is as follows. Every player begins at the same score. For each subsequent game, the loser transfers some points to the winner, where the amount of points transferred depends on how unlikely the win is. For example, a 1200-ranked player should gain much more points for beating a 2000-ranked player than for beating a 1300-ranked player.

Implement this system.

Examples

Example 1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Input: 
- Player A rating: 1200
- Player B rating: 1300
- Result: A wins
- K-factor: 32

Output: 
- Player A new rating: 1219
- Player B new rating: 1281

Explanation: 
Expected score for A = 1/(1+10^((1300-1200)/400)) = 0.36
Rating change = 32 * (1 - 0.36) = 20.48  19
A: 1200 + 19 = 1219, B: 1300 - 19 = 1281

Example 2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Input:
- Player A rating: 1200
- Player B rating: 2000
- Result: A wins
- K-factor: 32

Output:
- Player A new rating: 1231
- Player B new rating: 1969

Explanation:
Expected score for A = 1/(1+10^((2000-1200)/400)) = 0.009
Rating change = 32 * (1 - 0.009) = 31.71  31
A: 1200 + 31 = 1231, B: 2000 - 31 = 1969

Example 3

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Input:
- Player A rating: 2000
- Player B rating: 1200
- Result: A wins
- K-factor: 32

Output:
- Player A new rating: 2000
- Player B new rating: 1200

Explanation:
Expected score for A = 1/(1+10^((1200-2000)/400)) = 0.991
Rating change = 32 * (1 - 0.991) = 0.29  0
A: 2000 + 0 = 2000, B: 1200 - 0 = 1200

Example 4

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Input:
- Player A rating: 1500
- Player B rating: 1500
- Result: Draw
- K-factor: 32

Output:
- Player A new rating: 1500
- Player B new rating: 1500

Explanation:
Expected score for both = 0.5 (equal ratings)
For draw, actual score = 0.5 for both
Rating change = 32 * (0.5 - 0.5) = 0
Both players remain at 1500

Example 5

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Input:
- Player A rating: 1800
- Player B rating: 1600
- Result: B wins
- K-factor: 16

Output:
- Player A new rating: 1788
- Player B new rating: 1612

Explanation:
Expected score for A = 1/(1+10^((1600-1800)/400)) = 0.76
Rating change = 16 * (0 - 0.76) = -12.16  -12
A: 1800 - 12 = 1788, B: 1600 + 12 = 1612

Solution

Method 1 - Standard Elo Rating Calculation

Intuition

The Elo rating system calculates expected performance based on rating differences and adjusts ratings based on actual vs expected results. The core formula uses the logistic curve to determine win probability, then applies a K-factor to scale the rating change. Larger rating differences lead to more extreme expected scores, making upsets worth more points.

Approach

  1. Calculate expected score for each player using the Elo formula: E = 1 / (1 + 10^((opponent_rating - player_rating) / 400))
  2. Determine actual scores based on game result (1 for win, 0 for loss, 0.5 for draw)
  3. Calculate rating changes using: new_rating = old_rating + K * (actual_score - expected_score)
  4. Apply changes to both players (one gains what the other loses)
  5. Round to nearest integer for final ratings
  6. Handle edge cases like draws and equal ratings

Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class EloRatingSystem {
private:
    double kFactor;
    
public:
    EloRatingSystem(double k = 32.0) : kFactor(k) {}
    
    pair<int, int> updateRatings(int ratingA, int ratingB, int result) {
        // Calculate expected scores
        double expectedA = calculateExpectedScore(ratingA, ratingB);
        double expectedB = 1.0 - expectedA;
        
        // Determine actual scores based on result
        double actualA, actualB;
        if (result == 1) {  // A wins
            actualA = 1.0;
            actualB = 0.0;
        } else if (result == -1) {  // B wins
            actualA = 0.0;
            actualB = 1.0;
        } else {  // Draw
            actualA = 0.5;
            actualB = 0.5;
        }
        
        // Calculate rating changes
        int changeA = round(kFactor * (actualA - expectedA));
        int changeB = round(kFactor * (actualB - expectedB));
        
        return {ratingA + changeA, ratingB + changeB};
    }
    
private:
    double calculateExpectedScore(int rating, int opponentRating) {
        double diff = (double)(opponentRating - rating) / 400.0;
        return 1.0 / (1.0 + pow(10.0, diff));
    }
};

class Solution {
public:
    pair<int, int> updateEloRatings(int ratingA, int ratingB, int result, int k = 32) {
        EloRatingSystem elo(k);
        return elo.updateRatings(ratingA, ratingB, result);
    }
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import (
    "math"
)

type EloRatingSystem struct {
    kFactor float64
}

func NewEloRatingSystem(k float64) *EloRatingSystem {
    return &EloRatingSystem{kFactor: k}
}

func (e *EloRatingSystem) UpdateRatings(ratingA, ratingB, result int) (int, int) {
    // Calculate expected scores
    expectedA := e.calculateExpectedScore(ratingA, ratingB)
    expectedB := 1.0 - expectedA
    
    // Determine actual scores based on result
    var actualA, actualB float64
    switch result {
    case 1:  // A wins
        actualA, actualB = 1.0, 0.0
    case -1: // B wins
        actualA, actualB = 0.0, 1.0
    default: // Draw
        actualA, actualB = 0.5, 0.5
    }
    
    // Calculate rating changes
    changeA := int(math.Round(e.kFactor * (actualA - expectedA)))
    changeB := int(math.Round(e.kFactor * (actualB - expectedB)))
    
    return ratingA + changeA, ratingB + changeB
}

func (e *EloRatingSystem) calculateExpectedScore(rating, opponentRating int) float64 {
    diff := float64(opponentRating-rating) / 400.0
    return 1.0 / (1.0 + math.Pow(10.0, diff))
}

func updateEloRatings(ratingA, ratingB, result, k int) (int, int) {
    elo := NewEloRatingSystem(float64(k))
    return elo.UpdateRatings(ratingA, ratingB, result)
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class EloRatingSystem {
    private double kFactor;
    
    public EloRatingSystem(double k) {
        this.kFactor = k;
    }
    
    public int[] updateRatings(int ratingA, int ratingB, int result) {
        // Calculate expected scores
        double expectedA = calculateExpectedScore(ratingA, ratingB);
        double expectedB = 1.0 - expectedA;
        
        // Determine actual scores based on result
        double actualA, actualB;
        if (result == 1) {  // A wins
            actualA = 1.0;
            actualB = 0.0;
        } else if (result == -1) {  // B wins
            actualA = 0.0;
            actualB = 1.0;
        } else {  // Draw
            actualA = 0.5;
            actualB = 0.5;
        }
        
        // Calculate rating changes
        int changeA = (int) Math.round(kFactor * (actualA - expectedA));
        int changeB = (int) Math.round(kFactor * (actualB - expectedB));
        
        return new int[]{ratingA + changeA, ratingB + changeB};
    }
    
    private double calculateExpectedScore(int rating, int opponentRating) {
        double diff = (double)(opponentRating - rating) / 400.0;
        return 1.0 / (1.0 + Math.pow(10.0, diff));
    }
}

class Solution {
    public int[] updateEloRatings(int ratingA, int ratingB, int result, int k) {
        EloRatingSystem elo = new EloRatingSystem(k);
        return elo.updateRatings(ratingA, ratingB, result);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import math
from typing import Tuple

class EloRatingSystem:
    def __init__(self, k_factor: float = 32.0):
        self.k_factor = k_factor
    
    def update_ratings(self, rating_a: int, rating_b: int, result: int) -> Tuple[int, int]:
        # Calculate expected scores
        expected_a = self._calculate_expected_score(rating_a, rating_b)
        expected_b = 1.0 - expected_a
        
        # Determine actual scores based on result
        if result == 1:  # A wins
            actual_a, actual_b = 1.0, 0.0
        elif result == -1:  # B wins
            actual_a, actual_b = 0.0, 1.0
        else:  # Draw
            actual_a, actual_b = 0.5, 0.5
        
        # Calculate rating changes
        change_a = round(self.k_factor * (actual_a - expected_a))
        change_b = round(self.k_factor * (actual_b - expected_b))
        
        return rating_a + change_a, rating_b + change_b
    
    def _calculate_expected_score(self, rating: int, opponent_rating: int) -> float:
        diff = (opponent_rating - rating) / 400.0
        return 1.0 / (1.0 + math.pow(10.0, diff))

class Solution:
    def update_elo_ratings(self, rating_a: int, rating_b: int, result: int, k: int = 32) -> Tuple[int, int]:
        elo = EloRatingSystem(k)
        return elo.update_ratings(rating_a, rating_b, result)

Complexity

  • ⏰ Time complexity: O(1), all calculations are constant time mathematical operations
  • 🧺 Space complexity: O(1), only using constant extra space for calculations

Method 2 - Tournament Management System

Intuition

For managing multiple players and games, we need a more comprehensive system that tracks player histories, handles different K-factors based on player experience, and maintains a leaderboard. This approach extends the basic Elo calculation to a full tournament management system.

Approach

  1. Create a Player class to store rating, games played, and history
  2. Implement different K-factors based on player experience (higher for new players)
  3. Maintain a tournament system that can process multiple games
  4. Add methods for ranking players and calculating statistics
  5. Handle edge cases like minimum/maximum ratings and provisional ratings
  6. Provide methods to query player performance and rating trends

Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
class Player {
public:
    string name;
    int rating;
    int gamesPlayed;
    vector<int> ratingHistory;
    
    Player(string n, int initialRating = 1500) 
        : name(n), rating(initialRating), gamesPlayed(0) {
        ratingHistory.push_back(initialRating);
    }
    
    int getKFactor() {
        if (gamesPlayed < 30) return 40;      // New players
        else if (rating < 2100) return 20;    // Regular players
        else return 10;                       // Expert players
    }
    
    void updateRating(int newRating) {
        rating = max(100, min(3000, newRating));  // Clamp between 100-3000
        gamesPlayed++;
        ratingHistory.push_back(rating);
    }
};

class TournamentEloSystem {
private:
    unordered_map<string, Player> players;
    
public:
    void addPlayer(string name, int initialRating = 1500) {
        players[name] = Player(name, initialRating);
    }
    
    void recordGame(string playerA, string playerB, int result) {
        if (players.find(playerA) == players.end() || 
            players.find(playerB) == players.end()) {
            return;  // Player not found
        }
        
        Player& pA = players[playerA];
        Player& pB = players[playerB];
        
        // Use average K-factor for the game
        double kFactor = (pA.getKFactor() + pB.getKFactor()) / 2.0;
        
        // Calculate expected scores
        double expectedA = 1.0 / (1.0 + pow(10.0, (pB.rating - pA.rating) / 400.0));
        double expectedB = 1.0 - expectedA;
        
        // Determine actual scores
        double actualA, actualB;
        if (result == 1) {
            actualA = 1.0; actualB = 0.0;
        } else if (result == -1) {
            actualA = 0.0; actualB = 1.0;
        } else {
            actualA = 0.5; actualB = 0.5;
        }
        
        // Calculate and apply rating changes
        int changeA = round(kFactor * (actualA - expectedA));
        int changeB = round(kFactor * (actualB - expectedB));
        
        pA.updateRating(pA.rating + changeA);
        pB.updateRating(pB.rating + changeB);
    }
    
    vector<pair<string, int>> getLeaderboard() {
        vector<pair<string, int>> leaderboard;
        for (auto& p : players) {
            leaderboard.push_back({p.second.name, p.second.rating});
        }
        sort(leaderboard.begin(), leaderboard.end(), 
             [](const auto& a, const auto& b) { return a.second > b.second; });
        return leaderboard;
    }
    
    int getPlayerRating(string name) {
        return players.find(name) != players.end() ? players[name].rating : -1;
    }
};

class Solution {
public:
    pair<int, int> processGameAdvanced(string playerA, string playerB, 
                                      int ratingA, int ratingB, int result) {
        TournamentEloSystem tournament;
        tournament.addPlayer(playerA, ratingA);
        tournament.addPlayer(playerB, ratingB);
        tournament.recordGame(playerA, playerB, result);
        
        return {tournament.getPlayerRating(playerA), 
                tournament.getPlayerRating(playerB)};
    }
};
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
type Player struct {
    Name          string
    Rating        int
    GamesPlayed   int
    RatingHistory []int
}

func NewPlayer(name string, initialRating int) *Player {
    return &Player{
        Name:          name,
        Rating:        initialRating,
        GamesPlayed:   0,
        RatingHistory: []int{initialRating},
    }
}

func (p *Player) GetKFactor() int {
    if p.GamesPlayed < 30 {
        return 40 // New players
    } else if p.Rating < 2100 {
        return 20 // Regular players
    }
    return 10 // Expert players
}

func (p *Player) UpdateRating(newRating int) {
    if newRating < 100 {
        newRating = 100
    }
    if newRating > 3000 {
        newRating = 3000
    }
    
    p.Rating = newRating
    p.GamesPlayed++
    p.RatingHistory = append(p.RatingHistory, newRating)
}

type TournamentEloSystem struct {
    Players map[string]*Player
}

func NewTournamentEloSystem() *TournamentEloSystem {
    return &TournamentEloSystem{
        Players: make(map[string]*Player),
    }
}

func (t *TournamentEloSystem) AddPlayer(name string, initialRating int) {
    t.Players[name] = NewPlayer(name, initialRating)
}

func (t *TournamentEloSystem) RecordGame(playerA, playerB string, result int) {
    pA, okA := t.Players[playerA]
    pB, okB := t.Players[playerB]
    
    if !okA || !okB {
        return // Player not found
    }
    
    // Use average K-factor for the game
    kFactor := float64(pA.GetKFactor()+pB.GetKFactor()) / 2.0
    
    // Calculate expected scores
    diff := float64(pB.Rating-pA.Rating) / 400.0
    expectedA := 1.0 / (1.0 + math.Pow(10.0, diff))
    expectedB := 1.0 - expectedA
    
    // Determine actual scores
    var actualA, actualB float64
    switch result {
    case 1:
        actualA, actualB = 1.0, 0.0
    case -1:
        actualA, actualB = 0.0, 1.0
    default:
        actualA, actualB = 0.5, 0.5
    }
    
    // Calculate and apply rating changes
    changeA := int(math.Round(kFactor * (actualA - expectedA)))
    changeB := int(math.Round(kFactor * (actualB - expectedB)))
    
    pA.UpdateRating(pA.Rating + changeA)
    pB.UpdateRating(pB.Rating + changeB)
}

func (t *TournamentEloSystem) GetPlayerRating(name string) int {
    if player, ok := t.Players[name]; ok {
        return player.Rating
    }
    return -1
}

func processGameAdvanced(playerA, playerB string, ratingA, ratingB, result int) (int, int) {
    tournament := NewTournamentEloSystem()
    tournament.AddPlayer(playerA, ratingA)
    tournament.AddPlayer(playerB, ratingB)
    tournament.RecordGame(playerA, playerB, result)
    
    return tournament.GetPlayerRating(playerA), tournament.GetPlayerRating(playerB)
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
class Player {
    String name;
    int rating;
    int gamesPlayed;
    List<Integer> ratingHistory;
    
    public Player(String name, int initialRating) {
        this.name = name;
        this.rating = initialRating;
        this.gamesPlayed = 0;
        this.ratingHistory = new ArrayList<>();
        this.ratingHistory.add(initialRating);
    }
    
    public int getKFactor() {
        if (gamesPlayed < 30) return 40;      // New players
        else if (rating < 2100) return 20;    // Regular players
        else return 10;                       // Expert players
    }
    
    public void updateRating(int newRating) {
        this.rating = Math.max(100, Math.min(3000, newRating));
        this.gamesPlayed++;
        this.ratingHistory.add(this.rating);
    }
}

class TournamentEloSystem {
    private Map<String, Player> players;
    
    public TournamentEloSystem() {
        this.players = new HashMap<>();
    }
    
    public void addPlayer(String name, int initialRating) {
        players.put(name, new Player(name, initialRating));
    }
    
    public void recordGame(String playerA, String playerB, int result) {
        Player pA = players.get(playerA);
        Player pB = players.get(playerB);
        
        if (pA == null || pB == null) return;
        
        // Use average K-factor for the game
        double kFactor = (pA.getKFactor() + pB.getKFactor()) / 2.0;
        
        // Calculate expected scores
        double expectedA = 1.0 / (1.0 + Math.pow(10.0, (pB.rating - pA.rating) / 400.0));
        double expectedB = 1.0 - expectedA;
        
        // Determine actual scores
        double actualA, actualB;
        if (result == 1) {
            actualA = 1.0; actualB = 0.0;
        } else if (result == -1) {
            actualA = 0.0; actualB = 1.0;
        } else {
            actualA = 0.5; actualB = 0.5;
        }
        
        // Calculate and apply rating changes
        int changeA = (int) Math.round(kFactor * (actualA - expectedA));
        int changeB = (int) Math.round(kFactor * (actualB - expectedB));
        
        pA.updateRating(pA.rating + changeA);
        pB.updateRating(pB.rating + changeB);
    }
    
    public int getPlayerRating(String name) {
        Player player = players.get(name);
        return player != null ? player.rating : -1;
    }
}

class Solution {
    public int[] processGameAdvanced(String playerA, String playerB, 
                                   int ratingA, int ratingB, int result) {
        TournamentEloSystem tournament = new TournamentEloSystem();
        tournament.addPlayer(playerA, ratingA);
        tournament.addPlayer(playerB, ratingB);
        tournament.recordGame(playerA, playerB, result);
        
        return new int[]{tournament.getPlayerRating(playerA), 
                        tournament.getPlayerRating(playerB)};
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
from typing import Dict, List, Tuple
import math

class Player:
    def __init__(self, name: str, initial_rating: int = 1500):
        self.name = name
        self.rating = initial_rating
        self.games_played = 0
        self.rating_history = [initial_rating]
    
    def get_k_factor(self) -> int:
        if self.games_played < 30:
            return 40  # New players
        elif self.rating < 2100:
            return 20  # Regular players
        else:
            return 10  # Expert players
    
    def update_rating(self, new_rating: int) -> None:
        self.rating = max(100, min(3000, new_rating))  # Clamp between 100-3000
        self.games_played += 1
        self.rating_history.append(self.rating)

class TournamentEloSystem:
    def __init__(self):
        self.players: Dict[str, Player] = {}
    
    def add_player(self, name: str, initial_rating: int = 1500) -> None:
        self.players[name] = Player(name, initial_rating)
    
    def record_game(self, player_a: str, player_b: str, result: int) -> None:
        if player_a not in self.players or player_b not in self.players:
            return  # Player not found
        
        p_a = self.players[player_a]
        p_b = self.players[player_b]
        
        # Use average K-factor for the game
        k_factor = (p_a.get_k_factor() + p_b.get_k_factor()) / 2.0
        
        # Calculate expected scores
        expected_a = 1.0 / (1.0 + math.pow(10.0, (p_b.rating - p_a.rating) / 400.0))
        expected_b = 1.0 - expected_a
        
        # Determine actual scores
        if result == 1:  # A wins
            actual_a, actual_b = 1.0, 0.0
        elif result == -1:  # B wins
            actual_a, actual_b = 0.0, 1.0
        else:  # Draw
            actual_a, actual_b = 0.5, 0.5
        
        # Calculate and apply rating changes
        change_a = round(k_factor * (actual_a - expected_a))
        change_b = round(k_factor * (actual_b - expected_b))
        
        p_a.update_rating(p_a.rating + change_a)
        p_b.update_rating(p_b.rating + change_b)
    
    def get_player_rating(self, name: str) -> int:
        return self.players[name].rating if name in self.players else -1
    
    def get_leaderboard(self) -> List[Tuple[str, int]]:
        return sorted([(p.name, p.rating) for p in self.players.values()], 
                     key=lambda x: x[1], reverse=True)

class Solution:
    def process_game_advanced(self, player_a: str, player_b: str, 
                            rating_a: int, rating_b: int, result: int) -> Tuple[int, int]:
        tournament = TournamentEloSystem()
        tournament.add_player(player_a, rating_a)
        tournament.add_player(player_b, rating_b)
        tournament.record_game(player_a, player_b, result)
        
        return tournament.get_player_rating(player_a), tournament.get_player_rating(player_b)

Complexity

  • ⏰ Time complexity: O(1) per game, O(N log N) for leaderboard generation where N is number of players
  • 🧺 Space complexity: O(N), where N is the number of players stored in the system

Notes

  • The standard Elo formula uses base 10 and a divisor of 400 for the rating difference calculation
  • K-factor determines how much ratings change per game - higher for new/lower-rated players
  • The system maintains rating conservation (total points gained = total points lost)
  • Method 1 provides the core Elo calculation for single games
  • Method 2 extends to a full tournament management system with player tracking
  • Real chess systems often use different K-factors based on player strength and experience
  • The logistic curve ensures that rating differences translate to meaningful win probabilities