Incrementing Hash Fields (HINCRBY)
Redis hashes are perfect for grouping related counters under one key — a user’s login count, a product’s stock level, an article’s like count. HINCRBY is the command that lets you increase (or decrease) one of those counters by a whole number, atomically, in a single step. Instead of reading a field’s value, adding to it in your application, and writing it back — three separate round trips with a race condition waiting to happen — HINCRBY does the read-modify-write entirely inside Redis’s single-threaded command loop.
Overview: How HINCRBY Works
HINCRBY is the hash-field equivalent of the string command INCR. Where INCR increments an entire key’s value, HINCRBY increments one field inside a hash, leaving every other field untouched. It takes an integer increment (which can be negative, effectively decrementing the field) and returns the field’s new value after the change.
Two behaviors make HINCRBY especially convenient. First, if the hash key doesn’t exist at all, Redis creates a new hash for you. Second, if the key exists but the field doesn’t, Redis treats the field’s starting value as 0 before applying the increment. This means you never need a separate "initialize the counter" step — the very first HINCRBY call on a brand-new field just works, taking it from an implicit 0 to whatever increment you passed.
Under the hood, a hash in Redis is stored as one of two internal encodings depending on its size: a compact listpack (a tightly packed sequence of length-prefixed entries) for small hashes, or a full hash table once the hash grows past hash-max-listpack-entries fields or a field/value exceeds hash-max-listpack-value bytes. Either way, HINCRBY follows the same steps: locate the hash, locate the field within it, parse the field’s current string value as a 64-bit signed integer, add the increment, check for overflow, and write the result back as a string. Because Redis executes one command at a time on its main thread, no other client’s command can interleave in the middle of this read-modify-write — that’s what makes HINCRBY atomic even when many clients hit the same field concurrently.
Redis also ships a sibling command, HINCRBYFLOAT, for incrementing by a floating-point amount (useful for things like running averages or currency totals, though floating-point precision quirks apply there just as they do anywhere else). This lesson focuses on the integer form, HINCRBY, which is by far the more commonly used of the two for counters.
Reference: hash commands used in this lesson
| Command | Description | Time Complexity |
|---|---|---|
HSET key field value [field value ...] |
Set one or more fields in a hash | O(1) per field pair |
HGET key field |
Get a single field’s value | O(1) |
HGETALL key |
Get all fields and values | O(N), N = number of fields |
HINCRBY key field increment |
Atomically add an integer to a field | O(1) |
HINCRBYFLOAT key field increment |
Atomically add a float to a field | O(1) |
Syntax
HINCRBY key field increment
- key — the name of the hash. If it doesn’t exist, Redis creates a new hash containing just the one field you’re incrementing.
- field — the name of the field inside the hash to increment. If it doesn’t exist yet, Redis treats its starting value as
0. - increment — a signed 64-bit integer to add to the field’s current value. Pass a negative number to decrement instead.
It returns an integer reply: the field’s value after the increment is applied. If the field currently holds a value that can’t be parsed as an integer, or if applying the increment would overflow a 64-bit signed integer, Redis returns an error instead and the field is left unchanged.
Examples
Example 1: A simple page-view counter
HSET stats:page:home views 0
HINCRBY stats:page:home views 1
HINCRBY stats:page:home views 1
HGET stats:page:home views
Output:
(integer) 1
(integer) 1
(integer) 2
"2"
The HSET creates the hash with a views field starting at 0 and returns 1 because one new field was added. Each HINCRBY call adds 1 and returns the field’s new value. After two increments, HGET confirms the field holds 2, returned as a bulk string (hash field values are always stored and returned as strings, even though they represent integers internally during arithmetic).
Example 2: HINCRBY creates the field for you
HINCRBY user:1001:stats login_count 1
HINCRBY user:1001:stats login_count 1
HINCRBY user:1001:stats login_count 5
HGETALL user:1001:stats
Output:
(integer) 1
(integer) 2
(integer) 7
1) "login_count"
2) "7"
Notice there’s no HSET at all here — the key user:1001:stats didn’t exist before this block ran. The very first HINCRBY call created the hash and the login_count field, treated its starting value as 0, added 1, and returned 1. Subsequent calls just keep adding to whatever value is already there, including a jump of 5 on the third call, landing at 7.
Example 3: tracking inventory with increments and decrements
HSET inventory:sku:4471 stock 50 reserved 0
HINCRBY inventory:sku:4471 stock -3
HINCRBY inventory:sku:4471 reserved 3
HGETALL inventory:sku:4471
Output:
(integer) 2
(integer) 47
(integer) 3
1) "stock"
2) "47"
3) "reserved"
4) "3"
This is a realistic pattern: one hash per product SKU holds several related counters. Selling 3 units decrements stock by passing -3 as the increment, while reserved is bumped up by 3 to track units held for a pending order. Both operations are atomic individually, so concurrent sales can’t corrupt either counter — though note that moving stock into reserved as two separate HINCRBY calls is still not a single atomic transaction across both fields (see Common Mistakes below for when that distinction matters).
How It Works Step by Step
When Redis receives HINCRBY key field increment, it performs these steps entirely within one uninterruptible pass on the main thread:
- Look up
keyin the keyspace. If it doesn’t exist, create a new empty hash (starting in listpack encoding) and associate it withkey. - If the key exists but holds a non-hash type (a string, list, set, etc.), immediately return a
WRONGTYPEerror and stop — nothing is modified. - Look up
fieldwithin the hash. If it doesn’t exist, treat its current value as the string"0". - Parse the field’s current string value as a 64-bit signed integer. If it can’t be parsed (for example, it contains letters), return an error and stop.
- Add
incrementto the parsed value, checking for 64-bit overflow. If the result would overflow, return an error and stop. - Store the result back into the field as a string, and return that value as an integer reply to the client.
Because no other command can run on the server in the middle of these steps, two clients calling HINCRBY on the same field at the same time will always see their increments applied one after another, never interleaved or lost.
Common Mistakes
Mistake 1: reading, adding, and writing back instead of using HINCRBY
A very common bug is implementing a counter with HGET, adding to the value in application code, and writing it back with HSET. That’s three separate commands, and between the HGET and the HSET, another client can run the exact same sequence — both clients read the same starting value, both add 1, and one increment is silently lost.
Client A: HGET user:1001:stats login_count -> "7"
Client B: HGET user:1001:stats login_count -> "7"
Client A: HSET user:1001:stats login_count 8
Client B: HSET user:1001:stats login_count 8
(Result: 8, even though two logins happened -- one increment vanished)
The fix is to never do read-then-write for a counter. Use HINCRBY, which performs the read, add, and write as one atomic server-side operation:
HSET user:1001:stats login_count 7
HINCRBY user:1001:stats login_count 1
Mistake 2: incrementing a field that isn’t numeric
If a field currently holds text that can’t be parsed as an integer, HINCRBY fails rather than silently doing nothing:
HSET account:1001 balance "not-a-number"
HINCRBY account:1001 balance 10
Output:
(integer) 1
(error) ERR hash value is not an integer
The fix is to make sure fields you plan to increment are only ever written as plain integers (for example, always initialize a counter field with HSET account:1001 balance 0, never with a non-numeric placeholder).
Mistake 3: calling HINCRBY on the wrong key type
Every Redis key has exactly one type. If counter:visits was created with SET (a string), calling a hash command on it fails:
SET counter:visits 100
HINCRBY counter:visits value 1
Output:
OK
(error) WRONGTYPE Operation against a key holding the wrong kind of value
If you want a single standalone counter, use INCR counter:visits on a string key. Reserve HINCRBY for when the counter is one of several related fields that belong together under one hash key, such as page:home:stats holding both views and unique_visitors.
Best Practices
- Always use
HINCRBY(never a manualHGET-then-HSETpair) for any counter that multiple clients might update concurrently — it’s the only way to avoid lost updates. - You don’t need to pre-initialize a field with
HSETbefore incrementing it; the firstHINCRBYcall implicitly starts from0. - Group related counters into one hash (for example,
article:100:statswithlikesandsharesfields) instead of separate top-level keys — it keeps your keyspace smaller and lets you fetch everything with oneHGETALL. - Use negative increments to decrement (
HINCRBY key field -1) rather than reaching for a separate decrement command — Redis has noHDECRBY. - If a counter set should expire (like a daily hit counter), set a TTL on the whole hash key with
EXPIRE; TTLs apply to keys, not to individual hash fields. - Reach for
HINCRBYFLOATonly when you genuinely need fractional increments, and be aware floating-point results can accumulate small rounding errors over many operations. - Keep counter fields exclusively numeric — never store a placeholder string in a field you intend to increment later.
Practice Exercises
- Create a hash
article:100:statsand useHINCRBYto record 3 likes and 1 share (don’t pre-set the fields — letHINCRBYcreate them). Confirm the final state withHGETALL; you should end up withlikesat3andsharesat1. - Starting from a hash
warehouse:sku:900withstockset to20, simulate selling 5 units and then receiving a shipment of 15 units, using onlyHINCRBYcalls with appropriate positive and negative increments. What’s the final stock value? - Deliberately trigger a
WRONGTYPEerror: create a string key withSET, then try toHINCRBYa field on it. Read the error message carefully, then create a proper hash key and repeat the increment successfully.
Summary
HINCRBY key field incrementatomically adds a whole number (positive or negative) to one field inside a hash, in O(1) time.- If the key or field doesn’t exist, Redis creates it, treating a missing field’s starting value as
0. - Because Redis is single-threaded,
HINCRBYis safe under concurrent access — unlike a manualHGET-then-HSETsequence, which can lose updates to a race condition. - Incrementing a non-numeric field, or calling
HINCRBYon a key that isn’t a hash, returns an error and leaves the data unchanged. - For fractional increments, use the related command
HINCRBYFLOATinstead. - Group related counters into one hash key to keep your keyspace organized and retrievable in a single
HGETALLcall.
