Redis data structures
Learn which Redis data structure to use for timelines, counters, leaderboards, and sessions, with the specific commands and trade-offs each one makes.
Why Redis data structures matter in interviews
The caching.mdx article covers cache patterns (cache-aside, write-through) and eviction policies. What it does not cover is which Redis structure to use for a given problem, and the commands that structure exposes.
In system design interviews, reaching for "Redis" is the right instinct. Knowing which data structure inside Redis is the sign of real depth. Using a List where a Sorted Set is needed, or a String where a Hash would fit better, is the kind of detail that distinguishes a candidate who has read about Redis from one who has used it under load.
This article covers the five structures you need to know and the canonical use cases for each.
Strings
The simplest structure. A key maps to a single value: a byte string, integer, or float.
Commands:
SET key value [EX seconds]β store a value, optionally with a TTLGET keyβ retrieve the valueINCR key/INCRBY key deltaβ atomic increment (returns new value)SETNX key valueβ set only if the key does not exist (used for distributed locks)MGET key1 key2 ...β fetch up to N keys in one round-trip
When to use:
- Atomic counters.
INCR rate_limit:{user_id}is the standard rate limiter primitive. The increment and read are atomic: no two clients can increment simultaneously and both read the same old value. - Session tokens.
SET session:{token} {user_id_json} EX 3600stores a session with a 1-hour TTL. One key, one lookup, one eviction via TTL. - Feature flags.
SET feature:{name} 1/GET feature:{name}. Simple, instant toggles. - Caching serialized objects.
SET tweet:{tweet_id} {serialized_tweet} EX 86400. This is the tweet content cache from the Twitter design.
What it does not do well: Anything where you need partial updates. Updating one field in a cached user object requires deserializing, modifying, and re-serializing the entire value. Use a Hash for objects with many independently updated fields.
Lists
An ordered sequence of strings. Insertion is O(1) from either end. Access by index is O(N). Think of it as a doubly-linked list.
Commands:
RPUSH key value [value ...]β append to the right (tail)LPUSH key value [value ...]β prepend to the left (head)LRANGE key start stopβ return elements from index start to stop (0-indexed; -1 = last)LTRIM key start stopβ remove everything outside [start, stop]; destructiveLLEN keyβ number of elementsLREM key count valueβ remove the firstcountoccurrences of a value
When to use:
- Recent-N caches.
LPUSH recent_activity:{user_id} {event_id}thenLTRIM recent_activity:{user_id} 0 99keeps the last 100 events. Combine:LPUSH + LTRIMin a pipeline; the list self-maintains its size cap. - Simple queues.
RPUSH queue:{name} taskto enqueue;LPOP queue:{name}to dequeue. Simple and fast, though message queues (Kafka, SQS) are preferable for durable distributed queues. - Fan-out feed (simple version). Before adding scores, Twitter's early architecture used
LPUSH timeline:{user_id} tweet_id+LTRIMto maintain a 800-entry feed. This works but cannot do time-range lookups within the list without scanning all items.
What it does not do well: Finding an element by value in the middle of a long list (O(N)). Sorting or ranking items by a score. Use a Sorted Set if you need ordered access by a computed score rather than insertion order.
Sorted Sets
A set of unique members each with a floating-point score. Members are stored sorted by score. All operations maintain the sorted order.
Commands:
ZADD key score member [score member ...]β add or update members. If member exists, updates its score (useful withGTflag to only update on higher scores).ZREVRANGE key start stop [WITHSCORES]β return members from highest to lowest score, by rankZRANGEBYSCORE key min max [LIMIT offset count]β return members with score within [min, max]ZREVRANGEBYSCORE key max minβ reverse: highest score first within rangeZREMRANGEBYRANK key start stopβ remove members by rank (used to trim a sorted set to N entries)ZCARD keyβ count of membersZSCORE key memberβ get a member's scoreZRANK key member/ZREVRANK key memberβ get the zero-indexed rank of a member (forward or reverse)
Time complexity: Most operations are O(log N) due to the underlying skip list structure.
When to use:
- Home timeline cache.
ZADD home_timeline:{user_id} {unix_timestamp} {tweet_id}stores tweet_ids scored by creation time.ZREVRANGE home_timeline:{user_id} 0 19returns the 20 most recent tweet_ids in one command.ZREMRANGEBYRANK home_timeline:{user_id} 0 -801trims to the most recent 800, discarding older entries. This is the core data structure powering Twitter's pre-computed feed. - Leaderboards.
ZADD leaderboard:{game_id} {score} {player_id}withGTflag ensures scores only update upward.ZREVRANGE leaderboard:{game_id} 0 9 WITHSCORESreturns the top 10.ZREVRANK leaderboard:{game_id} {player_id}returns a player's current rank in O(log N). - Rate limiters with time windows. Store request timestamps as members with the timestamp as the score.
ZREMRANGEBYSCORE key 0 {window_start}evicts old requests;ZCARD keycounts requests in the current window. This is the sliding window rate limiter pattern. - Scheduled delayed jobs. Store job IDs scored by their intended execution timestamp. A worker polls
ZRANGEBYSCORE jobs 0 {now} LIMIT 0 10to find jobs ready to run.
What it does not do well: Member values must be unique within a sorted set. If two tweets are scored identically (same millisecond timestamp) and you use the timestamp as both score and member, only one survives. Always use the unique identifier (tweet_id) as the member, and the sort key (timestamp) as the score.
Hashes
A map of field-value pairs stored under a single key. Think of it as an object or row: one Redis key, many fields.
Commands:
HSET key field value [field value ...]β set one or more fieldsHGET key fieldβ get one fieldHMGET key field1 field2 ...β get multiple fields in one round-tripHGETALL keyβ get all fields and values (avoid on large hashes)HINCRBY key field deltaβ atomic increment of an integer fieldHDEL key field [field ...]β delete fieldsHEXISTS key fieldβ check field existence without fetching value
Continue Reading with Premium
Unlock this article and every other in-depth system design guide on the platform with NotesFromSDE Premium.