skip to content

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