Build a Real-Time Leaderboard with Redis

Use Redis Sorted Sets to build a ranked leaderboard with live score updates.

Introduction

30 Second Summary

Every game you've played online has a leaderboard showing who's on top. Behind the scenes, that ranking updates instantly because it's powered by an in-memory data store designed for speed.

In this project, you will build a real-time gaming leaderboard using Redis Sorted Sets, a data structure that keeps players ranked automatically as scores change.

What You'll Build

A Python application that manages a live leaderboard backed by a Redis server running in Docker.

By the end of this project, you'll have:

  • A running Redis server in a Docker container, with hands-on experience using redis-cli to explore data interactively.
  • A Python leaderboard module that adds players, updates scores, retrieves rankings, and shows the neighborhood around any player.
  • A live simulation script that randomly updates player scores and displays the leaderboard in real time, with automatic expiration using Redis TTL.
  • Secret Mission: A multi-league system with separate leaderboards and a cross-league hall of fame.

Are there any prerequisites?

You should be comfortable with basic Python (variables, functions, and loops). You also need Docker and VS Code installed on your machine.

Set Up Redis and Your Python Environment

Before you can build a leaderboard, you need two things running on your machine: a Redis server to store your ranked data, and a Python environment that can talk to it.

In this step, you will spin up Redis inside a Docker container, create a Python virtual environment, and confirm everything connects end to end.

In this step, get ready to:
  • Start a Redis server in Docker and verify it responds.
  • Create a Python virtual environment with the redis-py library installed.
  • Run a test script that proves Python can talk to Redis.
Start Redis in Docker

Redis is an in-memory data store that runs as a server process. Instead of installing it directly on your system, you will run it inside a Docker container for a clean, isolated setup.

  • Open a terminal in VS Code by pressing Ctrl+`.
  • Pull and start the Redis container by running this command:
docker run -d --name my-redis -p 6379:6379 redis:latest

What does this command do?

  • -d runs the container in the background (detached mode).
  • --name my-redis gives the container a friendly name so you can reference it later.
  • -p 6379:6379 maps the container's Redis port to the same port on your machine.
  • redis:latest pulls the official Redis image from Docker Hub.
  • Verify Redis is responding by running a PING test:
docker exec -it my-redis redis-cli PING

You should see PONG printed in your terminal. This confirms Redis is running and accepting connections.

Not seeing PONG?

  • Make sure Docker Desktop is running. If you see a "Cannot connect to the Docker daemon" error, start Docker first.
  • If you see a "name already in use" error, a container named my-redis already exists. Remove it with docker rm -f my-redis and re-run the command above.

Still stuck? Ask for help.

Create a Python virtual environment and install redis-py

A virtual environment keeps your project's Python packages isolated from the rest of your system. You will install the redis-py library here so your leaderboard code can communicate with Redis.

  • Create the virtual environment by running this command:
python3 -m venv venv
  • Activate the virtual environment:
source venv/bin/activate

You should see (venv) appear at the start of your terminal prompt. This confirms the virtual environment is active.

  • Install the Redis Python client by running:
pip install redis==8.0.1

You should see output ending with "Successfully installed" and the redis package listed.

Seeing a permission error or install failure?

  • Check that your terminal prompt starts with (venv). If it does not, re-run source venv/bin/activate before installing.
  • If you see a "No module named venv" error, your Python installation may be missing the venv module. Install it with sudo apt install python3-venv and try again.

Need more help?

Verify the connection from Python

You have Redis running and the Python library installed. Now you will write a quick test script to confirm Python can reach Redis and store data.

  • In VS Code, create a new file called test_connection.py in your project folder.
  • Add the following code to test_connection.py:
import redis

# Connect to the local Redis server running in Docker
r = redis.Redis(host="localhost", port=6379, decode_responses=True)

# Store a test value
r.set("test_key", "hello from python")

# Retrieve and print it
value = r.get("test_key")
print(f"Got from Redis: {value}")

# Clean up the test key
r.delete("test_key")
print("Connection test passed!")

What does this code do?

  • redis.Redis(host="localhost", port=6379, decode_responses=True) creates a connection to the Redis server running in your Docker container. The decode_responses=True option means Redis returns strings instead of raw bytes.
  • r.set() stores a key-value pair, and r.get() retrieves it. This is the simplest Redis operation and proves the full round-trip works.
  • r.delete() removes the test key so you do not leave stale data in Redis.
  • Save test_connection.py.
  • Run the script in your terminal:
python3 test_connection.py

You should see this output:

Got from Redis: hello from python
Connection test passed!

Seeing a ConnectionError?

  • Confirm the Redis container is still running with docker ps. You should see my-redis in the list. If it is not there, restart it with docker start my-redis.
  • Make sure you are running python3 test_connection.py from within the activated virtual environment (you should see (venv) in your prompt).

Help me debug my Python-to-Redis connection.

Your environment is fully wired up. Redis is running, Python can reach it, and you are ready to explore the data structure that makes leaderboards lightning fast: Sorted Sets.

Explore Redis Sorted Sets with redis-cli

Your Redis server is running and ready to accept commands. Before writing any Python code, you'll explore Redis Sorted Sets hands-on using redis-cli. This gives you an intuitive feel for how the data structure behaves before you wrap it in Python.

A Sorted Set is a Redis data structure that keeps every member ordered by a numeric score. When you add a player with a score, Redis automatically places them in the correct rank. No manual sorting required.

In this step, get ready to:
  • Add players and scores to a Sorted Set using ZADD.
  • Query rankings, individual ranks, and scores with ZRANGE, ZREVRANK, and ZSCORE.
  • Update a score with ZINCRBY, observe the rank change, and clean up with DEL.
Add players and scores using ZADD

The ZADD command adds a member to a Sorted Set with a given score. If the member already exists, its score gets updated. Redis keeps everything sorted automatically after each addition.

  • Connect to redis-cli in interactive mode by running this command:
docker exec -it my-redis redis-cli

You should see a prompt that looks like 127.0.0.1:6379>. This means you're inside the Redis interactive shell.

  • Add three players to a leaderboard Sorted Set by running these commands one at a time:
ZADD leaderboard 1500 "alice"
ZADD leaderboard 2300 "bob"
ZADD leaderboard 1800 "charlie"

What does ZADD do?

Each ZADD call takes the key name (leaderboard), a numeric score, and a member name. Redis stores all three members in a single Sorted Set, automatically ranked by score.

Each command returns (integer) 1 to confirm one new member was added.

After running all three commands, each should return (integer) 1. This confirms Redis accepted and stored all three players.

Seeing an error instead of (integer) 1?

Make sure you include the quotes around the player names. Without quotes, Redis may misinterpret names with special characters.

If you see Could not connect to Redis, your Docker container may have stopped. Run docker start my-redis in a separate terminal and try connecting again.

Help me troubleshoot my redis-cli ZADD commands.

Query the leaderboard rankings

With three players stored, you can now query the Sorted Set to see who is on top. Redis provides several commands for reading ranked data from different angles.

  • View the full leaderboard from highest to lowest score by running:
ZRANGE leaderboard 0 -1 REV WITHSCORES

What does this command do?

  • ZRANGE leaderboard 0 -1 fetches all members from index 0 to the last (-1 means the end).
  • REV reverses the order so the highest score appears first.
  • WITHSCORES includes the numeric score alongside each player name.

You should see bob at the top with 2300, followed by charlie with 1800, then alice with 1500. Redis sorted all three automatically when you added them.

  • Look up alice's rank (position from the top) by running:
ZREVRANK leaderboard "alice"

This returns (integer) 2. Ranks are 0-based, so 0 means first place, 1 means second, and 2 means third. Alice is in last place right now.

  • Check bob's exact score by running:
ZSCORE leaderboard "bob"

This returns "2300" as a string representation of bob's score.

Why three different commands?

Each command serves a different use case in a real leaderboard. ZRANGE with REV shows the top-N view ("who's winning?"). ZREVRANK answers "what place am I in?" and ZSCORE answers "what's my exact score?".

Update a score and clean up

In a real game, scores change constantly. ZINCRBY lets you add points to a player's existing score. Redis re-ranks everyone instantly after the update.

  • Give alice 1000 extra points by running:
ZINCRBY leaderboard 1000 "alice"

Redis returns "2500". That's alice's new score (1500 + 1000).

  • Check the leaderboard again to see how the rankings changed:
ZRANGE leaderboard 0 -1 REV WITHSCORES

Alice jumped from last place to first place with 2500 points. Redis re-sorted the entire set the instant her score changed. No rebuild, no re-query needed.

Why is this so fast?

Redis Sorted Sets use a skip list data structure internally. Insertions and rank lookups are O(log N), meaning even with millions of players the operation takes microseconds. This is why Redis is the industry standard for real-time leaderboards.

  • Clean up the test data by deleting the leaderboard key:
DEL leaderboard

This returns (integer) 1, confirming the key was deleted. The leaderboard no longer exists in Redis.

  • Exit redis-cli by typing:
exit

Alice didn't move to the top?

Double check you typed ZINCRBY leaderboard 1000 "alice" with the correct spelling of "alice" (lowercase). Redis member names are case-sensitive.

If you accidentally added a new member like "Alice" (capital A), run ZRANGE leaderboard 0 -1 REV WITHSCORES to see all members and their scores. You can start fresh by running DEL leaderboard and repeating the ZADD commands.

Help me debug my redis-cli Sorted Set commands.

You've now seen firsthand how Redis Sorted Sets maintain order automatically. Next up, you'll wrap these same operations in a Python class so your application can use them programmatically.

Build the Leaderboard Module in Python

In the previous step, you explored Redis Sorted Sets directly in redis-cli. You saw how commands like ZADD and ZRANGE keep data ranked automatically.

Now you will wrap those same commands into a reusable Python class. By the end of this step, you will have a leaderboard.py module that handles adding players, retrieving rankings, updating scores, and looking up a player's neighborhood on the leaderboard.

In this step, get ready to:
  • Create a Leaderboard class with Redis connection setup and basic add/retrieve methods.
  • Add rank lookup, score query, and score update methods.
  • Implement an "around me" view and utility methods to complete the module.
Create the leaderboard file with add and retrieve methods

Every method in your leaderboard class maps to a Sorted Set command you already used in redis-cli. The class simply wraps those commands so you can call them from Python without remembering the raw syntax each time.

  • In VS Code, create a new file called leaderboard.py in your project folder.
  • Paste the following code into leaderboard.py:
import redis


class Leaderboard:
    def __init__(self, name="leaderboard", host="localhost", port=6379):
        self.redis = redis.Redis(host=host, port=port, decode_responses=True)
        self.name = name

    def add_player(self, player_id, score):
        """Add a player with an initial score, or set their score if they already exist."""
        self.redis.zadd(self.name, {player_id: score})

    def get_top(self, n=10):
        """Return the top N players as a list of (player_id, score) tuples."""
        return self.redis.zrange(self.name, 0, n - 1, desc=True, withscores=True)

What does this code do?

  • __init__ connects to Redis on the given host and port. The decode_responses=True flag tells redis-py to return strings instead of raw bytes.
  • self.name stores the Redis key name for this leaderboard. You can create multiple leaderboards by passing different names.
  • add_player uses zadd to insert a player with a score. If the player already exists, their score is overwritten.
  • get_top uses zrange with desc=True and withscores=True to return players ranked from highest to lowest score.
  • Save leaderboard.py.
  • Test the module by running this command in your terminal:
python3 -c "
from leaderboard import Leaderboard
lb = Leaderboard()
lb.add_player('alice', 1500)
lb.add_player('bob', 2300)
print(lb.get_top(5))
"

You should see output like [('bob', 2300.0), ('alice', 1500.0)] showing bob ranked above alice because of his higher score.

Seeing a ModuleNotFoundError?

  • Make sure your virtual environment is activated. You should see (venv) at the start of your terminal prompt.
  • Confirm you are running the test command from the same folder where leaderboard.py is saved.
  • Check that redis is installed by running pip list | grep redis.

Still stuck? Help me fix a ModuleNotFoundError when importing my leaderboard module.

Add rank lookup, score query, and score update methods

With add and retrieve working, you need ways to look up a specific player's rank, check their score, and increment scores during gameplay.

  • In leaderboard.py, add the following three methods below the get_top method:
    def get_rank(self, player_id):
        """Return the 0-based rank of a player (0 = highest score). Returns None if not found."""
        return self.redis.zrevrank(self.name, player_id)

    def get_score(self, player_id):
        """Return the current score of a player. Returns None if not found."""
        return self.redis.zscore(self.name, player_id)

    def update_score(self, player_id, increment):
        """Increment a player's score by the given amount. Returns the new score."""
        return self.redis.zincrby(self.name, increment, player_id)

What do these methods do?

  • get_rank uses zrevrank to return a player's position in the descending ranking. Rank 0 means they are at the top.
  • get_score uses zscore to return the player's current point total as a float.
  • update_score uses zincrby to add points to a player's score atomically. Redis handles the math server-side, so you never have a stale-read problem.
  • Save leaderboard.py.
  • Test the new methods by running:
python3 -c "
from leaderboard import Leaderboard
lb = Leaderboard()
print('Bob rank:', lb.get_rank('bob'))
print('Alice score:', lb.get_score('alice'))
lb.update_score('alice', 1000)
print('Updated top:', lb.get_top(5))
"

You should see bob at rank 0, alice's score at 1500.0, and then the updated top list showing alice at 2500.0 above bob at 2300.0.

Seeing None for rank or score?

  • If get_rank or get_score returns None, the player does not exist in the leaderboard. Re-run the substep 1 test first to add alice and bob.
  • Check that self.name in your class uses the default value "leaderboard" so both tests target the same Redis key.

Need more help? Why are my get_rank and get_score methods returning None?

Implement the "around me" view and utility methods

In a real game, players want to see who is just above and below them on the leaderboard. The get_around_me method uses a player's rank to calculate a window and fetch their neighbors.

  • In leaderboard.py, add the following methods below update_score:
    def get_around_me(self, player_id, n=2):
        """Return n players above and below the given player, plus the player themselves."""
        rank = self.redis.zrevrank(self.name, player_id)
        if rank is None:
            return []
        start = max(0, rank - n)
        end = rank + n
        return self.redis.zrange(self.name, start, end, desc=True, withscores=True)

    def size(self):
        """Return the number of players on the leaderboard."""
        return self.redis.zcard(self.name)

    def reset(self):
        """Delete the leaderboard entirely."""
        self.redis.delete(self.name)

What does get_around_me do?

It first finds the player's rank with zrevrank. Then it calculates a window: start = max(0, rank - n) ensures you never request a negative index. Finally it fetches that slice of the sorted set in descending order.

The size method uses zcard to count how many players are on the board. The reset method deletes the entire key, wiping the leaderboard clean.

  • Save leaderboard.py.
  • Run a comprehensive test to verify all your methods work together:
python3 -c "
from leaderboard import Leaderboard
lb = Leaderboard()
lb.add_player('charlie', 1800)
print('Around alice:', lb.get_around_me('alice'))
print('Size:', lb.size())
lb.reset()
print('After reset:', lb.size())
"

You should see alice's neighbors (including charlie), a size of 3, and then 0 after the reset.

Getting an empty list from get_around_me?

  • An empty list means zrevrank returned None for that player. Make sure you ran the earlier tests so alice still exists in the leaderboard.
  • Verify the indentation of get_around_me is at the same level as your other methods (one level inside the class).

Need help? Help me debug why get_around_me returns an empty list.

✔️ Awesome, I've got everything!

Great. Double check you have saved leaderboard.py with all the methods in place.

ⓧ I'd like to double check the full code

Here is the complete leaderboard.py as it should look at the end of this step:

import redis


class Leaderboard:
    def __init__(self, name="leaderboard", host="localhost", port=6379):
        self.redis = redis.Redis(host=host, port=port, decode_responses=True)
        self.name = name

    def add_player(self, player_id, score):
        """Add a player with an initial score, or set their score if they already exist."""
        self.redis.zadd(self.name, {player_id: score})

    def get_top(self, n=10):
        """Return the top N players as a list of (player_id, score) tuples."""
        return self.redis.zrange(self.name, 0, n - 1, desc=True, withscores=True)

    def get_rank(self, player_id):
        """Return the 0-based rank of a player (0 = highest score). Returns None if not found."""
        return self.redis.zrevrank(self.name, player_id)

    def get_score(self, player_id):
        """Return the current score of a player. Returns None if not found."""
        return self.redis.zscore(self.name, player_id)

    def update_score(self, player_id, increment):
        """Increment a player's score by the given amount. Returns the new score."""
        return self.redis.zincrby(self.name, increment, player_id)

    def get_around_me(self, player_id, n=2):
        """Return n players above and below the given player, plus the player themselves."""
        rank = self.redis.zrevrank(self.name, player_id)
        if rank is None:
            return []
        start = max(0, rank - n)
        end = rank + n
        return self.redis.zrange(self.name, start, end, desc=True, withscores=True)

    def size(self):
        """Return the number of players on the leaderboard."""
        return self.redis.zcard(self.name)

    def reset(self):
        """Delete the leaderboard entirely."""
        self.redis.delete(self.name)

Your leaderboard module is complete. Next up, you will add time-limited expiry to the leaderboard and build a live simulation that updates scores in real time.

Add TTL and Run a Live Simulation

Your leaderboard module can add players, update scores, and query rankings. In a real game, leaderboards often reset on a schedule.

Redis has a built-in mechanism for this called TTL (Time To Live). You can set any key to automatically self-destruct after a number of seconds. In this step, you will add TTL support to your class and then build a simulation script that brings the whole leaderboard to life.

In this step, get ready to:
  • Add expiry methods to your Leaderboard class.
  • Create a runner script that simulates a live game with random score updates.
  • Run the simulation and verify TTL countdown in real time.
Add TTL methods to your Leaderboard class

TTL tells Redis to delete a key after a set number of seconds. This is perfect for leaderboards that reset every hour, every day, or every match.

  • In VS Code, open leaderboard.py.
  • Find the get_around_me method. After it (below the last line of that method), add the following two new methods:
    def set_expiry(self, seconds):
        """Set a TTL on the leaderboard key so it auto-deletes after the given time."""
        self.redis.expire(self.name, seconds)

    def get_ttl(self):
        """Return the remaining TTL in seconds. -1 means no expiry, -2 means key doesn't exist."""
        return self.redis.ttl(self.name)

What does this code do?

These two methods wrap Redis commands that manage key expiration:

  • set_expiry calls the Redis EXPIRE command on your leaderboard key. After the given number of seconds, Redis deletes the entire key and all its data automatically.
  • get_ttl calls the Redis TTL command. It returns how many seconds remain before the key is deleted. A return value of -1 means no expiry is set. A return value of -2 means the key no longer exists.
  • Save leaderboard.py.
  • You won't see a change yet. The next substep builds the script that uses these methods.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

import redis


class Leaderboard:
    def __init__(self, name="leaderboard", host="localhost", port=6379):
        self.redis = redis.Redis(host=host, port=port, decode_responses=True)
        self.name = name

    def add_player(self, player_id, score):
        """Add a player with an initial score, or set their score if they already exist."""
        self.redis.zadd(self.name, {player_id: score})

    def get_top(self, n=10):
        """Return the top N players as a list of (player_id, score) tuples."""
        return self.redis.zrange(self.name, 0, n - 1, desc=True, withscores=True)

    def get_rank(self, player_id):
        """Return the 0-based rank of a player (0 = highest score). Returns None if not found."""
        return self.redis.zrevrank(self.name, player_id)

    def get_score(self, player_id):
        """Return the current score of a player. Returns None if not found."""
        return self.redis.zscore(self.name, player_id)

    def update_score(self, player_id, increment):
        """Increment a player's score by the given amount. Returns the new score."""
        return self.redis.zincrby(self.name, increment, player_id)

    def get_around_me(self, player_id, n=2):
        """Return n players above and below the given player, plus the player themselves."""
        rank = self.redis.zrevrank(self.name, player_id)
        if rank is None:
            return []
        start = max(0, rank - n)
        end = rank + n
        return self.redis.zrange(self.name, start, end, desc=True, withscores=True)

    def set_expiry(self, seconds):
        """Set a TTL on the leaderboard key so it auto-deletes after the given time."""
        self.redis.expire(self.name, seconds)

    def get_ttl(self):
        """Return the remaining TTL in seconds. -1 means no expiry, -2 means key doesn't exist."""
        return self.redis.ttl(self.name)

    def size(self):
        """Return the number of players on the leaderboard."""
        return self.redis.zcard(self.name)

    def reset(self):
        """Delete the leaderboard entirely."""
        self.redis.delete(self.name)
Create the simulation script

Now you need a script that puts your leaderboard through its paces. It will generate fake players, seed them with random scores, run multiple rounds of updates, and display the rankings after each round.

  • Create a new file called main.py in the same folder as leaderboard.py.
  • Add the following imports and player-generation functions:
import random
import time

from leaderboard import Leaderboard


def generate_players(count=20):
    """Generate a list of player names."""
    adjectives = ["Swift", "Bold", "Clever", "Dark", "Epic", "Fierce", "Grand", "Hyper", "Iron", "Jolly"]
    nouns = ["Wolf", "Hawk", "Tiger", "Bear", "Fox", "Eagle", "Shark", "Cobra", "Lynx", "Raven"]
    players = []
    for i in range(count):
        name = f"{random.choice(adjectives)}{random.choice(nouns)}{i}"
        players.append(name)
    return players


def seed_leaderboard(lb, players):
    """Add all players with random initial scores."""
    for player in players:
        score = random.randint(100, 5000)
        lb.add_player(player, score)
    print(f"Seeded {len(players)} players.\n")

What does this code do?

  • generate_players creates 20 unique player names by combining a random adjective, noun, and index number (like SwiftWolf0 or BoldHawk3).
  • seed_leaderboard gives each player a random starting score between 100 and 5000 using your add_player method from earlier.
  • Below the seed_leaderboard function, add the simulation and display functions:
def simulate_round(lb, players):
    """Randomly update scores for a subset of players."""
    active_players = random.sample(players, k=min(5, len(players)))
    for player in active_players:
        increment = random.randint(50, 500)
        new_score = lb.update_score(player, increment)
        print(f"  {player} gained {increment} points -> new score: {new_score:.0f}")


def display_top(lb, n=10):
    """Print the current top N."""
    print(f"\n{'Rank':<6}{'Player':<25}{'Score':<10}")
    print("-" * 41)
    top = lb.get_top(n)
    for i, (player, score) in enumerate(top, start=1):
        print(f"{i:<6}{player:<25}{score:<10.0f}")
    print()


def display_around_me(lb, player_id, n=2):
    """Print the neighborhood around a specific player."""
    neighbors = lb.get_around_me(player_id, n)
    player_rank = lb.get_rank(player_id)
    print(f"Neighborhood around {player_id} (rank #{player_rank + 1}):")
    print(f"{'Rank':<6}{'Player':<25}{'Score':<10}")
    print("-" * 41)
    for player, score in neighbors:
        marker = " <--" if player == player_id else ""
        rank = lb.get_rank(player)
        print(f"{rank + 1:<6}{player:<25}{score:<10.0f}{marker}")
    print()

What does this code do?

  • simulate_round picks 5 random players and boosts each of their scores by a random amount using update_score. This mimics a burst of activity in a live game.
  • display_top prints a formatted table of the top N players with their rank and score.
  • display_around_me shows the "neighborhood" around a specific player. It marks the target player with an arrow so you can see who is directly above and below them.
  • Finally, add the main() function and entry point at the bottom of main.py:
def main():
    lb = Leaderboard(name="leaderboard:game1")
    lb.reset()

    players = generate_players(20)
    seed_leaderboard(lb, players)

    # Set a 5-minute TTL on the leaderboard
    lb.set_expiry(300)
    print(f"Leaderboard TTL: {lb.get_ttl()} seconds\n")

    # Run 3 rounds of score updates
    for round_num in range(1, 4):
        print(f"=== Round {round_num} ===")
        simulate_round(lb, players)
        display_top(lb, n=10)
        time.sleep(1)

    # Show "around me" for a random player
    random_player = random.choice(players)
    display_around_me(lb, random_player, n=2)

    print(f"Total players on leaderboard: {lb.size()}")
    print(f"Remaining TTL: {lb.get_ttl()} seconds")


if __name__ == "__main__":
    main()

What does this code do?

The main() function orchestrates the entire simulation:

  • It creates a leaderboard named leaderboard:game1 and resets it to start fresh.
  • It seeds 20 players, then sets a 300-second (5-minute) TTL so the leaderboard will auto-delete itself.
  • It runs 3 rounds of random score updates, printing the top 10 after each round so you can watch rankings shift.
  • After the rounds, it picks a random player and shows their neighborhood. Then it prints the total player count and how many seconds of TTL remain.
  • Save main.py.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

import random
import time

from leaderboard import Leaderboard


def generate_players(count=20):
    """Generate a list of player names."""
    adjectives = ["Swift", "Bold", "Clever", "Dark", "Epic", "Fierce", "Grand", "Hyper", "Iron", "Jolly"]
    nouns = ["Wolf", "Hawk", "Tiger", "Bear", "Fox", "Eagle", "Shark", "Cobra", "Lynx", "Raven"]
    players = []
    for i in range(count):
        name = f"{random.choice(adjectives)}{random.choice(nouns)}{i}"
        players.append(name)
    return players


def seed_leaderboard(lb, players):
    """Add all players with random initial scores."""
    for player in players:
        score = random.randint(100, 5000)
        lb.add_player(player, score)
    print(f"Seeded {len(players)} players.\n")


def simulate_round(lb, players):
    """Randomly update scores for a subset of players."""
    active_players = random.sample(players, k=min(5, len(players)))
    for player in active_players:
        increment = random.randint(50, 500)
        new_score = lb.update_score(player, increment)
        print(f"  {player} gained {increment} points -> new score: {new_score:.0f}")


def display_top(lb, n=10):
    """Print the current top N."""
    print(f"\n{'Rank':<6}{'Player':<25}{'Score':<10}")
    print("-" * 41)
    top = lb.get_top(n)
    for i, (player, score) in enumerate(top, start=1):
        print(f"{i:<6}{player:<25}{score:<10.0f}")
    print()


def display_around_me(lb, player_id, n=2):
    """Print the neighborhood around a specific player."""
    neighbors = lb.get_around_me(player_id, n)
    player_rank = lb.get_rank(player_id)
    print(f"Neighborhood around {player_id} (rank #{player_rank + 1}):")
    print(f"{'Rank':<6}{'Player':<25}{'Score':<10}")
    print("-" * 41)
    for player, score in neighbors:
        marker = " <--" if player == player_id else ""
        rank = lb.get_rank(player)
        print(f"{rank + 1:<6}{player:<25}{score:<10.0f}{marker}")
    print()


def main():
    lb = Leaderboard(name="leaderboard:game1")
    lb.reset()

    players = generate_players(20)
    seed_leaderboard(lb, players)

    # Set a 5-minute TTL on the leaderboard
    lb.set_expiry(300)
    print(f"Leaderboard TTL: {lb.get_ttl()} seconds\n")

    # Run 3 rounds of score updates
    for round_num in range(1, 4):
        print(f"=== Round {round_num} ===")
        simulate_round(lb, players)
        display_top(lb, n=10)
        time.sleep(1)

    # Show "around me" for a random player
    random_player = random.choice(players)
    display_around_me(lb, random_player, n=2)

    print(f"Total players on leaderboard: {lb.size()}")
    print(f"Remaining TTL: {lb.get_ttl()} seconds")


if __name__ == "__main__":
    main()
Run the simulation and verify TTL

Time to see your leaderboard in action. You will run the simulation and then confirm that Redis is counting down the TTL on your key.

  • Run the simulation by executing this command in your terminal:
python3 main.py

You should see output showing 20 players seeded, a TTL of 300 seconds printed, then 3 rounds of score updates. After each round, the top 10 leaderboard prints with ranks shifting. At the end, you will see the "around me" neighborhood for a random player.

Not seeing any output?

  • Make sure your virtual environment is activated. You should see (venv) at the start of your terminal prompt.
  • Check that your Redis container is still running by running docker ps. You should see my-redis listed.
  • Verify both files are saved and in the same folder.

Help me debug my leaderboard simulation

  • Now verify the TTL countdown by checking the key directly in redis-cli. Run this command:
docker exec -it my-redis redis-cli TTL leaderboard:game1

You should see a number less than 300 (the seconds remaining). If you wait and run the command again, the number will be lower. Once it reaches 0, the key is deleted and subsequent calls return -2.

Why does TTL matter for leaderboards?

In production games, leaderboards often reset on a schedule. A daily leaderboard might have a TTL of 86400 seconds (24 hours). When the key expires, Redis removes it entirely and the next game session starts fresh with a clean slate.

This is simpler than writing cleanup scripts or cron jobs. Redis handles the expiration for you at the server level.

Your leaderboard is fully functional. It handles ranked data in real time and cleans up after itself with TTL. Next up, a secret mission that takes this further with multiple leagues and a cross-league hall of fame.

Secret mission

Multi-League Hall of Fame

What if your game has multiple leagues? In this extension, you'll create separate leaderboards for bronze, silver, and gold leagues, then combine them into a cross-league hall of fame using Redis ZUNIONSTORE.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Docker container named my-redis running on port 6379.
  • Python virtual environment venv with redis-py installed.
  • Project files: leaderboard.py and main.py.

Keep everything running

No action needed. Choose this if you're still actively experimenting with Redis or planning to extend the leaderboard.

  • Your my-redis container stays running and uses minimal memory.
  • Your project files and virtual environment remain in place for future use.

Pause - I'll come back to this later

Stop the Redis container to free up memory. Your data and container configuration are preserved so you can restart later.

  • Stop the Redis container by running:
docker stop my-redis
  • Deactivate your Python virtual environment by running:
deactivate
  • When you're ready to come back, restart the container by running:
docker start my-redis
  • Reactivate your virtual environment by running:
source venv/bin/activate

Delete - I don't want to use this again

Remove all project resources and start fresh. This deletes the container, virtual environment, and project files permanently.

  • Force-remove the Redis container by running:
docker rm -f my-redis
  • Deactivate the virtual environment first (if still active) by running:
deactivate
  • Delete the virtual environment and project files by running:
rm -rf venv leaderboard.py main.py

What about the Redis Docker image?

The redis:latest image still exists on your machine. If you want to remove it too, run:

docker rmi redis:latest

Nice Work!

Nice Work!

Nice work! You just built a real-time gaming leaderboard powered by Redis Sorted Sets. Players get ranked instantly, scores update in real time, and the whole thing resets on a timer.

You've learned how to:

  • Set up a Redis server in Docker and explored Sorted Sets interactively with redis-cli.
  • Built a Python leaderboard module using redis-py that supports adding players, updating scores, top-N rankings, rank lookup, and "around me" queries.
  • Used Redis TTL (EXPIRE) to create time-limited leaderboards that automatically expire and reset.
  • Secret Mission: Built a multi-league system and used ZUNIONSTORE to aggregate rankings into a cross-league hall of fame.

Ready to quiz yourself?