Hashes

beginner redis hashes data-structures

A Redis hash is a map of field → value stored under one key. Think of it like a tiny dict-inside-a-dict, or a database row where the columns are the fields. It’s the most natural way to store objects in Redis.

Why not just stringify a JSON blob and stuff it in a string key? Because with a hash we can read or update individual fields without touching the rest — no parse, mutate, re-serialize dance.

The basics

HSET user:42 name "Alice" age 30 city "Mumbai"
# (integer) 3   — three new fields

HGET user:42 name
# "Alice"

HGETALL user:42
# 1) "name"
# 2) "Alice"
# 3) "age"
# 4) "30"
# 5) "city"
# 6) "Mumbai"

HMGET user:42 name city
# 1) "Alice"
# 2) "Mumbai"

Update one field cheaply

This is the killer move. Compared to “fetch JSON → parse → mutate → write back”:

HSET user:42 city "Bangalore"
HGET user:42 city
# "Bangalore"

One round trip, one operation, no parsing.

Atomic field counters

HINCRBY is to hashes what INCR is to strings — atomic and useful.

HSET stats:user:42 logins 0 page_views 0
HINCRBY stats:user:42 logins 1
# (integer) 1
HINCRBY stats:user:42 page_views 5
# (integer) 5

Real-world: per-user counters, per-product stock levels, per-feature toggles.

Layout

Hash: user:42
field value
nameAlice
age30
cityMumbai
emailalice@example.com
Small hashes are encoded as a packed listpack — extremely memory-efficient.

Memory: hashes are tiny when small

When a hash has fewer than ~128 fields and each value is under 64 bytes (defaults configurable via hash-max-listpack-entries), Redis encodes it as a listpack — a contiguous, cache-friendly byte array. This is dramatically smaller than the same data spread across individual string keys.

Real-world example: storing 1M user profiles as user:{id}:name, user:{id}:age etc. uses ~5x more memory than HSET user:{id} name ... age .... This is why hashes are often called the “object encoding” of Redis.

Inspecting

HKEYS user:42         # all field names
HVALS user:42         # all values
HEXISTS user:42 city  # 1 or 0
HDEL user:42 city
HLEN user:42          # number of fields

Real-world: cached user profile

async function cacheUser(user) {
  await redis.hset(`user:${user.id}`, {
    name: user.name,
    email: user.email,
    plan: user.plan,
    lastSeen: user.lastSeen,
  });
  await redis.expire(`user:${user.id}`, 3600);
}

async function getUserField(id, field) {
  return redis.hget(`user:${id}`, field);   // one round trip, no parsing
}

Gotchas

  • HGETALL is O(N) — fine for small hashes, painful when the hash holds 100k fields. Use HSCAN for large ones.
  • Hash fields don’t have individual TTLs (until Redis 7.4 added per-field TTL). Older versions: the whole key expires together.
  • Field names use memory too — keep them short. n beats name if you have a billion of them; usually not worth it.