← Back to list

RESP

The Tiny, Honest Protocol Hiding Inside Redis

Pradhansujay · 2026-06-04 12:13 · 0 claps · 5.6 min read
#redis #resp #cache #fast
Open on Medium ↗

RESP

The Tiny, Honest Protocol Hiding Inside Redis

I remember the exact moment Redis stopped feeling like magic.

I was debugging a connection issue late one night, the kind where you’ve already restarted everything twice and you’re starting to suspect the universe. Out of frustration more than strategy, I opened a raw TCP connection to my Redis server and just started typing commands by hand. No client library. No SDK. Just me, a terminal, and a socket.

And Redis answered me. In plain text. Something I could read.

That was the night I learned about RESP, and it quietly changed how I think about the tools I use every day.

So what is RESP, really?

RESP stands for REdis Serialization Protocol. It’s the language your Redis client and your Redis server use to talk to each other over the wire. Every time you call redis.set("user:1", "Giri") in your code, something underneath translates that friendly function call into a stream of bytes, ships it across a TCP socket, and translates the reply back.

That translation layer is RESP.

Here’s the thing that surprised me: most protocols that sit at this level of the stack are binary, dense, and frankly hostile to human eyes. You need a special tool just to look at them. RESP went the other direction. It’s almost defiantly simple. You can read it. You can type it by hand. You can understand the entire thing in an afternoon.

That wasn’t an accident. When Salvatore Sanfilippo (antirez) designed it, he was optimizing for three things at once: it had to be simple to implement, fast to parse, and human-readable. Most protocol designers will tell you that you have to pick two. RESP somehow gets all three, and the trick to how it does that is the whole story.

The whole protocol fits in five symbols

Here’s the part I find genuinely beautiful. In its classic form (RESP2), the entire protocol is built on five data types, and you can tell which one you’re looking at by the very first byte:

  • + means a simple string — short status replies like +OK
  • - means an error — like -ERR unknown command
  • : means an integer — like :1000
  • $ means a bulk string — binary-safe text of any length
  • * means an array — a list of any of the above

Every single message ends with \r\n (carriage return, line feed). That's it. That's the protocol. The first byte tells the parser what's coming, and the \r\n tells it where the piece ends. A parser doesn't have to guess, look ahead, or backtrack. It reads one byte, knows the shape of what follows, and moves on.

This is why RESP is fast despite being readable. The “human-readable” part isn’t fighting the “fast to parse” part. They’re the same design decision wearing two hats.

Let me show you a real conversation

Theory is fine, but the moment it clicked for me was seeing actual bytes. So let’s do that.

Say you run the simplest command in Redis:

SET foo bar

Your client doesn’t send the string "SET foo bar". It sends this:

*3\r\n
$3\r\nSET\r\n
$3\r\nfoo\r\n
$3\r\nbar\r\n

Read it slowly, because once you see the logic you can’t unsee it:

  • *3 — "I'm sending you an array of 3 elements." (Commands are just arrays.)
  • $3\r\nSET — "The first element is a bulk string of length 3, and it's SET."
  • $3\r\nfoo — "Next, a bulk string of length 3, foo."
  • $3\r\nbar — "And finally, a bulk string of length 3, bar."

Every command you’ve ever sent to Redis is shaped exactly like this: an array of bulk strings. GET, LPUSH, HSET, your scariest Lua script call — all of them are just arrays of length-prefixed strings.

The server reads that, does the work, and replies:

+OK\r\n

A simple string. The + says "this is a status, not data, and everything went fine."

Now ask for the value back:

*2\r\n$3\r\nGET\r\n$3\r\nfoo\r\n

And Redis answers:

$3\r\nbar\r\n

A bulk string: length 3, contents bar. There it is.

Why the length prefix matters more than you’d think

That little $3 in front of every bulk string is doing heavy lifting, and it's worth pausing on.

Because the length is declared up front, the value can contain literally anything — newlines, null bytes, binary image data, a serialized protobuf, an entire JPEG. RESP doesn’t care what’s inside, because it never has to scan the content looking for a terminator. It already knows exactly how many bytes to read. This is what people mean when they say bulk strings are binary-safe.

Compare that to a protocol that says “a string ends at the next newline.” The moment your data contains a newline, that protocol breaks or needs escaping hacks. RESP sidesteps the entire class of problem by just telling you the length first. It’s such a small decision with such large consequences.

There’s also a special case worth knowing: $-1\r\n is the null bulk string. It's how Redis says "that key doesn't exist" or "no value here." A length of negative one is a clean, unambiguous "nothing." (RESP2 also has *-1\r\n for a null array, which trips people up the first time they see it.)

Try it yourself — this is the fun part

You don’t have to take my word for any of this. If you have Redis running, open a terminal and connect with a raw tool like nc (netcat) or telnet:

nc localhost 6379

Then type a command in plain English, like:

PING

Redis will reply:

+PONG

You’re now speaking RESP by hand. The server is being polite and accepting the human-friendly “inline command” form here, then answering in proper RESP. Try SET name redis, then GET name, and watch the $ replies come back. There's something oddly grounding about it — the abstraction peels away and you realize there was never any magic, just a well-designed conversation.

RESP3: the same idea, grown up

Everything above is RESP2, which served Redis faithfully for years. But it had a real limitation: it couldn’t express much type information. A client receiving :1 had no way to know whether that integer was meant to be a number, a boolean, or something else. The client library had to remember, per command, how to interpret each reply. That's fragile.

So Redis 6 introduced RESP3, and it’s less a rewrite than a thoughtful expansion. It keeps the same first-byte philosophy and adds new types so the data can describe itself:

  • _\r\n — a proper null (no more overloading $-1)
  • #t / #f — actual booleans
  • ,doubles (floating point)
  • (big numbers
  • %maps (key-value pairs, so a hash comes back as an actual map instead of a flat array you have to pair up yourself)
  • ~sets
  • >push messages, for server-initiated data like Pub/Sub notifications

That last one matters more than it looks. In RESP2, Pub/Sub messages and command replies traveled on the same channel, and clients had to carefully untangle which was which. RESP3’s push type gives server-pushed data its own clearly marked lane. Cleaner, less error-prone.

A client opts into RESP3 by sending the HELLO 3 command when it connects. If it doesn't, the server keeps speaking RESP2. Backward compatibility, handled gracefully — which is its own small lesson in how to evolve a protocol without breaking the world.

What this little protocol actually taught me

I could have gone my whole career calling redis.get() without ever knowing any of this, and my code would have worked fine. So why does it matter?

Because understanding RESP changed my relationship with the abstraction. When a connection hangs now, I have intuition about why — I know there’s a length prefix the parser might be waiting on, a \r\n that might be missing. When I read about a new Redis feature, I can picture the bytes. When I had to debug a custom client once, RESP being readable is the only reason I solved it before sunrise.

But honestly, the bigger lesson was about design taste. RESP is a quiet argument that simplicity and performance aren’t enemies. That you don’t always have to choose between “fast” and “understandable.” That a protocol — a protocol, of all things — can be considerate toward the human who might one day have to read it at 2 a.m.

Most of the tools we lean on every day have a layer like this hiding just beneath the surface. Readable. Learnable. Waiting for the night you get curious enough to open a raw socket and just start typing.

I’d encourage you to go find yours.

If you try the nc localhost 6379 trick and watch RESP reply to you in real time, I'd love to hear how it felt. There's a specific kind of joy in watching the magic turn into mechanism.


메타데이터
post_id
a4f8686232dd
slug
resp-a4f8686232dd
url
https://medium.com/@pradhansujay856/resp-a4f8686232dd
canonical_url
https://medium.com/@pradhansujay856/resp-a4f8686232dd
author_url
https://medium.com/@pradhansujay856
status
ok
fetched_at
2026-06-16 19:09:56