skip to content

RESP Protocol

You will learn the deliberately simple wire protocol every Redis client speaks — typed frames for strings, errors, arrays, and RESP3's maps and push messages — and the HELLO handshake that upgrades a connection. Interviewers ask because RESP3 push is what unlocks client-side caching.

part ofRedisoverview, primer and where to startread it →
on this pageshow

questions

5

Redis clients talk to the server using the REdis Serialization Protocol (RESP). Describe how a command and its reply are framed on the wire, and name the basic RESP2 data types with their prefix bytes.

level: middleimportance: must knowfreq 45%

answer

  1. simple, - error, : integer, $ bulk, * array — CRLF everywhere
  2. request = always array of bulk strings
  3. $ is length-prefixed = binary safe; $-1 = null, *-1 = null array
  4. error's first word is the code: WRONGTYPE, MOVED, NOSCRIPT
  5. HGETALL in RESP2 = flat array, client rebuilds the map

basics

~20 s

A client sends every command as a RESP array of bulk strings; the server replies with one typed frame. RESP2 has five types identified by a first byte: + simple string, - error, : integer, $ bulk string (length-prefixed, binary safe), * array. Every frame ends with CRLF.

solid answer

~50 s

RESP is a **request/response, first-byte-typed, CRLF-terminated** protocol over TCP. A client always sends a command as an **array of bulk strings**: `SET k v` goes on the wire as `*3\r\n$3\r\nSET\r\n$1\r\nk\r\n$1\r\nv\r\n`. There is no separate command grammar — the server just receives an argument vector. The reply is a single frame whose first byte names its type (RESP2): | byte | type | example | |---|---|---| | `+` | simple string | `+OK\r\n` | | `-` | error | `-ERR unknown command\r\n` | | `:` | integer | `:1000\r\n` | | `$` | bulk string | `$5\r\nhello\r\n`; `$-1\r\n` is null | | `*` | array | `*2\r\n:1\r\n:2\r\n`; `*-1\r\n` is a null array | Simple strings and errors are single-line and cannot contain CR or LF; **bulk strings are length-prefixed and therefore binary safe**, which is why values may be arbitrary bytes. Arrays nest, so a reply can contain arrays of bulk strings. The error frame's first word (`ERR`, `WRONGTYPE`, `MOVED`, `NOSCRIPT`) is a convention clients parse.

code

text · 11 lines
text
# client -> server: always an array of bulk strings
*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$5\r\nvalue\r\n

# server -> client, one typed frame each:
+OK\r\n                       simple string
-WRONGTYPE Operation ...\r\n error (first word = code)
:42\r\n                       integer
$5\r\nhello\r\n              bulk string (binary safe)
$-1\r\n                      null bulk string  (GET on a missing key)
*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n  array
*-1\r\n                      null array (e.g. BLPOP timeout)

go deeper

for a junior

Recall the five type bytes and that a command goes out as an array of bulk strings terminated by CRLF.

for a middle

Explain length prefixing and binary safety, the null bulk string versus empty string, and the error-code convention in the first word.

for a senior

Connect framing to behaviour: ordered replies enabling pipelining, flat arrays that clients reassemble, and the RESP2 pub/sub awkwardness that motivated RESP3 push frames.

for a principal

Discuss the design tradeoff — a deliberately simple, human-readable, length-prefixed protocol that keeps client implementations trivial and evolution cheap, at the cost of verbosity and type poverty that RESP3 later addressed.

## What RESP is RESP (REdis Serialization Protocol) is the wire format between clients and the Redis server. It runs over TCP (port 6379 by default, optionally with TLS wrapping it). Its design goals are stated plainly in the protocol documentation: simple to implement, fast to parse, and human readable. It is not a general-purpose serialization format like Protobuf; it exists only to carry command arguments one way and typed replies the other. ## Request framing: everything is an array of bulk strings A client does not send the text `SET name value`. It sends a **RESP array whose elements are bulk strings**: ``` *3\r\n$3\r\nSET\r\n$4\r\nname\r\n$5\r\nvalue\r\n ``` Read it as: an array of 3 elements; a 3-byte string `SET`; a 4-byte string `name`; a 5-byte string `value`. `\r\n` (CRLF) terminates every line. Two consequences fall out of this: 1. **There is no command syntax to parse.** The server receives an argument vector and dispatches on argument 0. This is why adding a command or a new option requires no grammar change, and why a client library can implement `SEND(*args)` generically. 2. **Arguments are opaque bytes.** Because bulk strings are length-prefixed, an argument may contain spaces, newlines, or arbitrary binary — a serialized image, a protobuf blob. Nothing needs escaping. ## Reply framing: one byte says what it is RESP2 defines five frame types, distinguished by the first byte: **`+` Simple string.** `+OK\r\n`, `+PONG\r\n`. A single line of text with no CR or LF inside, and no length prefix. Cheap to produce and parse; used for short status replies. **`-` Error.** `-ERR unknown command 'FOO'\r\n`, `-WRONGTYPE Operation against a key holding the wrong kind of value\r\n`. Structurally identical to a simple string but the leading `-` tells the client to raise/return an error rather than a value. By convention the **first word is an uppercase error code** — clients and applications branch on `WRONGTYPE`, `NOSCRIPT`, `MOVED`, `ASK`, `LOADING`, `BUSYGROUP`, `NOAUTH`, `OOM`, `READONLY` — and the rest is a human-readable message. **`:` Integer.** `:1000\r\n`. A 64-bit signed integer. Commands like `INCR`, `LLEN`, `DEL`, `EXISTS` reply with this. Boolean-ish commands (`EXPIRE`, `SETNX`, `SISMEMBER`) use `:0` / `:1` in RESP2, since RESP2 has no boolean type. **`$` Bulk string.** `$5\r\nhello\r\n` — a byte count, CRLF, exactly that many bytes, CRLF. **Binary safe**: the length tells the parser precisely how much to read, so the payload may contain CRLF or any byte. `$0\r\n\r\n` is the empty string. **`$-1\r\n` is the null bulk string**, which is how `GET missing-key` says "no such key" — distinct from the empty string, a distinction clients must preserve (Python `None` vs `b""`, Java `null` vs `""`). **`*` Array.** `*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n` — an element count followed by that many complete RESP frames of any type, including nested arrays. `*0\r\n` is an empty array; **`*-1\r\n` is a null array**, returned for example by a blocking pop that timed out. `LRANGE`, `HGETALL`, `MGET`, and `EXEC` all return arrays; `EXEC` returns an array whose elements are the individual command replies, and `*-1` when a `WATCH` guard aborted the transaction. ## Nesting and heterogeneity An array's elements need not share a type. `XRANGE` returns arrays containing an id bulk string and a nested array of field/value bulk strings. `CONFIG GET` returns a flat array alternating name and value in RESP2, and `HGETALL` likewise returns a flat `field, value, field, value` array — the client library is what reassembles it into a map. This flattening is one of RESP2's real weaknesses and is exactly what the RESP3 map type fixes. ## Why CRLF and why text Every frame line ends with `\r\n`. Combined with the leading type byte and the explicit length prefixes, this makes the protocol trivially parseable with a state machine and no lookahead beyond the declared lengths — the server never has to scan for a delimiter inside a payload. It is also debuggable: you can read a packet capture, or drive a server from `nc`, without a decoder. ## The client's job A client library is therefore mostly: serialise `args` into `*N` + `$len` frames; write them; read one frame; map the frame type to a language value; special-case the null bulk string; and raise on `-`. Pipelining is nothing more than writing several request arrays before reading, because replies come back in the same order as requests on the same connection. Pub/Sub in RESP2 is the awkward exception: subscription messages arrive as ordinary arrays pushed at the client, which is why a RESP2 connection in subscribe mode may only issue subscription-related commands — and it is exactly the problem the RESP3 push type was introduced to solve.

  • How does a client tell a missing key apart from a key holding an empty string?
    By the frame, not the content. A missing key produces the null bulk string `$-1\r\n`, while an empty value produces `$0\r\n\r\n`. A correct client maps the first to the language's null/None/nil and the second to a zero-length string. Collapsing them — as some naive parsers do — breaks patterns like distinguishing 'not cached' from 'cached as empty', which is the difference between a cache miss and a negative cache entry.
  • Why are bulk strings length-prefixed while simple strings are not?
    A simple string is terminated by CRLF, so it can never contain CR or LF and is limited to short status text. A bulk string declares its byte count up front, so the parser reads exactly that many bytes and the payload may contain any byte sequence including CRLF — that is what makes Redis values binary safe. The cost is a few extra bytes per frame, which is why cheap fixed status replies like `+OK` use the simple form.
  • What does the first word of a RESP error reply mean?
    It is an uppercase error code that clients and applications branch on, followed by a human-readable message. `WRONGTYPE` means the key holds a different data type, `MOVED` and `ASK` are cluster redirections carrying a slot and address, `NOSCRIPT` tells a client to fall back from EVALSHA to EVAL, and `LOADING` means the dataset is still being read from disk. Client libraries parse this prefix to decide whether to retry, redirect, or surface an exception.

saying these in an interview costs you the question

  • Claiming the client sends plain text like 'SET k v' rather than an array of bulk strings
  • Treating the null bulk string `$-1` as an empty string, losing the missing-key distinction
  • Thinking values must be escaped or cannot contain newlines — length prefixing makes them binary safe
  • Assuming RESP is a binary protocol like Protobuf, or that it compresses payloads
  • Believing HGETALL returns a map on the wire in RESP2; it returns a flat array that the client reassembles

context

open as a page

Redis 6 introduced version 3 of its serialization protocol alongside the existing one. How does a client select the protocol version on a connection, and what does the newer version add?

level: middleimportance: should knowfreq 38%

basics

~20 s

A connection starts in RESP2; the client opts in by sending HELLO 3 (optionally with AUTH and SETNAME), and the server replies with a map of server info or an error on older servers. RESP3 adds typed replies — map, set, double, boolean, big number, verbatim string, null — plus out-of-band push frames and attributes.

open as a page

In the RESP protocol, most frames a client reads are replies to commands it sent. Explain what an out-of-band push frame is, why the older protocol version could not express one, and what capabilities it unlocked.

level: seniorimportance: should knowfreq 28%

basics

~20 s

A push frame (prefix >, RESP3 only) is a server-initiated message that is not a reply to any command. RESP2 had no such marker, so subscription messages looked like ordinary array replies and a subscribed connection had to be restricted to subscription commands. Push frames let a client demultiplex, so one connection can carry both commands and server notifications.

open as a page

You can open a raw TCP connection to Redis with netcat, type `PING` followed by Enter, and get a reply — even though real clients send commands as length-prefixed arrays. What mechanism makes that work, and what are its limitations?

level: juniorimportance: nice to knowfreq 18%

basics

~20 s

Redis supports inline commands: if a line does not start with *, the server splits it on whitespace and treats the words as command arguments. It exists for interactive debugging over telnet or netcat. It cannot carry binary or spaces inside arguments, and the line has a length limit, so real clients never use it.

open as a page

Redis's wire protocol is deliberately human-readable text with CRLF line endings rather than a compact binary encoding. What are the engineering tradeoffs of that choice, and how does it still handle arbitrary binary values?

level: seniorimportance: nice to knowfreq 24%

basics

~20 s

Text framing makes the protocol trivial to implement, debug with tcpdump or netcat, and extend without versioning. Binary safety comes from length prefixes: bulk strings declare their byte count, so payloads may contain any bytes including CRLF. The cost is a few extra bytes per frame and integer-to-text conversion, which is negligible next to network round trips.

open as a page