arrow_backBACK TO REDIS — THE IN-MEMORY DATA-STRUCTURE STORE, END TO END
Lesson 02Redis — The In-Memory Data-Structure Store, End to End4 min read

The Data Structures

October 30, 2026

TL;DR

Redis stores six core structures, each ~100k ops/sec (measured). STRING: a value (text/number/bytes/JSON) — GET/SET, INCR for counters; use for cache, counters, flags, sessions. LIST: ordered sequence (LPUSH/RPUSH/LPOP) — a queue or stack; recent-items, timelines. HASH: a map of fields→values under one key (HSET/HGET) — store an object and update single fields. SET: unordered unique members (SADD, SINTER/SUNION) — tags, unique visitors, relationships. SORTED SET: unique members each with a score, kept ordered (ZADD/ZRANGE, O(log n)) — leaderboards, priority queues, rate limits. STREAM: append-only log with consumer groups (Lesson 6) — event streams, durable queues. The skill is matching structure to problem: a leaderboard IS a sorted set, a unique-count IS a set — then the solution is a few O(1)/O(log n) commands instead of a job.

Lesson 1 said Redis’s superpower is that it stores data structures, not opaque blobs. This lesson is those structures — the six core types, what each does, and the jobs each is perfect for. The practical skill is matching the structure to the problem: when a problem maps onto a Redis structure, the solution collapses from a batch job into a handful of fast commands.

The six core structures

The core data structures, measured ~100k ops/sec each. String: a value (text, number, bytes, JSON), GET/SET, INCR for counters; use for cache, counters, flags, sessions. List: ordered sequence, LPUSH/RPUSH/LPOP, a queue or stack; use for job queues, recent-items, timelines. Hash: a map of fields to values under one key, HSET/HGET; use for objects (a user's fields), partial updates. Set: unordered unique members, SADD, SINTER/SUNION; use for tags, unique visitors, relationships. Sorted Set: unique members each with a score, kept ordered, ZADD/ZRANGE; use for leaderboards, priority queues, rate limits. Stream: append-only log of entries plus consumer groups; use for event streams, durable queues. Measured: INCR 105,988, ZADD 106,326, RPUSH 99,800, SADD 95,740, HSET 95,831 ops/sec, p50 ~0.25 ms.
  • String — the simplest type: a value that can be text, a number, raw bytes, or a serialized object (JSON). GET/SET to read and write; INCR/DECR to use it as an atomic counter (page views, rate-limit counts); SET ... EX to add a TTL. Measured INCR at 105,988 ops/sec. Use for: cached values, counters, flags, serialized sessions.
  • List — an ordered sequence of strings, with push/pop at both ends (LPUSH/RPUSH/LPOP/RPOP). Acts as a queue (push one end, pop the other) or a stack, and LRANGE reads a slice. Measured RPUSH at 99,800 ops/sec. Use for: simple job queues, “recent N items,” activity timelines.
  • Hash — a map of fields→values under a single key, like a small object: user:42 → {name, email, last_login}. HSET/HGET/HGETALL, and you can read or update a single field without rewriting the whole object. Measured HSET at 95,831 ops/sec. Use for: storing objects compactly, partial updates.
  • Set — an unordered collection of unique members: SADD adds, duplicates are ignored, SISMEMBER tests membership. The power is set operations: SINTER (intersection), SUNION (union), SDIFF (difference) — computed server-side. Measured SADD at 95,740 ops/sec. Use for: tags, unique visitors, “users who did X” and intersecting audiences, social-graph relationships.
  • Sorted Set (ZSet) — the most powerful structure: unique members, each with a numeric score, kept in sorted order. ZADD member score, then ZRANGE/ZREVRANGE for ranges, ZRANK for a member’s position — all O(log n). Measured ZADD at 106,326 ops/sec. Use for: leaderboards (score = points, read the top-N instantly), priority queues (score = priority), rate limiters (score = timestamp, sliding window), anything “ranked” or “ordered by a number.”
  • Stream — an append-only log of entries with ids, plus consumer groups for distributed, acknowledged consumption (full treatment in Lesson 6). Use for: durable queues and event streams where you can’t lose messages.

All of these run at ~100,000 operations per second on a single instance (measured), because they’re in RAM and the operations are O(1) or O(log n).

The skill: match structure to problem

The reason Redis feels magical when used well is that so many problems map directly onto one of these structures, and when they do, the solution is a few commands instead of a subsystem:

  • A leaderboard is a sorted set. ZADD leaderboard 4820 "player:7" to update a score; ZREVRANGE leaderboard 0 9 WITHSCORES for the top 10; ZREVRANK leaderboard "player:7" for a player’s rank. Real-time rankings over millions of players — each operation O(log n). Doing this in a relational database means an expensive sorted query on every read; in Redis it’s instant.
  • A unique-visitor count is a set. SADD visitors:2026-10-30 user:42; SCARD for the count. (For huge cardinalities, HyperLogLog — Lesson 11 — does it in ~12 KB.)
  • A session store is a hash with a TTL. HSET session:abc field value, EXPIRE session:abc 1800.
  • A rate limiter is a counter or a sorted set. INCR a per-window key with EXPIRE, or a sliding window with a sorted set of timestamps (Lesson 11).
  • A recent-activity feed is a capped list. LPUSH feed:user:42 item, LTRIM feed:user:42 0 99 to keep the latest 100.

So the design question for any Redis task is: “which structure does this problem naturally fit?” Get that right and the implementation is trivial, atomic, and fast. Get it wrong (e.g. serializing a list into a string and re-parsing it on every access) and you lose the server-side operations that make Redis fast. Learn the structures and their operations well — they’re the vocabulary of everything else in this course, from caching to coordination to the advanced patterns. Next we look at the constraint all of this lives under: memory and expiry.

Pitfalls to Avoid

  1. Serializing a structure into a string. Storing a JSON array in a string and re-parsing it in your app on every access throws away Redis’s server-side operations. Use the native list/hash/set/sorted-set so Redis does the work.
  2. Using the wrong structure. A leaderboard on a plain list means sorting in the app; a “unique” collection on a list means dedup in the app. Match the structure (sorted set, set) to the access pattern.
  3. Ignoring O(n) commands on big structures. Some commands are O(n) (LRANGE 0 -1 on a huge list, SMEMBERS on a giant set). On the single thread, these block everything (Lesson 12). Read ranges/pages, or use SCAN-family cursors.
  4. One giant key. A list or hash with millions of elements is a “big key” that’s slow to operate on and hard to evict (Lesson 12). Split it (e.g. per-day, per-shard) or cap it (LTRIM).
  5. Forgetting TTLs on structures. The same memory discipline (Lesson 3) applies to every structure — a sorted set or hash with no expiry grows forever. Cap or expire them.

Quick Recap

  • Six core structures (each ~100k ops/sec, measured): String (value/counter — GET/SET/INCR), List (ordered — queue/stack), Hash (object of fields), Set (unique members + set ops), Sorted Set (scored & ordered — ZADD/ZRANGE, O(log n)), Stream (append-only log + consumer groups).
  • Sorted sets are the most powerful: leaderboards, priority queues, sliding-window rate limits — anything ranked by a number, in O(log n).
  • The skill is matching structure to problem: a leaderboard is a sorted set, a unique-count is a set — then the solution is a few atomic commands, not a job.
  • Use the native structures (not serialized blobs) so Redis does the work server-side; beware O(n) commands and big keys (Lesson 12).

Next lesson: the constraint everything lives under — keys, expiry and memory (TTLs, maxmemory, eviction).