Build a Real-Time Redis Leaderboard

Build a Bash CLI that manages live Redis leaderboard scores and ranks.

Introduction

30 Second Summary

Game scores can change after every round. A manually sorted ranking goes stale as soon as one player earns more points.

In this project, you will build a game leaderboard in macOS Terminal using Redis Open Source sorted sets. A reusable Bash CLI presents those live rankings as numbered output.

What You'll Build

After you award Grace 25 points, she immediately jumps to first place on a numbered board.

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

  • A numbered leaderboard you can print from Terminal, with players ordered from highest score to lowest.
  • Score controls that accept positive or negative points. Redis immediately moves each player to the correct position.
  • A personal rank lookup that reports one player's current place and score. You can also reset and reseed the board without deleting unrelated data.
  • Secret Mission: Add a 30-second sprint leaderboard that shows its remaining time and deletes itself when the round ends.

Are there any prerequisites?

Your Mac needs Redis Open Source 8.10.1, redis-cli, Homebrew, Bash, and macOS Terminal.

Everything runs locally, so you create no paid resources.

Before We Start

Your goal is a Terminal leaderboard that makes changing player scores visible. Redis sorted sets store players with scores while maintaining score order as values change.

Verify Redis and Prepare the Workspace

Your leaderboard will live in Redis Open Source. Every ranking depends on a working local connection.

The redis-cli tool needs to reach the local service that stores your scores. A PONG reply proves that connection before you create any leaderboard data.

In this step, get ready to:
  • Confirm that Redis Open Source reports version 8.10.1.
  • Start the local service through Homebrew.
  • Prepare the ~/redis-leaderboard workspace with a successful connection check.
Match the project version

The project targets Redis Open Source 8.10.1. Matching this version keeps the upcoming leaderboard commands aligned with the project environment.

You can check the installed CLI directly from macOS Terminal. The command reports its version without connecting to the Redis service.

  • Press Cmd+Space to open the macOS search bar.
  • Type Terminal into the search bar.
  • Press Enter to open macOS Terminal.
  • Ask redis-cli to report its installed version by running this command:
redis-cli --version

What Does This Command Do?

  • The redis-cli command prints its installed version before exiting.
  • The reported version confirms whether your local CLI matches the project target.

✔️ I see the required version

Good start. Your local CLI reports 8.10.1.

Your CLI matches the project version. You can move on to the local service.

ⓧ I see an older version

An older Redis installation may behave differently from the project environment. Homebrew can update the existing formula in place.

  • Upgrade the existing Homebrew Redis formula by running this command:
brew upgrade redis

What Does This Command Do?

This command updates the Redis formula managed by Homebrew. It brings the existing installation up to the current project version.

  • Check the installed version again by running this command:
redis-cli --version

Why Check Again?

The second check confirms that the CLI now points to the upgraded Redis installation. This catches cases where an older executable remains active.

You should now see version 8.10.1 reported.

Having Trouble Upgrading Redis?

  • Read the first line in the output that mentions a failure.
  • Retry the upgrade after Homebrew finishes any pending work.

Use this prompt to investigate the output: Help me diagnose why my Homebrew Redis upgrade failed.

Start the Redis service

A Redis service keeps the server available in the background. Homebrew manages that service while you continue using the same Terminal window.

  • Start the local Redis service by running this command:
brew services start redis

What Does This Command Do?

  • Homebrew starts Redis as a local background service.
  • The service keeps running after the command returns control to your prompt.

Homebrew should report that the service started. That's the background service in place.

Redis Service Not Starting?

  • Read the service command output for the first reported failure.
  • Retry the command after Homebrew finishes any pending operation.

Use this prompt to diagnose the service output: Help me work out why my local Redis service did not start.

Create the workspace and test the connection

The ~/redis-leaderboard folder gives every later project file a predictable home. The final connection check runs from inside that workspace.

  • Create the ~/redis-leaderboard workspace and switch into it by running this command:
mkdir -p ~/redis-leaderboard && cd ~/redis-leaderboard

What Does This Command Do?

  • The mkdir command creates the workspace at the named location.
  • The -p option keeps the command safe when the folder already exists.
  • The cd command switches your current Terminal session into that workspace.
  • Make a mental prediction about the reply that would prove the service is healthy.
  • Test the local Redis connection from the workspace by running this command:
redis-cli PING

What Does This Command Prove?

The command sends a small health check through redis-cli. A successful reply proves that the CLI can reach the running local service.

You should see PONG. That's your local Redis foundation ready.

The connection now works from ~/redis-leaderboard. No leaderboard keys or project files have been created.

Not Seeing PONG?

  • Check that the Homebrew service start completed successfully earlier.
  • Run the connection check again after the service reports that it started.

Use this prompt to trace the connection problem: Help me diagnose why redis-cli cannot reach my local Redis service.

Your Redis service is ready. Next, you will seed the first sorted set to watch its order change.

Create the First Live Leaderboard

Your local Redis service is live. The next proof is whether changing a score immediately reshapes the ranking.

A sorted set gives every player a numeric score. You will test both its ordering and the raw terminal view.

In this step, get ready to:
  • Seed four players in a live leaderboard.
  • Inspect the descending order in the raw query output.
  • Raise Grace's score to test automatic reordering.
Seed the first board

Each player is a unique member of the sorted set. The player's score controls their position in the ranking.

  • Clear only the game:leaderboard key by running this command:
redis-cli DEL game:leaderboard

Why clear one named key?

The DEL command removes only game:leaderboard. Any unrelated Redis data remains untouched.

You should see a deleted-key count of zero because this project key did not exist yet.

  • Seed Ada, Linus, Grace, and Margaret by running this command:
redis-cli ZADD game:leaderboard 120 Ada 95 Linus 110 Grace 80 Margaret

How Does ZADD Build the Board?

  • The ZADD command pairs each player with a numeric score.
  • The game:leaderboard key holds the complete board.
  • Each score appears immediately before its player in the command.

You should see a count of four newly added members.

Board Not Seeded?

  • Confirm that the local Redis service from the previous step is still running.
  • Compare each score and player pair with the seed command above.

Ask for help if the seed command still fails: Help me troubleshoot why my Redis sorted set was not seeded.

Inspect the raw ranking

A descending rank query asks Redis for the highest scores first. This gives you the first complete view of the board.

Before you run this, which player do you expect Redis to place first?

  • Query every leaderboard position in descending score order by running this command:
redis-cli ZRANGE game:leaderboard 0 -1 REV WITHSCORES

What Does This Query Show?

  • The REV option returns scores from high to low.
  • The WITHSCORES option includes every player's score.

You will see Ada with 120 first. Grace follows with 110.

The output alternates between player lines and score lines. The ordering works, but the presentation takes effort to scan.

Increase Grace's score

An atomic score increment lets Redis adjust one player's score as a single operation. The sorted set uses the new score for future ranking queries.

  • Increase Grace's score by 25 points by running this command:
redis-cli ZINCRBY game:leaderboard 25 Grace

What Does ZINCRBY Change?

The ZINCRBY command adds 25 to Grace's existing score. Redis stores the resulting 135 as one operation.

That score update worked: Grace now has 135 points.

Unexpected Score for Grace?

  • Reseed the board with the command from the first substep if Grace started from another score.
  • Check that the player name uses the capitalized value Grace.

Ask for help if the score is still unexpected: Help me find why Grace's score did not increase to 135.

Before you check the board, do you expect Grace to stay below Ada or move above her?

  • Query the updated board in descending score order by running this command:
redis-cli ZRANGE game:leaderboard 0 -1 REV WITHSCORES

What Changed?

The same descending query now puts Grace before Ada. Grace has 135 points.

The stored scores determine the order each time you query the sorted set.

That's your first live ranking change: Grace is now number one with 135 points.

Your board now reorders itself as scores change. Next, you will turn the raw command output into a readable leaderboard CLI.

Build the Leaderboard CLI

Your Redis sorted set already reorders the game board as scores change. Grace now leads the board with 135 points.

The raw output still alternates names with scores. A small Bash CLI will turn those repeated commands into a numbered board.

In this step, get ready to:
  • Create a reusable Bash script for the leaderboard.
  • Print a numbered top-five board.
  • Award points through one command.
Create the CLI foundation

The script needs one key name plus clear usage instructions. Its input checks will stop invalid points or limits before Redis receives them.

  • Use your preferred text editor's new-file control to create leaderboard.sh inside ~/redis-leaderboard.
  • Build the script foundation by pasting this code into leaderboard.sh:
set -euo pipefail

BOARD="game:leaderboard"

usage() {
  cat <<'USAGE'
Usage:
  bash leaderboard.sh seed
  bash leaderboard.sh top [limit]
  bash leaderboard.sh score <player> <points>
USAGE
}

require_number() {
  local value="$1"
  if ! [[ "$value" =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then
    echo "Points must be a whole number or decimal."
    exit 1
  fi
}

require_positive_integer() {
  local value="$1"
  if ! [[ "$value" =~ ^[1-9][0-9]*$ ]]; then
    echo "Limit must be a positive integer."
    exit 1
  fi
}

How does the foundation work?

  • The opening settings make the script stop when a command fails or an expected value is missing.
  • The BOARD constant keeps every operation scoped to game:leaderboard.
  • The usage() function documents the three supported command shapes.
  • The validation functions accept numeric points plus positive whole-number limits.

Why strict mode matters

A command-line tool can receive missing arguments or failed Redis commands. Strict execution stops the script close to the cause instead of continuing with unreliable values.

  • Save leaderboard.sh.
  • Confirm your editor shows leaderboard.sh inside ~/redis-leaderboard.

File missing from the folder?

  • Check that your editor saved the file inside ~/redis-leaderboard.
  • Check that the filename is exactly leaderboard.sh.

Help me confirm where my Bash file was saved.

  • Insert the seeding function below require_positive_integer() by pasting this code:
seed_board() {
  redis-cli DEL "$BOARD" >/dev/null
  redis-cli ZADD "$BOARD" 120 "Ada" 95 "Linus" 110 "Grace" 80 "Margaret" >/dev/null
  echo "Seeded $BOARD with four players."
}

What does the seed function do?

  • The named DEL operation clears only the project leaderboard.
  • The ZADD operation recreates the board with four known starting scores.
  • The final message confirms that the board is ready.
  • Save leaderboard.sh.
  • Confirm seed_board() appears directly below require_positive_integer().

Seed function in the wrong place?

  • Move the complete seed_board() function below the closing brace of require_positive_integer().
  • Check that every opening brace has a matching closing brace.

Help me check the placement of my seed function.

  • Add the temporary command dispatcher at the bottom of leaderboard.sh by pasting this code:
command="${1:-}"

case "$command" in
  seed)
    [[ $# -eq 1 ]] || { usage; exit 1; }
    seed_board
    ;;
  top)
    [[ $# -le 2 ]] || { usage; exit 1; }
    show_top "$BOARD" "${2:-5}"
    ;;
  score)
    [[ $# -eq 3 ]] || { usage; exit 1; }
    add_score "$2" "$3"
    ;;
  *)
    usage
    exit 1
    ;;
esac

How does the dispatcher work?

The dispatcher reads the first argument as a command. The seed branch is ready now.

The top and score branches connect to the functions you add in the next two chunks.

  • Save leaderboard.sh.
  • Reseed only the project leaderboard by running this command:
bash leaderboard.sh seed

What does this command prove?

The command reaches the seed branch and runs seed_board(). You should see Seeded game:leaderboard with four players..

Good progress. Your first CLI command now resets the board without touching unrelated Redis data.

Seed command not working?

  • Check that your terminal is still inside ~/redis-leaderboard.
  • Check that seed_board() appears above the command dispatcher.
  • Check each brace plus each closing ;; in the dispatcher.

Help me debug the seed command.

Format a numbered top five

Redis returns each player on one line followed by the score on the next line. The show_top() function turns those pairs into labeled rows.

  • Insert show_top() below seed_board() by pasting this code:
show_top() {
  local board="$1"
  local limit="$2"
  local data
  local rank=1
  local player
  local score

  require_positive_integer "$limit"
  data="$(redis-cli --raw ZRANGE "$board" 0 "$((limit - 1))" REV WITHSCORES)"

  if [[ -z "$data" ]]; then
    echo "No scores yet."
    return
  fi

  printf "%-5s %-12s %s\n" "Rank" "Player" "Score"
  while IFS= read -r player && IFS= read -r score; do
    printf "%-5s %-12s %s\n" "#$rank" "$player" "$score"
    rank=$((rank + 1))
  done < <(printf '%s\n' "$data")
}

How does the table take shape?

  • The function checks that the requested limit is a positive integer.
  • The ZRANGE query asks Redis for the highest scores first.
  • The loop reads one player plus one score for each numbered row.
  • The formatted output gives the result headers plus aligned columns.

How do raw pairs become rows?

The --raw output places each member plus score on separate lines. Two reads inside each loop cycle collect one complete leaderboard entry.

Bash owns the presentation while Redis continues to own the ranking.

  • Save leaderboard.sh.
  • Print the default top-five leaderboard by running this command:
bash leaderboard.sh top

What does the top command prove?

The dispatcher sends the default limit of 5 to show_top(). Redis supplies the descending order.

You should see Ada at #1 with 120 points. Grace follows at #2 with 110 points because the seed command restored the starting scores.

Leaderboard missing its rows?

  • Check that show_top() appears above the command dispatcher.
  • Check that the top branch calls show_top with BOARD.
  • Run the seed command from the previous substep again if the board has no scores.

Help me debug the numbered leaderboard output.

Award points through the CLI

A leaderboard becomes useful when one command can update a score and reveal the new order. The add_score() function validates the points before applying an atomic increment.

  • Insert add_score() below show_top() by pasting this code:
add_score() {
  local player="$1"
  local points="$2"
  local new_score

  require_number "$points"
  new_score="$(redis-cli --raw ZINCRBY "$BOARD" "$points" "$player")"
  echo "$player now has $new_score points."
  show_top "$BOARD" 5
}

What does the score function do?

  • The function captures the player plus point change from the command arguments.
  • The numeric check allows positive or negative score changes.
  • The ZINCRBY operation updates the score atomically.
  • The function prints the new score before showing the reordered board.
  • Save leaderboard.sh.
  • Confirm add_score() appears immediately above the command dispatcher.

Score function in the wrong place?

  • Move the complete add_score() function above command="${1:-}".
  • Check that the function closes before the dispatcher begins.

Help me check the score function placement.

✔️ Awesome, I've got everything!

Your script now contains the foundation plus all three command paths for this step.

ⓧ I'd like to double check the full code

set -euo pipefail

BOARD="game:leaderboard"

usage() {
  cat <<'USAGE'
Usage:
  bash leaderboard.sh seed
  bash leaderboard.sh top [limit]
  bash leaderboard.sh score <player> <points>
USAGE
}

require_number() {
  local value="$1"
  if ! [[ "$value" =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then
    echo "Points must be a whole number or decimal."
    exit 1
  fi
}

require_positive_integer() {
  local value="$1"
  if ! [[ "$value" =~ ^[1-9][0-9]*$ ]]; then
    echo "Limit must be a positive integer."
    exit 1
  fi
}

seed_board() {
  redis-cli DEL "$BOARD" >/dev/null
  redis-cli ZADD "$BOARD" 120 "Ada" 95 "Linus" 110 "Grace" 80 "Margaret" >/dev/null
  echo "Seeded $BOARD with four players."
}

show_top() {
  local board="$1"
  local limit="$2"
  local data
  local rank=1
  local player
  local score

  require_positive_integer "$limit"
  data="$(redis-cli --raw ZRANGE "$board" 0 "$((limit - 1))" REV WITHSCORES)"

  if [[ -z "$data" ]]; then
    echo "No scores yet."
    return
  fi

  printf "%-5s %-12s %s\n" "Rank" "Player" "Score"
  while IFS= read -r player && IFS= read -r score; do
    printf "%-5s %-12s %s\n" "#$rank" "$player" "$score"
    rank=$((rank + 1))
  done < <(printf '%s\n' "$data")
}

add_score() {
  local player="$1"
  local points="$2"
  local new_score

  require_number "$points"
  new_score="$(redis-cli --raw ZINCRBY "$BOARD" "$points" "$player")"
  echo "$player now has $new_score points."
  show_top "$BOARD" 5
}

command="${1:-}"

case "$command" in
  seed)
    [[ $# -eq 1 ]] || { usage; exit 1; }
    seed_board
    ;;
  top)
    [[ $# -le 2 ]] || { usage; exit 1; }
    show_top "$BOARD" "${2:-5}"
    ;;
  score)
    [[ $# -eq 3 ]] || { usage; exit 1; }
    add_score "$2" "$3"
    ;;
  *)
    usage
    exit 1
    ;;
esac

What should match?

Use this full-file reference to compare function order plus dispatcher branches. Your saved leaderboard.sh should match it exactly.

Before you run these commands, consider where Grace will appear after receiving 25 points.

  • Award Grace 25 points before printing the board again by running these commands:
bash leaderboard.sh score Grace 25
bash leaderboard.sh top

What do these commands prove?

The first command increments Grace from 110 to 135. The score function immediately prints the reordered top five.

The second command queries the saved Redis data again. This confirms that the updated score persists outside the script process.

You should see Grace now has 135 points. followed by a numbered board with Grace at #1.

That is the main interface working. Your leaderboard now updates plus presents its ranking through reusable commands.

Grace not at number one?

  • Run the seed command from the first substep before awarding the 25 points again.
  • Check that add_score() passes BOARD plus the supplied points to ZINCRBY.
  • Check that the score dispatcher branch passes its second plus third arguments to add_score().

Help me debug why Grace is not number one.

Your CLI now turns Redis score updates into a presentation-ready leaderboard. Next, you will add personal rank lookups plus safer controls.

Add Rank Lookups and Safe Controls

Your Bash CLI now turns raw Redis replies into a numbered leaderboard. A top list still cannot tell one player exactly where they stand.

Unchecked arguments can send confusing values to Redis.

A rank lookup closes the first gap. Scoped controls protect the persistent board.

In this step, get ready to:
  • Add a player-specific rank lookup.
  • Add a reset command scoped to game:leaderboard.
  • Prove invalid score input leaves the board unchanged.
Add rank and reset functions

Redis stores descending ranks from zero. The CLI adds one before presenting the result.

  • In leaderboard.sh, find the closing brace of add_score().
  • Paste the following functions below it:
show_rank() {
  local player="$1"
  local rank
  local score

  rank="$(redis-cli --raw ZREVRANK "$BOARD" "$player")"
  if [[ -z "$rank" ]]; then
    echo "$player is not on the leaderboard."
    return 1
  fi

  score="$(redis-cli --raw ZSCORE "$BOARD" "$player")"
  echo "$player is #$((rank + 1)) with $score points."
}

reset_board() {
  redis-cli DEL "$BOARD" >/dev/null
  echo "Reset $BOARD."
}

What Do These Functions Do?

  • The show_rank() function uses ZREVRANK to get a zero-based position in descending score order.
  • The ZSCORE command supplies the player's current score.
  • The rank + 1 calculation produces a human-friendly position.
  • The reset_board() function calls DEL with $BOARD. Unrelated Redis keys remain untouched.
  • Save leaderboard.sh.
  • Confirm the existing top command still runs by running:
bash leaderboard.sh top

What Should I See?

You'll see Grace at #1 with 135 points.

This result confirms that the new function definitions did not break the existing command path.

Seeing a Syntax Error?

  • Check the closing brace at the end of add_score().
  • Check that both new functions have their own closing brace.

Help me fix the new Bash functions in leaderboard.sh.

Connect the new commands

The usage guide shows which commands the CLI accepts. The dispatcher maps each command word to its function.

  • In leaderboard.sh, find the entire usage() function.
  • Replace that function with the following version:
usage() {
  cat <<'USAGE'
Usage:
  bash leaderboard.sh seed
  bash leaderboard.sh top [limit]
  bash leaderboard.sh score <player> <points>
  bash leaderboard.sh rank <player>
  bash leaderboard.sh reset
USAGE
}

What Does This Change Do?

The usage guide now documents the rank command.

It also documents the scoped reset command.

  • Save leaderboard.sh.
  • Confirm the existing command path still works by running:
bash leaderboard.sh top

What Should I See?

You'll see the same four-player leaderboard with Grace at the top.

The unchanged output confirms that the updated usage function is valid Bash.

Did the Top Command Stop?

  • Check that the opening marker is exactly USAGE.
  • Check that the closing USAGE marker starts at the beginning of its line.

Help me repair the usage function.

  • Find the line that sets command near the bottom of leaderboard.sh.
  • Select everything from that line through the final esac.
  • Replace the selected dispatcher with the following code:
command="${1:-}"

case "$command" in
  seed)
    [[ $# -eq 1 ]] || { usage; exit 1; }
    seed_board
    ;;
  top)
    [[ $# -le 2 ]] || { usage; exit 1; }
    show_top "$BOARD" "${2:-5}"
    ;;
  score)
    [[ $# -eq 3 ]] || { usage; exit 1; }
    add_score "$2" "$3"
    ;;
  rank)
    [[ $# -eq 2 ]] || { usage; exit 1; }
    show_rank "$2"
    ;;
  reset)
    [[ $# -eq 1 ]] || { usage; exit 1; }
    reset_board
    ;;
  *)
    usage
    exit 1
    ;;
esac

How Does the Dispatcher Protect the CLI?

  • The rank branch sends one player name to show_rank().
  • The reset branch calls reset_board() without extra arguments.
  • Each $# check rejects the wrong number of arguments.
  • The top path still reaches require_positive_integer() through show_top().
  • Save leaderboard.sh.

Before you run this check, where do you expect Grace to rank?

  • Test the new rank path by running:
bash leaderboard.sh rank Grace

What Should I See?

You'll see Grace is #1 with 135 points.

The CLI has converted Redis's zero-based result into the position a player expects.

Rank Command Showing Usage?

  • Check that the dispatcher contains the rank) branch.
  • Check that the branch calls show_rank "$2".

Help me connect the rank command.

✔️ Awesome, I've got everything!

Great. Double-check that you saved leaderboard.sh.

ⓧ I'd like to double check the full code

set -euo pipefail

BOARD="game:leaderboard"

usage() {
  cat <<'USAGE'
Usage:
  bash leaderboard.sh seed
  bash leaderboard.sh top [limit]
  bash leaderboard.sh score <player> <points>
  bash leaderboard.sh rank <player>
  bash leaderboard.sh reset
USAGE
}

require_number() {
  local value="$1"
  if ! [[ "$value" =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then
    echo "Points must be a whole number or decimal."
    exit 1
  fi
}

require_positive_integer() {
  local value="$1"
  if ! [[ "$value" =~ ^[1-9][0-9]*$ ]]; then
    echo "Limit must be a positive integer."
    exit 1
  fi
}

seed_board() {
  redis-cli DEL "$BOARD" >/dev/null
  redis-cli ZADD "$BOARD" 120 "Ada" 95 "Linus" 110 "Grace" 80 "Margaret" >/dev/null
  echo "Seeded $BOARD with four players."
}

show_top() {
  local board="$1"
  local limit="$2"
  local data
  local rank=1
  local player
  local score

  require_positive_integer "$limit"
  data="$(redis-cli --raw ZRANGE "$board" 0 "$((limit - 1))" REV WITHSCORES)"

  if [[ -z "$data" ]]; then
    echo "No scores yet."
    return
  fi

  printf "%-5s %-12s %s\n" "Rank" "Player" "Score"
  while IFS= read -r player && IFS= read -r score; do
    printf "%-5s %-12s %s\n" "#$rank" "$player" "$score"
    rank=$((rank + 1))
  done < <(printf '%s\n' "$data")
}

add_score() {
  local player="$1"
  local points="$2"
  local new_score

  require_number "$points"
  new_score="$(redis-cli --raw ZINCRBY "$BOARD" "$points" "$player")"
  echo "$player now has $new_score points."
  show_top "$BOARD" 5
}

show_rank() {
  local player="$1"
  local rank
  local score

  rank="$(redis-cli --raw ZREVRANK "$BOARD" "$player")"
  if [[ -z "$rank" ]]; then
    echo "$player is not on the leaderboard."
    return 1
  fi

  score="$(redis-cli --raw ZSCORE "$BOARD" "$player")"
  echo "$player is #$((rank + 1)) with $score points."
}

reset_board() {
  redis-cli DEL "$BOARD" >/dev/null
  echo "Reset $BOARD."
}

command="${1:-}"

case "$command" in
  seed)
    [[ $# -eq 1 ]] || { usage; exit 1; }
    seed_board
    ;;
  top)
    [[ $# -le 2 ]] || { usage; exit 1; }
    show_top "$BOARD" "${2:-5}"
    ;;
  score)
    [[ $# -eq 3 ]] || { usage; exit 1; }
    add_score "$2" "$3"
    ;;
  rank)
    [[ $# -eq 2 ]] || { usage; exit 1; }
    show_rank "$2"
    ;;
  reset)
    [[ $# -eq 1 ]] || { usage; exit 1; }
    reset_board
    ;;
  *)
    usage
    exit 1
    ;;
esac
Prove invalid input is safe

The guard in require_number() runs before ZINCRBY. A rejected value cannot reach Redis.

Before you run this test, do you expect Grace's score to change?

  • Test the score guard by running:
bash leaderboard.sh score Grace nope

The Rejection Is Expected

You'll see Points must be a whole number or decimal.

The script exits before ZINCRBY can change Grace's score.

Before you inspect the board, which score do you expect to see beside Grace?

  • Confirm the leaderboard remains unchanged by running:
bash leaderboard.sh top

What Should I See?

You'll still see Grace at #1 with 135 points.

Ada remains at 120. Linus remains at 95. Margaret remains at 80.

One last prediction: which rank should the personal lookup report for Grace?

  • Verify the rank lookup by running:
bash leaderboard.sh rank Grace

What Should I See?

The terminal prints Grace is #1 with 135 points.

Great work. Your CLI now reports a player's rank without risking the stored scores.

Seeing the Wrong Rank or Score?

  • Check that show_rank() uses ZREVRANK with $BOARD.
  • Check that the displayed rank uses rank + 1.
  • Run the top command again to confirm Grace still has 135 points.

Help me debug the final leaderboard checks.

Secret mission

Build a 30-Second Sprint Board

Your main leaderboard keeps scores until you reset it. Extend the CLI with a separate 30-second sprint board that reports its remaining time and automatically deletes itself when the round ends.

Clean Up Your Resources

Clean Up Your Resources

This local project has no ongoing costs. Choose whether to keep it running, pause it, or delete it.

Resources you used:

  • The local Redis service that Homebrew manages.
  • The persistent game:leaderboard sorted set.
  • The optional game:leaderboard:sprint sorted set when a sprint is active.
  • The ~/redis-leaderboard directory containing the leaderboard.sh Bash CLI.

Keep everything running

No action needed. Choose this if you are still using the leaderboard or extending the CLI.

  • Leave the Homebrew Redis service active while you continue using the CLI.
  • Keep game:leaderboard available with the latest player scores.
  • Keep ~/redis-leaderboard so you can continue editing leaderboard.sh.

Pause - I'll come back to this later

Shut down the local service to free its memory. Your project files remain available for later.

  • Stop the Redis service by running this command:
brew services stop redis

What does this command do?

Homebrew stops the local Redis service. Your ~/redis-leaderboard files stay on your Mac.

  • Keep the ~/redis-leaderboard directory so the CLI is ready when you return.

Delete - I don't want to use this again

Deleting the project is permanent. The Redis command targets only your two named project keys.

  • Delete both project keys by running this command:
redis-cli DEL game:leaderboard game:leaderboard:sprint

What does this command remove?

The DEL command removes only game:leaderboard and game:leaderboard:sprint. An expired sprint key is safely ignored.

The returned number shows how many of those keys existed when the command ran.

  • Stop the local Redis service by running this command:
brew services stop redis

What happens to Redis?

Homebrew stops the running Redis service. Redis stays installed for future local projects.

  • Remove the local project directory safely by running these commands:
cd ~
rm -rf ~/redis-leaderboard

Why leave the folder first?

Your terminal currently sits inside ~/redis-leaderboard. The first command moves to your home directory before the second permanently removes the project folder.

Cleanup complete. Your project keys and local folder are gone.

Nice Work!

Nice Work!

You did it! You built a live terminal leaderboard with a Redis sorted set.

Your leaderboard now updates rankings as scores change. Your sprint board also removes itself when the round expires.

You learned how to:

  • Model changing rankings with scores that Redis keeps ordered automatically.
  • Use ZINCRBY to update player scores atomically. Query descending rankings with ZRANGE plus REV.
  • Wrap Redis operations in a reusable Bash CLI with numbered output. Add argument validation for safer score updates.
  • Secret Mission: Built a 30-second sprint leaderboard that reports its remaining TTL. Confirmed Redis deletes the temporary board after expiry.

Ready to quiz yourself?