How would you store a user profile in a Redis hash, and which commands read a single attribute, several attributes, and the whole object? What happens to the key when you delete its last field?
answer
- HSET multi-pair since 4.0; HMSET deprecated
- HMGET = several fields, one round trip
- HGETALL is O(fields) — whole object
- HDEL last field ⇒ key disappears (and its TTL)
- values are flat strings, no nesting
basics
~20 sHSET user:42 name Ada email [email protected] writes fields; HGET reads one, HMGET reads several in one call, HGETALL returns every field and value. HDEL removes fields, and when the last field goes the key itself is deleted — Redis has no empty hashes.
solid answer
~50 sA hash is a flat map of string field names to string values under one key. ``` HSET user:42 name Ada email [email protected] plan pro HGET user:42 plan # one field HMGET user:42 name plan # several, one round trip HGETALL user:42 # every field and value ``` `HSET` takes multiple field/value pairs since Redis 4.0 and returns how many fields were *new*; it overwrites existing ones. `HSETNX` writes only if the field is absent. Reading a missing field gives `nil`, and reading a missing key behaves like an empty hash — `HGETALL` returns an empty reply rather than an error. Supporting commands: `HLEN` (field count), `HEXISTS`, `HKEYS`/`HVALS`, `HSTRLEN`, `HDEL` (accepts several fields), `HSCAN` for cursor-based iteration. Two things to know: values are plain strings — no nesting, no arrays — and `HDEL`ing the last field deletes the key, because Redis never keeps empty containers.
code
text · 12 linesHSET user:42 name Ada email [email protected] plan pro
(integer) 3 # number of NEW fields
HGET user:42 plan # "pro"
HMGET user:42 name plan missing # "Ada", "pro", (nil)
HGETALL user:42 # every field + value
HLEN user:42 # (integer) 3
HEXISTS user:42 email # (integer) 1
HDEL user:42 name email plan # (integer) 3
EXISTS user:42 # (integer) 0 <- key is gone
TYPE user:42 # nonego deeper
Recall the command vocabulary — HSET, HGET, HMGET, HGETALL, HDEL, HLEN, HEXISTS — and the rule that deleting the last field deletes the key.
Add the details: HSET's return semantics, HMSET deprecation, HGETALL's O(N) cost versus HMGET, and that TTL applies to the key not the field.
Emphasise the TTL-loss-on-empty trap, preferring HMGET/HSCAN over HGETALL in production, and keeping field names short since they are stored per hash.
Position hashes as the memory-efficient object representation with cheap partial access, and note the constraints they impose: flat, string-typed, one key per object, so one object is one slot in a cluster.
## What a hash is A Redis hash is a single key holding a flat map from **field names** to **values**, both of which are plain byte strings. It is the natural shape for "an object with named attributes": a user profile, a session, a device's settings, a row-like record. ``` HSET user:42 name Ada email [email protected] plan pro logins 3 ``` Field names are unique inside the hash; writing the same field again overwrites its value. Nothing about a hash is typed — `logins` is the four-byte string `"3"`, and it becomes a number only when a command like `HINCRBY` interprets it. ## Writing - **`HSET key field value [field value ...]`** — the single write command. Since Redis 4.0 it accepts many pairs in one call, which is why `HMSET` is deprecated (it still works and does the same thing; new code should not use it). The return value is the number of fields that were **newly created**, so re-setting an existing field returns 0 even though the write happened. - **`HSETNX key field value`** — sets the field only if it does not already exist; returns 1 or 0. Useful for "claim this slot once" semantics on a single field. Writing to a key that does not exist creates the hash implicitly. Writing to a key that holds a different type (a string, a list) fails with `WRONGTYPE`. ## Reading - **`HGET key field`** → the value, or `nil` if either the field or the key is missing. You cannot distinguish "no such key" from "no such field" by the reply alone; use `EXISTS`/`HEXISTS` if you must. - **`HMGET key field [field ...]`** → an array in the order you asked, with `nil` in the positions of missing fields. This is the workhorse read: fetching three attributes costs one round trip, and you get only the bytes you need. - **`HGETALL key`** → a flat field/value reply for the entire hash (RESP3 clients present it as a map). Convenient, but the cost is O(number of fields) and the whole thing is serialised into one reply — fine for a 10-field profile, dangerous for a hash with millions of fields. - **`HKEYS` / `HVALS`** → just the field names, or just the values. - **`HLEN`** → field count, O(1). - **`HEXISTS key field`** → 1/0. - **`HSTRLEN key field`** → length of a value without transferring it — handy for checking whether a blob is large before fetching it. - **`HSCAN key cursor [MATCH pat] [COUNT n]`** → cursor-based iteration that returns a chunk at a time instead of the whole hash in one command. The right tool for large hashes. - **`HRANDFIELD key [count [WITHVALUES]]`** → random field(s). ## Deleting, and the empty-hash rule `HDEL key field [field ...]` removes fields and returns how many were actually removed. The rule people miss: **Redis never stores an empty container**. When the last field is deleted the key stops existing — `EXISTS user:42` returns 0, `TYPE` returns `none`, and any TTL that was set on the key is gone with it. The same rule applies to lists, sets and sorted sets. Conversely, you cannot create an empty hash: there is no `HCREATE`. This interacts with expiry in a way that bites: if you `EXPIRE user:42 3600` and later delete the fields one by one, the key vanishes early and the TTL disappears with it. And if you then re-create the hash with a new `HSET`, the new key has **no** TTL — a very common source of keys that live forever. ## Expiry TTL in classic Redis is a property of the **key**, not of a field: `EXPIRE user:42 3600` expires the whole object at once. Per-field expiry only became possible with the `HEXPIRE` family introduced in Redis 7.4. ## Flatness Hashes are strictly one level deep. There is no nesting, no arrays, no numbers-as-types. Modelling `user.address.city` means either flattening the name (`address.city`) or serialising the sub-object into one field's value — at which point that field is opaque to Redis and can only be replaced wholesale. ## Naming and cost By convention keys read as `type:id` (`user:42`, `session:abc123`), and the hash's fields are the attributes. Small hashes are stored in a compact flat encoding, which makes many small objects dramatically cheaper in memory than the equivalent one-key-per-attribute layout — that memory efficiency, plus one-round-trip partial access, is the main reason to reach for a hash at all.
- You set a one-hour TTL on a hash, then delete its fields one at a time. What is the state afterwards?Once the final field is removed the key itself ceases to exist, and the TTL disappears with it. A later `HSET` on that name creates a brand-new key with **no** expiry, so the object silently becomes permanent. Any code that deletes fields must re-apply `EXPIRE` after re-creating the hash, or set the TTL in the same pipeline as the write.
- When would you use HMGET instead of HGETALL?Whenever you need a known subset of attributes. `HGETALL` is O(number of fields) and ships every value over the wire, so on a wide object it wastes CPU on the server, bandwidth, and client-side deserialisation. `HMGET` fetches exactly the fields you name in the same single round trip, and returns `nil` in the slots of fields that do not exist.
- What does HSET return, and why can that be confusing?It returns the number of fields that were *newly added*, not the number written. Updating three existing fields returns 0, which looks like a failed write to someone expecting a success count. If you need to know whether a field already existed, use `HSETNX` (1 if it set, 0 if it was already there) or check with `HEXISTS` first.
A hash is one filing-cabinet drawer (the key) with labelled folders inside (the fields): you can pull one folder without emptying the drawer, and when the last folder is removed the drawer itself is thrown away.
saying these in an interview costs you the question
- Believing an empty hash persists after the last HDEL, or that its TTL survives
- Using HMSET in new code (deprecated since Redis 4.0; HSET takes multiple pairs)
- Reading HSET's return value as 'number of fields written' rather than 'newly created'
- Calling HGETALL on wide objects when only two fields are needed
- Expecting nested objects or typed values — hash fields and values are flat strings