Why Redis Strings are not strings — they are binary-safe byte arrays. Integer encoding, atomic INCR/DECR, the GET/SET command family, MGET/MSET for batching, and why storing JSON blobs is the first mistake engineers make.
F-2 — Strings, Numbers, and Binary Safety
Who this module is for: You have used
SETandGETand assumed you understood Redis Strings. This module will show you what is actually happening under the hood — how Redis encodes integers differently from text, why "String" is a misleading name for what is really a binary-safe byte array, and why the seemingly obvious pattern ofSET key (JSON.stringify(obj))creates problems you will only discover under load.
What a Redis String Actually Is
The name "String" is the first thing Redis gets slightly wrong. When most engineers hear "string," they think of text — a sequence of Unicode characters with an encoding like UTF-8.
A Redis String is not that.
A Redis String is a binary-safe byte array — a sequence of raw bytes with no imposed encoding, no null-termination requirement, and no character set assumption. It can hold:
- Plain text:
"Hello, Redis" - A serialized JSON object:
"{\"id\":1,\"name\":\"Jatin\"}" - A serialized Protocol Buffer
- A JPEG image
- A packed binary struct
- An integer:
"42" - An empty string:
""
The maximum size is 512 MB per key. In practice, values larger than a few kilobytes start to create problems (large values block the event loop during serialization, consume significant memory, and become expensive to transfer over the network), but the hard limit is 512 MB.
The "binary-safe" property matters because many key-value systems from Redis's era (early 2000s Memcached, for example) used C-style null-terminated strings, which meant you could not store arbitrary binary data — a null byte would terminate the string early. Redis stores the length alongside the data, so a null byte in the middle of a value is perfectly valid.
How Redis Encodes Strings Internally
Here is something most Redis users never learn: Redis does not store all String values the same way internally. It uses three different encodings depending on the value:
int encoding
If the value is an integer that fits in a 64-bit signed long (roughly -9.2 × 10¹⁸ to 9.2 × 10¹⁸), Redis stores the actual integer, not a string representation of it.
This is significant for two reasons:
-
Memory efficiency. An integer stored as
intencoding takes 8 bytes. The same number stored as a string"42"would take 2 bytes of data plus string header overhead. For large integers like Unix timestamps or IDs, the difference is meaningful at scale. -
Atomic arithmetic.
INCR,DECR,INCRBY,DECRBYonly work on keys withintencoding (or keys whose value is a string representation of an integer). Redis can parse and operate on them atomically.
Additionally, Redis maintains a shared integer pool for the integers 0 through 9999. When you store any of these values, Redis does not allocate new memory — it points to a pre-allocated shared object. This is why OBJECT REFCOUNT on small integers returns a large number.
embstr encoding
For strings up to 44 bytes, Redis uses embstr (embedded string) encoding. The string header and the data are allocated in a single contiguous memory block, making it cache-friendly and reducing allocator overhead.
embstr objects are immutable — any modification (like APPEND) causes Redis to convert the encoding to raw and reallocate.
raw encoding
For strings longer than 44 bytes, Redis uses raw encoding: a standard dynamic string (SDS — Simple Dynamic String) where the header and data are in separate memory allocations.
Why does this matter? Because encoding determines memory usage and performance characteristics. If you are storing millions of short strings (session tokens, feature flags, user IDs), embstr gives you better cache locality. If you are storing large JSON blobs, raw encoding is unavoidable — and that is where the "storing JSON in a String" pattern starts to cost you.
The SET Command in Full
Most engineers know SET key value. The full signature is considerably richer:
SET key value [NX | XX] [GET] [EX seconds | PX milliseconds | EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]
Let us go through each option:
Expiry options
KEEPTTL is underused. It lets you update a value without accidentally removing the TTL. Without it, SET on a key that already has a TTL will reset the TTL to infinite (persistent) — a common source of session expiry bugs.
Conditional options
NX is the foundation of distributed locking (covered in depth in A-5). SET key value NX EX seconds is the atomic "acquire a lock" primitive — it either sets the key and returns OK (lock acquired) or returns (nil) (lock already held).
The old pattern of SETNX + EXPIRE as two separate commands is broken — if the process crashes between the two commands, the key has no expiry and the lock is never released. SET ... NX EX is atomic. Always use it.
GET option (Redis 6.2+)
This is equivalent to the old GETSET command (which is now deprecated), but integrated into SET itself.
The GET Command Family
Beyond plain GET, Redis provides several atomic read-and-modify commands:
GETDEL
Sign in to keep reading
The rest of this module is free — sign in with Google to unlock it and track your progress.
Sign in & RegisterDiscussion
0Join the discussion