Check if a Key Exists in a Python Dictionary (in, get and More)

To check whether a Python dictionary contains a key, use in: "state" in customer returns True or False. It checks keys, not values, and it’s the fastest way to ask:

customer = {"name": "Ava", "state": "TX"}

if "state" in customer:
    print(customer["state"])

Values, get(), several keys at once and nested JSON each have a catch, so I’ll walk through them with real output from Python 3.14.7.

Diagram comparing a Python dictionary key lookup that jumps to one slot with a value search that scans every value
Checking a key jumps straight to it; checking a value has to look at each one.

Check if a key exists in a Python dictionary with in

Here’s in on a small customer record, plus the method people still find in old tutorials:

customer = {"name": "Ava", "state": "TX", "zip": None, "orders": 0}

print("'state' in customer ->", "state" in customer)
print("'email' in customer ->", "email" in customer)
print("'TX' in customer    ->", "TX" in customer, "  (in checks KEYS, not values)")
print()

try:
    customer.has_key("name")
except AttributeError as err:
    print("has_key() ->", err)

Output:

'state' in customer -> True
'email' in customer -> False
'TX' in customer    -> False   (in checks KEYS, not values)

has_key() -> 'dict' object has no attribute 'has_key'
Command Prompt showing Python in operator checking dictionary keys and has_key raising AttributeError
in checks keys only, and has_key() no longer exists.

"state" in customer is True because state is a key. "TX" in customer is False even though TX is in there, because it’s a value.

has_key() was removed in Python 3 and now raises AttributeError. Some tutorials still recommend it, so if you copy one and get that error, switch to in.

You’ll also see key in customer.keys(). It gives the same answer, but the .keys() part is redundant, so plain in is the idiomatic form.

Check a key without caring about upper or lower case

Keys are case-sensitive, which bites with things like HTTP headers. Compare against a lowercased set of keys:

headers = {"Content-Type": "application/json", "X-Request-Id": "8841"}

print("'content-type' in headers ->", "content-type" in headers)

lower_keys = {k.lower() for k in headers}
print("case-insensitive check    ->", "content-type" in lower_keys)

Output:

'content-type' in headers -> False
case-insensitive check    -> True

"content-type" isn’t a key because the real one is capitalized. Building lower_keys once and checking against it makes the test case-insensitive, and you can reuse the set for many checks.

Check if a dictionary contains a value

For values, ask the .values() view instead, or .items() for a key and value together:

customer = {"name": "Ava", "state": "TX", "zip": None, "orders": 0}

print("'TX' in customer.values()          ->", "TX" in customer.values())
print("('state', 'TX') in customer.items() ->", ("state", "TX") in customer.items())
print("('state', 'CA') in customer.items() ->", ("state", "CA") in customer.items())
print()

matches = [key for key, value in customer.items() if value == "TX"]
print("keys whose value is 'TX':", matches)

Output:

'TX' in customer.values()          -> True
('state', 'TX') in customer.items() -> True
('state', 'CA') in customer.items() -> False

keys whose value is 'TX': ['state']

"TX" in customer.values() finds the value, and a tuple in .items() checks that a specific key holds a specific value. That’s handy when the same value might sit under different keys.

Check for a partial match in keys or values

in needs the exact key. For a partial match, like any key containing a word, loop with any() or a comprehension:

inventory = {"iphone_16": 12, "iphone_16_pro": 4, "pixel_9": 7}

print("keys containing 'iphone':", [k for k in inventory if "iphone" in k])
print("any key starting with 'pixel'?", any(k.startswith("pixel") for k in inventory))
print("any value above 10?", any(v > 10 for v in inventory.values()))

Output:

keys containing 'iphone': ['iphone_16', 'iphone_16_pro']
any key starting with 'pixel'? True
any value above 10? True

These scan every key or value, so they’re slower than an exact in check on a big dictionary. For a few hundred entries you won’t notice.

To find which key holds a value, loop over .items() as in the last lines. Finding a key by its value covers that in depth.

Dictionary get() vs in: the None trap

get() looks like a shortcut for checking a key, and most of the time it works. Here’s where it doesn’t:

customer = {"name": "Ava", "state": "TX", "zip": None, "orders": 0}

print("customer.get('zip')   ->", customer.get("zip"), "  (key exists, value is None)")
print("customer.get('phone') ->", customer.get("phone"), "  (key does not exist)")
print("'zip' in customer     ->", "zip" in customer)
print()

if customer.get("orders"):
    print("has orders")
else:
    print("get('orders') looked falsy, but the key exists:", "orders" in customer)
print()

MISSING = object()
for key in ("zip", "phone"):
    print(f"{key!r:<8} missing? {customer.get(key, MISSING) is MISSING}")

Output:

customer.get('zip')   -> None   (key exists, value is None)
customer.get('phone') -> None   (key does not exist)
'zip' in customer     -> True

get('orders') looked falsy, but the key exists: True

'zip'    missing? False
'phone'  missing? True
Command Prompt showing Python dictionary get returning None for an existing key and a missing key alike
get() returns None both for a key set to None and for a missing key.

customer.get("zip") and customer.get("phone") both return None, but only one of those keys exists. get() can’t tell you which, and in can.

The orders check is the same trap in another form. The key exists with a value of 0, and if customer.get("orders"): treats that as missing.

When you need get() and must tell the cases apart, pass a unique default like MISSING = object(). Nothing in your data can be that object, so the check is exact.

Check if a key does not exist

not in is the natural way to guard against a missing key, for example before adding a default:

customer = {"name": "Ava", "state": "TX", "zip": None, "orders": 0}

if "email" not in customer:
    customer["email"] = "ava@example.com"

print(customer["email"])
print("setdefault on an existing key keeps it:", customer.setdefault("state", "CA"))

Output:

ava@example.com
setdefault on an existing key keeps it: TX

The email key didn’t exist, so it was added. setdefault() does the check and the insert in one call, and leaves an existing value alone, which is why state stayed TX.

For more ways to add entries, see adding items to a Python dictionary.

Check if a dictionary contains several keys at once

Form validation and API payloads often need a whole set of keys. .keys() behaves like a set, which makes this neat:

customer = {"name": "Ava", "state": "TX", "zip": None, "orders": 0}
required = {"name", "state", "email"}

print("has name and state? ", customer.keys() >= {"name", "state"})
print("has all required?   ", customer.keys() >= required)
print("missing keys:       ", required - customer.keys())
print()
print("all():", all(k in customer for k in ("name", "state")))
print("any():", any(k in customer for k in ("email", "phone", "zip")))

Output:

has name and state?  True
has all required?    False
missing keys:        {'email'}

all(): True
any(): True
Command Prompt showing Python checking multiple dictionary keys with keys set comparison all and any
keys() >= {...} checks them all at once, and set subtraction lists what’s missing.

customer.keys() >= required is true only if every required key is present. Subtracting customer.keys() from the required set gives the missing ones, ready for an error message.

Check a key across a list of dictionaries

Records from an API or a CSV usually arrive as a list of dictionaries, and not every record has every key:

orders = [
    {"id": 1042, "state": "TX", "coupon": "FALL26"},
    {"id": 1043, "state": "OH"},
    {"id": 1044, "state": "UT", "coupon": "FALL26"},
]

print("any order has a coupon?  ", any("coupon" in o for o in orders))
print("every order has a state? ", all("state" in o for o in orders))
print("orders with a coupon:    ", [o["id"] for o in orders if "coupon" in o])

Output:

any order has a coupon?   True
every order has a state?  True
orders with a coupon:     [1042, 1044]

any() answers “does at least one record have it”, all() answers “do they all”, and a comprehension lists which ones. Each check inside is a fast key lookup.

all() and any() read well for a short fixed list. I use the set version when I want to report which keys are missing, not just that something is.

Check nested keys and JSON data

JSON loads into nested dictionaries, and a missing level in the middle breaks a direct lookup:

import json

payload = json.loads('{"customer": {"name": "Ava", "address": {"city": "Austin", "zip": "78701"}}}')

print("'customer' in payload:", "customer" in payload)
print("city:   ", payload.get("customer", {}).get("address", {}).get("city"))
print("billing:", payload.get("customer", {}).get("billing", {}).get("city"))

try:
    payload["customer"]["billing"]["city"]
except KeyError as err:
    print("direct lookup -> KeyError:", err)

Output:

'customer' in payload: True
city:    Austin
billing: None
direct lookup -> KeyError: 'billing'

Chaining .get("key", {}) gives each level an empty dictionary to fall back on, so a missing billing section returns None instead of crashing.

The direct lookup raised KeyError: 'billing' at the first missing level. For deeply nested data you check often, a small helper function that walks a list of keys keeps this readable.

Handle a missing key with try and except KeyError

Sometimes it’s cleaner to just use the key and handle the failure, especially when a missing key is rare:

prices = {"coffee": 3.50, "bagel": 2.25}

for item in ("coffee", "muffin"):
    try:
        print(f"{item}: ${prices[item]:.2f}")
    except KeyError as err:
        print(f"{item}: not on the menu (KeyError: {err})")

Output:

coffee: $3.50
muffin: not on the menu (KeyError: 'muffin')

Python style often prefers this: try the lookup and catch KeyError. It avoids looking the key up twice, once to check and once to use it.

The Python dictionary KeyError guide covers the error itself, including why the message shows only the key.

How fast is checking a key in a Python dictionary?

Checking a key takes about the same time whether the dictionary holds ten items or a million. Values are a different story:

import timeit

big = {i: str(i) for i in range(1_000_000)}

key_ns = min(timeit.repeat(lambda: 999_999 in big, number=10_000, repeat=3)) / 10_000 * 1e9
keys_ns = min(timeit.repeat(lambda: 999_999 in big.keys(), number=10_000, repeat=3)) / 10_000 * 1e9
value_ms = min(timeit.repeat(lambda: "999999" in big.values(), number=10, repeat=3)) / 10 * 1e3

print(f"key in dict:        {key_ns:,.0f} ns")
print(f"key in dict.keys(): {keys_ns:,.0f} ns")
print(f"value in values():  {value_ms:,.1f} ms")
print(f"the value search took about {value_ms * 1e6 / key_ns:,.0f}x as long")

Output:

key in dict:        74 ns
key in dict.keys(): 129 ns
value in values():  21.9 ms
the value search took about 294,433x as long
Command Prompt timing Python dictionary key lookup against keys and a search through values on a million items
A key lookup takes nanoseconds; searching a million values takes milliseconds.

A key check is a hash lookup, so its time barely depends on the size of the dictionary. Searching .values() has to walk through the values, which on a million items was hundreds of thousands of times slower.

If you check values often, build a reverse dictionary once, mapping values back to keys. The exact timings change from run to run; the gap doesn’t.

Surprising matches: True, 1 and unhashable keys

Two last cases that catch people out:

flags = {True: "on"}
print("1 in {True: 'on'} ->", 1 in flags)

codes = {1: "one"}
print("1.0 in {1: 'one'} ->", 1.0 in codes)
print()

teams = {"a": [1, 2]}
try:
    [1, 2] in teams
except TypeError as err:
    print("[1, 2] in dict -> TypeError:", err)
print("[1, 2] in dict.values() ->", [1, 2] in teams.values())

Output:

1 in {True: 'on'} -> True
1.0 in {1: 'one'} -> True

[1, 2] in dict -> TypeError: cannot use 'list' as a dict key (unhashable type: 'list')
[1, 2] in dict.values() -> True
Command Prompt showing Python dictionary treating True and 1 as the same key and rejecting a list as a key
1 finds a True key, and a list can’t be checked as a key at all.

True, 1 and 1.0 are equal and hash the same, so they count as the same key. Mixing them as keys in one dictionary is asking for trouble. The difference between is and == explains why they compare equal.

A list can’t be a key, so [1, 2] in teams raises TypeError rather than returning False. It can still be a value, and in teams.values() finds it.

For more dictionary work, these fit well next:

Frequently asked questions

How do I check if a key exists in a Python dictionary?

Use in: if key in my_dict:. It returns True or False and checks keys only.

How do I check if a dictionary contains a value?

Use value in my_dict.values(), or (key, value) in my_dict.items() for a specific pair.

Is get() a good way to check if a key exists?

Not on its own. get() returns None both for a missing key and for a key whose value is None. Use in.

Why does has_key() give an AttributeError?

has_key() was removed in Python 3. Replace d.has_key(k) with k in d.

How do I check if a key does not exist?

Use not in: if key not in my_dict:.

How do I check multiple keys at once?

Compare the keys view with a set: my_dict.keys() >= {"a", "b"}. Subtracting gives the missing keys.

What is the time complexity of checking a key in a dictionary?

On average it’s constant time, O(1), because keys are hashed. Searching values is O(n). The Python dictionary documentation lists every dictionary operation.