skip to content

What do the Redis commands `TTL` and `PTTL` return for a key that exists but has no expiry, versus a key that does not exist at all — and how do you remove an expiry without deleting the key?

level: juniorimportance: must knowfreq 58%

answer

  1. -1 = exists, no expiry
  2. -2 = key does not exist
  3. PTTL = same in milliseconds
  4. PERSIST → 1 removed / 0 nothing to remove
  5. EXPIRE with 0 or negative deletes the key

basics

~20 s

TTL returns the remaining seconds, -1 if the key exists with no expiry, and -2 if the key does not exist. PTTL is the same in milliseconds. PERSIST key removes the expiry and keeps the value, returning 1 if an expiry was removed, 0 otherwise.

solid answer

~50 s

`TTL key` returns a non-negative integer for the remaining seconds, and two sentinel values: - **`-1`** — the key exists but has no associated expiry (it is permanent). - **`-2`** — the key does not exist (either never created, or already expired and reclaimed). `PTTL` is identical but in milliseconds, which matters when TTLs are sub-second or when you need precision before re-applying one. The `-1` vs `-2` split exists since Redis 2.8; older versions returned `-1` for both, which is why some legacy client code treats "no TTL" and "missing key" as the same case. To drop an expiry without losing the value, use `PERSIST key`: it returns `1` if an expiry was removed and `0` if the key was missing or already permanent. `EXPIRE`/`PEXPIRE` similarly return `1` if the timeout was set and `0` if it was not (key missing, or a condition flag like `NX`/`GT` was not satisfied). A non-positive expiry deletes the key immediately rather than storing a past deadline.

code

text · 23 lines
text
> SET a 1
OK
> TTL a
(integer) -1        # exists, no expiry

> EXPIRE a 60
(integer) 1         # timeout was set
> TTL a
(integer) 60
> PTTL a
(integer) 59994     # millisecond precision

> PERSIST a
(integer) 1         # expiry removed, value kept
> PERSIST a
(integer) 0         # nothing left to remove

> TTL nosuchkey
(integer) -2        # key does not exist
> EXPIRE a -1
(integer) 1
> TTL a
(integer) -2        # a non-positive expiry deleted the key

go deeper

for a junior

Recite the three outcomes (remaining seconds, -1 no expiry, -2 no key) and that PERSIST clears an expiry while keeping the value.

for a middle

Add PTTL precision, the 1/0 return convention shared by EXPIRE and PERSIST, and that a non-positive expiry deletes the key.

for a senior

Use the -1 vs -2 distinction diagnostically — auditing a cache namespace for lost TTLs — and note the interaction with volatile eviction pools.

for a principal

Treat these as a contract the application layer should surface: distinguish miss from permanent-key in the client so TTL-loss bugs are observable rather than invisible.

## The return values `TTL key` answers "how long does this key have left?" and folds two exceptional cases into negative sentinels: | Return | Meaning | |---|---| | `n ≥ 0` | key exists and expires in about `n` seconds | | `-1` | key exists, **no expiry set** — it lives until deleted or evicted | | `-2` | **key does not exist** — never created, or already expired/deleted | `PTTL key` is the millisecond version with the same sentinels. Use it whenever you need precision: `TTL` rounds, so a key with 900 ms left reports `1`, and re-applying a TTL from a rounded `TTL` read slowly inflates or deflates deadlines. The distinction between `-1` and `-2` is genuinely useful in application code: `-1` on a key you believed was a cache entry is a bug signal (somebody overwrote it in a way that dropped the expiry), whereas `-2` is simply a miss. Conflating them hides the first case entirely. ## Historical note Before Redis 2.8, `TTL` returned `-1` for both "no expiry" and "no such key". Any code or tutorial that treats `-1` as "missing" is written against that old behavior. On any supported version today, check for `-2` to mean missing. ## Removing an expiry `PERSIST key` strips the expiry and leaves the value in place: - returns `1` when an expiry existed and was removed, - returns `0` when the key does not exist or had no expiry. This is how you promote a temporary key to a permanent one — for example, a draft that becomes a saved document, or a provisional lock entry you decide to keep. From Redis 6.2 you can also do it during a read with `GETEX key PERSIST`, which returns the value and clears the expiry atomically. Beware the interaction with eviction: making a key permanent removes it from the candidate pool of any `volatile-*` `maxmemory-policy`, so `PERSIST` on many keys can quietly shrink what Redis is allowed to evict. ## Related return conventions The expiry commands share a consistent reply style, which interviewers like to probe: - `EXPIRE key seconds` / `PEXPIRE` / `EXPIREAT` / `PEXPIREAT` → `1` if the timeout was set, `0` if not. "Not" means the key does not exist, or (Redis 7.0+) a supplied `NX`/`XX`/`GT`/`LT` condition was not met. - `PERSIST` → `1` / `0` as above. - `SET key value EX n` → the usual `OK`; it does not report anything about a previous TTL, which is exactly why the silent TTL-clearing behavior of plain `SET` surprises people. ## Non-positive expiries delete `EXPIRE key 0`, `EXPIRE key -1`, or an `EXPIREAT` with a timestamp already in the past does not store a weird deadline — Redis deletes the key straight away (propagating a `DEL` to replicas and the AOF). So computing a TTL as `deadline - now` and passing a negative result is a delete, not a no-op. Guard the arithmetic, or you will remove keys you meant to leave alone. ## Reading TTLs in practice `TTL`/`PTTL` are O(1) and safe to call in hot paths and in monitoring. Two common uses: 1. **Spot-checking cache hygiene.** `SCAN` over a prefix and `TTL` each key; any `-1` in a namespace that should be entirely volatile means some write path dropped the expiry. 2. **Debugging session or lock lifetime.** `PTTL lock:orders:42` tells you exactly how much lease a lock holder has left, which is far more useful than the second-rounded `TTL` when leases are short. One caution: `TTL` reflects the key's logical state. A key whose deadline has passed but which has not yet been physically reclaimed still reports `-2` and behaves as missing, because Redis checks expiry before answering. You never observe a negative-but-alive key.

  • When should you prefer `PTTL` over `TTL`?
    Whenever sub-second precision matters — short lock leases, sub-second caches, or any code that reads a remaining TTL and re-applies it elsewhere. `TTL` rounds to whole seconds, so repeatedly reading and re-applying it drifts the real deadline. `PTTL` also makes it possible to distinguish a key with 50 ms left from one with 950 ms, which `TTL` reports identically as 1.
  • What does `EXPIRE key 0` do?
    It deletes the key immediately rather than storing a zero or past deadline, and it returns 1 to indicate the operation applied. The same is true for any negative TTL or an `EXPIREAT` timestamp already in the past. This matters when a TTL is computed as `deadline - now`: if the deadline has passed, you are issuing a delete, so guard the arithmetic before sending the command.
  • You find keys in a cache namespace returning `TTL` of -1. What does that tell you?
    Those keys exist but carry no expiry, so something wrote them without one — most often a refresh path using a plain `SET`, which discards the previous expiry. They will never be reclaimed by expiration, and under a `volatile-*` eviction policy they are not even eviction candidates, so they grow the instance's floor permanently. The fix is at the write path: require an expiry in the cache client, or use `SET … KEEPTTL` on refresh.

saying these in an interview costs you the question

  • Reading `-1` as "key does not exist" — that is `-2` on any modern Redis.
  • Assuming `TTL` returns 0 for an expired key; expired keys report -2 and behave as missing.
  • Thinking `PERSIST` deletes the key instead of just removing its expiry.
  • Expecting `EXPIRE key 0` to be a harmless no-op rather than an immediate delete.
  • Round-tripping a TTL through the second-precision `TTL` command when milliseconds matter.

context