A Practical Walkthrough of Redis
From data types to building a game leaderboard
What is Redis?
Redis is an in-memory key value datastore used for fast data access. Unlike traditional datastores, it stores the working data in RAM that is faster than SSD or HDD.
Redis is commonly used for caching, session store, rate limiting, counters, real-time dashboards.
Redis support multiple data structure including Strings, Hashes, Lists, Sets, Sorted Sets, etc. which we will discuss in details in next sections. Redis also supports other useful types such as Streams, HyperLogLog, Bitmap and Geospatial Indexes.
Why Redis?
Redis is widely used for caching solution to improve application performance. By storing frequently accessed data in memory, it helps reduce database load, improve throughput and reduce the response latency of your application. This is especially useful when you need to serve frequently requested data.
Imagine you have an bakery website that displays a list of items available for purchase. Instead of querying the database every time a user visits your website, you can cache the item details in Redis and serve subsequent requests directly from the cache.
This eliminates unnecessary database queries, speeds up response times, and allows your application to handle more requests efficiently. When the item details change, you can update or invalidate the cached data to ensure users receive the latest information.
A brief on how Redis works internally
How does Redis handle thousands of concurrent client connections without creating a separate thread for each one?
Redis uses I/O multiplexing along with event-driven architecture. Instead of dedicating a thread to every client, Redis uses an OS-level I/O multiplexing mechanism (epoll on Linux) to efficiently monitor multiple client connections.
- When a client connects, Redis registers its socket with
epoll. The OS monitors these sockets and identifies which are ready for reading or writing. - Redis runs an event loop that waits for the OS to report ready sockets.
- Redis then reads the incoming data, parses the commands, and executes them sequentially on its main command-execution thread. Once the commands are processed, Redis sends the responses back to the respective clients.
How Redis Handles Client Requests?Redis Data Type
The most commonly used data types in Redis are:
- String
Stores text, numbers, binary data. Generally used for caching, session tokens, counters, etc.
SET user:101:name “John”
GET user:101:name
INCR page:views- Hashes
Stores attribute pairs under a key. Generally used to store object attributes like user profiles, item details, etc.
HSET product:101 name "Belgian Chocolate Cake" price 30
HGET product:101 name
HGETALL product:101- Lists
Stores ordered sequence of data. Generally used for recent activity tracking.
LPUSH user:101:activity "login"
LPUSH user:101:activity "purchase"
LRANGE user:101:activity 0 -1- Sets
Store unique elements. Generally used to store unique visitors, membership checks.
SADD article:42:likes user:101 user:102
SISMEMBER article:42:likes user:101
SCARD article:42:likes- Sorted Sets
Stores unique elements ordered by a score. Generally used to store rankings, leaderships, etc.
ZADD game:weekly 1200 player:101
ZADD game:weekly 950 player:102
ZREVRANGE game:weekly 0 9 WITHSCORESSorted Set
A sorted set is a collection of unique elements where each element has a floating-point score. Redis automatically keeps elements ordered by score and hence this data type is the go-to choice for implementing leaderboards, rankings without the application level sorting.
Sorted Set is implemented using Hash tables and Skip-list.
- Hash tables maps each element to its score giving O(1) access time.
- Skip list maintains elements ordered by score.
A skip-list is a layer linked list structure. The bottom layer contains all the elements in sorted order; higher levels contains subset of elements letting Redis skip over large portions of the list while searching.
Weekly Game Leaderboard
Consider you’re building a game which conducts weekly contests where millions of players compete to earn the highest score. Every time a player completes some level, their score can change, potentially moving them up or down the leaderboard.
Get Abhishek’s stories in your inbox
Join Medium for free to get updates from this writer.
You need a leaderboard which supports 3 features:
- Top players: Display the top N players in the weekly contest.
- Player ranking: Get the players current rank without fetching all the users.
- Nearby players: Show a few players ranked above and below the player.
The challenge is to make all the 3 operations fast even with millions of players. The score may change throughout the week as players keep playing and the leaderboard needs to reflect these changes efficiently without repeatedly fetching and sorting every player’s score in the application.
Additionally the leaderboard resets every week for a new contest while the previous contest results remain accessbile.
Let’s see how Redis sorted set can be used to retrieve the player ranking efficiently. Sorted sets can be used to add/update the player score, retrieve top players, find a player’s rank and get list of nearby players.
Add players’ score to a Sorted Set:
ZADD leaderboard:weekly:2026-W42 10 player_1 — Adds player_1 with score 10
ZADD leaderboard:weekly:2026-W42 5 player_2 — Adds player_2 with score 5
There is no need to explicitly create a sorted set before starting to add elements. Redis automatically creates the data structure when the first element is added using
ZADDcommand. If a player already exists in the Sorted Set,ZADDupdates their score instead of creating a duplicate member.
Check whether a player exists:
ZSCORE leaderboard:weekly:2026-W42 player_1
ZSCORE returns the player's current score. If the player doesn't exist, Redis returns a null result, which can also be used to check membership.
Update player’s score:
ZINCRBY leaderboard:weekly:2026-W42 5 player_1
This increments player_1's score by 5. This is an atomic transaction.
Get the top 3 players:
ZRANGE leaderboard:weekly:2026-W42 0 2 REV WITHSCORES
The REV option returns players in descending order, while WITHSCORES includes their scores in the result.
Get a player’s rank:
ZREVRANK leaderboard:weekly:2026-W42 player_1
This returns the player’s rank in descending order. Redis uses zero-based indexing, so rank 0 represents first place.
Get a player’s score:
ZSCORE leaderboard:weekly:2026-W42 player_1
Same as checking player’s existence in the set
Remove a player:
ZREM leaderboard:weekly:2026-W42 player_1
Get nearby players:
ZREVRANK leaderboard:weekly:2026-W42 player_1
ZRANGE leaderboard:weekly:2026-W42 48 52 REV WITHSCORES
When a new contest starts, the application starts writing to a new key. The previous week’s leaderboard remains separate and can still be accessed until its deleted or expired.
Lua Scripting: Atomic leaderboard updates
So far, we have seen how Redis Sorted Sets make leaderboard operations efficient. However, there is a subtle problem.
When finding the nearby players, we first retrieve the players’ rank and then fetch the nearby players.
But imagine a scenario when before executing the second command, some players’s score gets updated which changes the rank of current player. So, when the second command executes, it returns a different result.
This happens because each Redis command is atomic individually, but the two commands together are not an atomic operation. Another client can modify the leaderboard between them.
Redis supports Lua scripting, which lets us execute multiple Redis commands as a single atomic operation. While a script is running, Redis does not interleave commands from other clients.
— KEYS[1]: Leaderboard key
— ARGV[1]: Player ID
local leaderboard = KEYS[1]
local player = ARGV[1]
local neighbors = 3
- Find the player's zero-based rank (highest score first)
local rank = redis.call('ZREVRANK', leaderboard, player)
- Return nil if the player is not on the leaderboard
if not rank then
return nil
end
- Calculate the range around the player
local start_rank = math.max(0, rank - neighbors)
local end_rank = rank + neighbors
- Fetch nearby players and their scores
return redis.call(
'ZRANGE',
leaderboard,
start_rank,
end_rank,
'REV',
'WITHSCORES'
)By combining the commands in the above lua script, we ensure that the player’s rank and nearby players are fetched atomically. The same approach could be used when multiple commands are to be executed as a single atomic operation.
Conclusion
Redis is more than a key-value store. Its data structures let us model common application requirements which make the implemention both simple, efficient and improves the performance of the application.









