Redis Get Started (redis-cli)

Redis is an in-memory, key-value data store used for caching, session storage, counters, queues, and more. Because it keeps data in RAM and processes commands one at a time on a single thread, it responds to most operations in well under a millisecond. redis-cli is the official command-line client that ships with Redis, and it’s the fastest way to talk to a Redis server directly — type a command, press Enter, and see the reply immediately. This lesson gets you connected and comfortable running commands before you touch any client library.

Overview: What Redis Is and How redis-cli Talks to It

At its core, Redis is a server process (the redis-server binary) that listens on a TCP port (6379 by default) and holds all of its data in memory. Every piece of data lives under a key — a string like user:1001:name — and every key holds exactly one value of exactly one type: a simple string, a hash, a list, a set, a sorted set, and a few more specialized types covered in later lessons. There is no schema and no tables; you simply create keys as you need them.

redis-cli is a separate, tiny program. When you launch it, it opens a TCP connection to the server and gives you an interactive prompt. Every line you type is parsed into a command and its arguments, sent over that connection, and the server sends back a reply, which redis-cli prints in a specific notation: plain text for simple replies (OK), (integer) N for numbers, quoted strings for bulk string replies, numbered lines for arrays, and (nil) when a key doesn’t exist. Learning to read this notation is essential — it’s exactly what you’ll see every time you run a command, in this lesson and in production debugging.

A critical fact about Redis’s design: the server is single-threaded for command execution. Only one command runs at any instant, from start to finish, before the next one begins. This means every individual Redis command is atomic by default — no other client can see a half-finished write. This is why Redis is trusted for counters, rate limiters, and locks without any extra locking logic on your part. The tradeoff is that a single very slow command (like an unbounded KEYS * over millions of keys) blocks every other client until it finishes, which is why command choice matters even at this “getting started” stage.

Syntax

You start redis-cli from your terminal, not from inside Redis itself:

redis-cli

This connects to a Redis server on localhost port 6379 with no password, which is the default for a local development instance. To connect elsewhere, you pass flags before entering the prompt:

redis-cli -h <host> -p <port> -a <password>
  • -h — the hostname or IP address of the Redis server (default 127.0.0.1).
  • -p — the TCP port the server listens on (default 6379).
  • -a — the password, if the server requires authentication (requirepass is set).

Once you’re connected, every subsequent example in this lesson is typed directly at the prompt, one command per line, in this general shape:

COMMAND key [argument ...]

The command name (SET, GET, DEL, and so on) is conventionally written in uppercase on this site, though Redis itself is case-insensitive about it. Arguments follow, space-separated; wrap any argument containing spaces in quotes.

Examples

Example 1: Connectivity and your first key

PING
SET greeting "Hello, Redis"
GET greeting
DEL greeting
GET greeting

Output:

PONG
OK
"Hello, Redis"
(integer) 1
(nil)

PING is the simplest possible command — it just confirms the server is alive and replies PONG. SET stores the string "Hello, Redis" under the key greeting and replies OK. GET retrieves it, printed with quotes because it’s a bulk string reply. DEL removes the key and reports how many keys were actually deleted as an integer (1). The final GET on a now-missing key returns (nil) — not an error, not an empty string, but Redis’s explicit “no such key” reply.

Example 2: A counter with EXISTS and TYPE

SET page:views 10
INCR page:views
GET page:views
EXISTS page:views
TYPE page:views

Output:

OK
(integer) 11
"11"
(integer) 1
string

INCR atomically parses the stored value as an integer, adds one, saves it back, and returns the new value directly as an integer reply — there’s no separate read-modify-write from your side, which matters because it stays atomic even under concurrent access. Note that even though the value behaves like a number, GET still shows it as a quoted string reply ("11"): Redis strings can hold text or numeric-looking data, and commands like INCR only interpret the content as an integer when needed. EXISTS returns 1 because the key is present, and TYPE confirms it’s stored as the string type.

Example 3: Expiration and how SET interacts with TTL

SET session:abc123 "user:42"
EXPIRE session:abc123 60
TTL session:abc123
SET session:abc123 "user:42-updated"
TTL session:abc123
EXPIRE session:abc123 60
SET session:abc123 "user:42-final" KEEPTTL
TTL session:abc123

Output:

OK
(integer) 1
(integer) 60
OK
(integer) -1
(integer) 1
OK
(integer) 60

This is one of the most important behaviors to internalize early: EXPIRE attaches a time-to-live in seconds to a key, and TTL reports how many seconds remain (-1 means the key exists but has no expiration; -2 would mean the key doesn’t exist at all). But a plain SET on an existing key overwrites the value and silently clears any TTL — that’s why the second TTL call returns -1 right after we re-set the value with a normal SET. Adding the KEEPTTL option to SET preserves whatever TTL was already on the key, which is why the final TTL still reports 60.

How It Works Step by Step

When you press Enter on a command like SET session:abc123 "user:42", this happens on the server:

1. The command and its arguments arrive over the TCP connection and are parsed by Redis’s request parser (using the RESP protocol).

2. Because Redis is single-threaded for command execution, this command is placed into the single execution path — no other command can run concurrently with it, so it either fully happens or hasn’t happened yet from any other client’s point of view.

3. Redis looks up the key in its main in-memory hash table (the core structure backing the entire keyspace). For a SET, it creates or overwrites the entry and, unless KEEPTTL was given, removes any expiration metadata associated with the old value.

4. If the server is configured for persistence, the write is also appended to the AOF (append-only file) log and/or marked dirty for the next RDB snapshot, depending on configuration — this happens asynchronously to the client reply in most setups.

5. The reply (OK) is encoded and sent back over the same connection, and redis-cli prints it.

Expiration itself is checked two ways: lazily, meaning any time a key is accessed, Redis first checks whether its TTL has passed and deletes it on the spot if so, returning as if it never existed; and actively, meaning a background cycle periodically samples a portion of keys with TTLs and proactively removes expired ones, so memory is reclaimed even for keys nobody ever reads again.

Common Mistakes

Mistake 1: Forgetting to quote multi-word values. Typing SET greeting Hello Redis without quotes sends three separate arguments instead of one, causing an arity error:

SET greeting Hello Redis

Output:

(error) ERR wrong number of arguments for 'set' command

Wrap the value in quotes instead: SET greeting "Hello Redis".

Mistake 2: Running a command against the wrong type. Every key has exactly one type, and Redis enforces that strictly:

LPUSH mylist:demo "a"
GET mylist:demo

Output:

(integer) 1
(error) WRONGTYPE Operation against a key holding the wrong kind of value

Use TYPE mylist:demo first if you’re unsure what a key holds, and use the matching command family (LRANGE, not GET, for a list).

Mistake 3: Assuming EXPIRE does something on a key that doesn’t exist. It quietly returns 0 instead of erroring, which is easy to miss:

EXPIRE nosuchkey:demo 30

Output:

(integer) 0

Always check the return value of EXPIRE (1 = TTL was set, 0 = key didn’t exist) rather than assuming success.

Mistake 4: Reaching for KEYS * to explore data. KEYS scans the entire keyspace in one blocking pass with O(N) time complexity, and because Redis is single-threaded, every other client is frozen until it finishes. On a production database with millions of keys this can cause a multi-second outage. Use SCAN 0 instead, which walks the keyspace incrementally via a cursor without blocking the server, covered in depth in a later lesson.

Best Practices

  • Always namespace keys with colons (user:1001:email) so related data is easy to scan and reason about.
  • Check command return values (0/1, (nil)) instead of assuming success — Redis replies are precise and meaningful.
  • Use TYPE key when debugging an unfamiliar key before running a type-specific command on it.
  • Never run KEYS * against a production instance; use SCAN for exploration.
  • Remember that a bare SET clears any existing TTL — use KEEPTTL if you need to update a value without resetting its expiration.
  • Set a TTL on any key that represents temporary data (sessions, caches, rate-limit counters) so memory isn’t leaked forever.

Practice Exercises

Exercise 1: Connect with redis-cli and set a key app:name to your favorite programming language. Confirm it with GET, then delete it and confirm GET now returns (nil).

Exercise 2: Create a key counter:visits with an initial value of 0, increment it three times with INCR, and check its final value with GET. It should end at 3.

Exercise 3: Set a key token:temp with a 30-second TTL using EXPIRE. Check TTL right away, then overwrite the key with a plain SET and check TTL again — explain in your own words why the number changed.

Summary

  • redis-cli is the interactive command-line client for talking directly to a Redis server.
  • Redis stores data as key-value pairs where every key has exactly one type, and mismatched-type commands fail with WRONGTYPE.
  • Redis is single-threaded for command execution, making every individual command atomic.
  • (nil) means “key not found,” not an error — distinguish it from actual (error) replies.
  • A plain SET clears any existing TTL unless you add KEEPTTL; use TTL to check remaining time and EXPIRE to set it.
  • Prefer SCAN over KEYS in anything beyond local experimentation.