Machine Coding Problem

Game Leaderboard (Min-Heap/Skip List)

macoAllgamesmin-heap-/-skip-list-logic
Commonly Asked By:Riot GamesEpic GamesRoblox

Functional Specifications

  • Dynamic Rank Updates: Support score changes for any player with efficient O(log n) updates, reflecting instantly on rank listings.
  • Efficient Top-K Queries: Maintain sorted indexes to retrieve the top K players in O(K) time without sorting the entire dataset.
  • Tie-Breaking Timestamp Logic: If multiple players hold identical scores, the player who reached the score first is prioritized with the higher rank.
  • Nearby-Rank Window Queries: Retrieve a slice of the leaderboard centered on a target player (e.g., the target player plus the N ranks above and N ranks below).

Production reference implementations demonstrating composite score keys, sorted skip lists, dynamic rank lookup, and sliding rank windows:

// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;

class ConcurrentLeaderboard {
    private final ConcurrentHashMap<String, PlayerScore> playerMap = new ConcurrentHashMap<>();
    private final ConcurrentSkipListSet<ScoreKey> sortedScores = new ConcurrentSkipListSet<>();

    public synchronized void updateScore(String playerId, double score) {
        long now = System.currentTimeMillis();
        PlayerScore existing = playerMap.get(playerId);
        if (existing != null) {
            // Remove old key from sorted set
            sortedScores.remove(new ScoreKey(existing.getScore(), existing.getTimestamp(), playerId));
            existing.setScore(score, now);
        } else {
            existing = new PlayerScore(playerId, score, now);
            playerMap.put(playerId, existing);
        }
        // Insert new key to sorted set
        sortedScores.add(new ScoreKey(score, now, playerId));
    }

    public synchronized int getRank(String playerId) {
        PlayerScore player = playerMap.get(playerId);
        if (player == null) return -1;

        ScoreKey key = new ScoreKey(player.getScore(), player.getTimestamp(), playerId);
        // HeadSet gives elements strictly greater than key.
        // Size operation is O(log n) when using index traversal or order-statistic skip list structures.
        return sortedScores.headSet(key).size() + 1;
    }

    public List<LeaderboardEntry> getTopK(int k) {
        List<LeaderboardEntry> result = new ArrayList<>();
        int count = 0;
        int rank = 1;
        for (ScoreKey key : sortedScores) {
            if (count >= k) break;
            result.add(new LeaderboardEntry(key.getPlayerId(), key.getScore(), rank++));
            count++;
        }
        return result;
    }

    public List<LeaderboardEntry> getWindow(String playerId, int n) {
        PlayerScore target = playerMap.get(playerId);
        if (target == null) return Collections.emptyList();

        List<ScoreKey> all = new ArrayList<>(sortedScores);
        int targetIdx = -1;
        for (int i = 0; i < all.size(); i++) {
            if (all.get(i).getPlayerId().equals(playerId)) {
                targetIdx = i;
                break;
            }
        }

        if (targetIdx == -1) return Collections.emptyList();

        int start = Math.max(0, targetIdx - n);
        int end = Math.min(all.size() - 1, targetIdx + n);

        List<LeaderboardEntry> window = new ArrayList<>();
        for (int i = start; i <= end; i++) {
            ScoreKey key = all.get(i);
            window.add(new LeaderboardEntry(key.getPlayerId(), key.getScore(), i + 1));
        }
        return window;
    }

    public static class PlayerScore {
        private final String playerId;
        private double score;
        private long timestamp;

        public PlayerScore(String playerId, double score, long timestamp) {
            this.playerId = playerId;
            this.score = score;
            this.timestamp = timestamp;
        }

        public String getPlayerId() { return playerId; }
        public double getScore() { return score; }
        public long getTimestamp() { return timestamp; }

        public void setScore(double score, long timestamp) {
            this.score = score;
            this.timestamp = timestamp;
        }
    }

    public static class ScoreKey implements Comparable<ScoreKey> {
        private final double score;
        private final long timestamp;
        private final String playerId;

        public ScoreKey(double score, long timestamp, String playerId) {
            this.score = score;
            this.timestamp = timestamp;
            this.playerId = playerId;
        }

        public double getScore() { return score; }
        public long getTimestamp() { return timestamp; }
        public String getPlayerId() { return playerId; }

        @Override
        public int compareTo(ScoreKey other) {
            if (Double.compare(other.score, this.score) != 0) {
                // Descending score order
                return Double.compare(other.score, this.score);
            }
            if (this.timestamp != other.timestamp) {
                // Ascending timestamp order (tie-breaker: earlier is better)
                return Long.compare(this.timestamp, other.timestamp);
            }
            return this.playerId.compareTo(other.playerId);
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            ScoreKey scoreKey = (ScoreKey) o;
            return Double.compare(scoreKey.score, score) == 0 &&
                    timestamp == scoreKey.timestamp &&
                    playerId.equals(scoreKey.playerId);
        }

        @Override
        public int hashCode() {
            return Objects.hash(score, timestamp, playerId);
        }
    }

    public static class LeaderboardEntry {
        private final String playerId;
        private final double score;
        private final int rank;

        public LeaderboardEntry(String playerId, double score, int rank) {
            this.playerId = playerId;
            this.score = score;
            this.rank = rank;
        }

        public String getPlayerId() { return playerId; }
        public double getScore() { return score; }
        public int getRank() { return rank; }
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println("=== GAME LEADERBOARD DEMO (JAVA) ===");
        ConcurrentLeaderboard leaderboard = new ConcurrentLeaderboard();
        
        // Update scores
        leaderboard.updateScore("ApexPredator", 9850);
        leaderboard.updateScore("ShadowSlayer", 8600);
        leaderboard.updateScore("CyberPunk", 8600); // Equal score, later timestamp (ranks lower)
        leaderboard.updateScore("PixelKing", 7200);
        
        System.out.println("Initial Top 3 Players:");
        for (ConcurrentLeaderboard.LeaderboardEntry entry : leaderboard.getTopK(3)) {
            System.out.println("Rank #" + entry.getRank() + ": " + entry.getPlayerId() + " - Score: " + entry.getScore());
        }
        
        System.out.println("\nRank of CyberPunk: " + leaderboard.getRank("CyberPunk"));
        System.out.println("Rank of ShadowSlayer: " + leaderboard.getRank("ShadowSlayer"));
        
        System.out.println("\nRetrieving window around ShadowSlayer (Radius 1):");
        for (ConcurrentLeaderboard.LeaderboardEntry entry : leaderboard.getWindow("ShadowSlayer", 1)) {
            System.out.println("Rank #" + entry.getRank() + ": " + entry.getPlayerId() + " - Score: " + entry.getScore());
        }
        
        System.out.println("\nUpdating CyberPunk score to 9900...");
        leaderboard.updateScore("CyberPunk", 9900);
        System.out.println("New Top 3 Players:");
        for (ConcurrentLeaderboard.LeaderboardEntry entry : leaderboard.getTopK(3)) {
            System.out.println("Rank #" + entry.getRank() + ": " + entry.getPlayerId() + " - Score: " + entry.getScore());
        }
    }
}