Redis Error: WRONGTYPE — Wrong Key Type
127.0.0.1:6379> SET user:42 '{"name":"Alice"}'
OK
127.0.0.1:6379> LPUSH user:42 'role:admin'
(error) WRONGTYPE Operation against a key holding the wrong kind of value
WRONGTYPE is Redis telling you “this key holds a different data type than the command expects.” Each Redis key is locked to a single type from its first write until it’s deleted. Mixing operations across types — or worse, two code paths writing different types to the same key — is the only way WRONGTYPE happens.
The single most effective preventive habit is type-suffixed key naming: user:42:profile (string), user:42:tags (set), user:42:counters (hash). The key name encodes the shape, the shape is visible at every read site, and accidental cross-type usage becomes a code-review issue rather than a runtime bug. Combined with a small TYPE check at sensitive call sites, you can eliminate WRONGTYPE almost entirely.
Why this happens
- Two code paths writing different types to the same key. One service writes a JSON string with `SET user:42 '{...}'`; another tries to push a tag with `LPUSH user:42 ...`. Both look correct in isolation but collide at runtime. WRONGTYPE is the symptom of an unmapped namespace boundary.
- Migration left old-typed keys behind. You changed the schema from `SET user:42:tags 'admin,beta'` to `SADD user:42:tags admin beta` but didn't run the migration. New code hits old keys and fires WRONGTYPE on every read.
- Cache key clash with another feature. Two unrelated features chose the same key prefix (`session:abc`). One uses HSET (hash), the other GET (string). Neither team realises until a customer hits both code paths in the same session.
- SET overwriting a complex type's key. If a feature writes `SET key value` to a key that previously held a list/hash/set, Redis allows the SET (it replaces the value entirely with a string). Subsequent commands against the old type fail. Sometimes the bug is that SET *succeeded* — the destruction is silent.
- TTL-zero key returning unexpectedly. A key that expired and was rewritten by the wrong code path may now have a different type than your reader expects. Cache stampedes after TTL expiry are a common WRONGTYPE source.
How to fix it
Fixes are ordered by likelihood. Start with the first one that matches your context.
1. Check the key''s type before mixing operations
Use `TYPE <key>` (returns `none`, `string`, `list`, `set`, `zset`, `hash`, `stream`) and branch your code defensively. Better yet, namespace keys so types are obvious from the name.
import { createClient } from 'redis';
const redis = createClient();
await redis.connect();
async function appendTag(userId, tag) {
const key = `user:${userId}:tags`; // type-suffixed
const type = await redis.type(key);
if (type !== 'set' && type !== 'none') {
throw new Error(`Expected set at ${key}, got ${type}`);
}
await redis.sAdd(key, tag);
}
2. Namespace keys by type explicitly
`user:42:profile` for the JSON string, `user:42:tags` for the set, `user:42:counters` for the hash. The key name contains the data shape; new contributors can't accidentally mix operations.
3. Use a Lua script to overwrite a key atomically
To replace a key's type without a race, run `DEL` and the new write in a single Lua script. EVAL is atomic in Redis, so no other client can observe the intermediate empty state.
-- KEYS[1] = key, ARGV = elements to SADD
redis.call('DEL', KEYS[1])
for i = 1, #ARGV do
redis.call('SADD', KEYS[1], ARGV[i])
end
return #ARGV
4. Catch WRONGTYPE in code as a domain signal
For migration code, treat WRONGTYPE as "old-format key, run the convertor." For application code, surface it loudly — WRONGTYPE in steady state is a bug, not a transient.
import redis
r = redis.Redis()
def get_tags(user_id):
key = f"user:{user_id}:tags"
try:
return r.smembers(key)
except redis.exceptions.ResponseError as e:
if 'WRONGTYPE' not in str(e):
raise
# Old format: comma-separated string. Migrate.
raw = r.get(key)
tags = set(raw.decode().split(',')) if raw else set()
pipe = r.pipeline()
pipe.delete(key)
if tags:
pipe.sadd(key, *tags)
pipe.execute()
return tags
5. Audit your codebase for shared key prefixes
`grep -r "redis.*\"user:"` (and similar) across services. Any prefix used by multiple commands of different types is a future WRONGTYPE waiting to fire. Promote shared prefixes to constants in a single shared config file.
Detection and monitoring in production
Surface WRONGTYPE as its own metric — it's almost always a bug, never a transient. A single occurrence in production warrants investigation. Tag by key prefix in your error tracker so you see clusters around specific features. Redis Slowlog and `MONITOR` can capture the offending command sequence during incidents.
Related errors
- redisOOMRedis hit its `maxmemory` limit and the configured `maxmemory-policy` is `noeviction` (the default), so it refuses every write. Reads still work; commands that allocate memory return `OOM command not allowed when used memory > 'maxmemory'`.
- postgres23505Your INSERT or UPDATE produced a row whose unique-constrained column(s) already exist in the table. Postgres SQLSTATE 23505 — `unique_violation` — is raised when a UNIQUE or PRIMARY KEY index would otherwise contain duplicate values.
Frequently asked questions
Why doesn't Redis just convert types automatically? +
How do I see what type a key currently is? +
Can SET overwrite a list or hash? +
Should I retry on WRONGTYPE? +
Does Redis Cluster make WRONGTYPE more likely? +
Will EXPIRE on a WRONGTYPE key fail? +
How do I prevent WRONGTYPE in a multi-service setup? +
Can a Lua script return WRONGTYPE? +
When to escalate to Redis support
No vendor escalation needed for WRONGTYPE — it's purely a code-side issue. If you see WRONGTYPE on a key your service is the sole writer of, audit deploys around the failure timestamp; usually a recently-deployed code path is mis-using the key. Snapshot the key with `DEBUG OBJECT <key>` for forensic detail before deleting and re-creating.