Machine Coding Problem

Game Leaderboard (Min-Heap/Skip List)

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

Requirements & Scope

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).

Leaderboard Skip List Architecture

Entity relationships and hierarchical skip list indexing models:

Loading...

Design Patterns

  • Composite Key Pattern: Bundles multiple sorting metrics (score, timestamp, playerId) into a single comparable key to enforce deterministic natural sorting.
  • Index Multi-Leveling (Skip List): Utilizes probabilistic node heights to build skip links, bypassing long scans to find matching nodes in logarithmic time.
  • Order-Statistic Tree Expansion: Augments index nodes with subtree sizes to make rank retrieval an O(log n) operation instead of O(n) scan.

Execution Workflows

Score Update & Re-indexing Workflow

  1. Lookup Player: Query PlayerMap to verify if the player has an existing score record.
  2. Index Invalidation: If present, extract the previous composite key and remove the matching node from the SkipListIndex.
  3. Generate New Key: Record the new score and capture the current timestamp. Instantiated as the new composite key.
  4. Sorted Insertion: Traverse down skip list layers. Insert new node at random height and repair pointer tracks in O(log n).
  5. Rank Resolution: Count the cumulative size of preceding nodes at higher intervals to resolve the updated rank instantly.

Clean Code Blueprint

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());
        }
    }
}

Interactive Simulator

❖ Logarithmic Game Leaderboard Simulator

Real-time score updates, composite tie-breakers, and visual multi-level index skip lists.

πŸ“Š Skip List Multi-Level Index Visualizer

Layer 3
Head
──►
ApexPredator
Score: 9850
Layer 2
Head
──►
ApexPredator
Score: 9850
──────►
PixelKing
Score: 7200
Layer 1
Head
──►
ApexPredator
Score: 9850
──────►
ShadowSlayer
Score: 8600
──────►
PixelKing
Score: 7200
──────►
SpeedRunner
Score: 6500
Layer 0
Head
──►
ApexPredator
Score: 9850
──────►
ShadowSlayer
Score: 8600
──────►
CyberPunk
Score: 8600
──────►
PixelKing
Score: 7200
──────►
SpeedRunner
Score: 6500
──────►
Valkyrie
Score: 5400

πŸ’‘ Higher layers act as expressways, skipping redundant nodes to execute score index lookups in O(log n) time.

πŸ“ Dynamic Score Update Center

πŸ† Active Leaderboard Scoreboard

RankPlayer IDScoreTimestamp Tie-breaker
#1ApexPredator9,85010:13:20 AM
#2ShadowSlayer8,60010:30:00 AM
#3CyberPunk8,60010:46:40 AM
#4PixelKing7,20010:21:40 AM
#5SpeedRunner6,50011:03:20 AM
#6Valkyrie5,40010:38:20 AM

πŸ” Nearby-Rank Window Query

Center Player:
Radius (N):
#2ShadowSlayer 8,600
#3CyberPunk (Center Target)8,600
#4PixelKing 7,200

🎯 Instant O(log n) Rank Lookup

Index Traversal Console Logs
[13:50:00] Concurrent Skip List index initialized.
[13:50:02] Loaded 6 default player scores.

πŸ’¬Review

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...