Skip to content
Happy Programming Guide
Start learning
Python

Python Dictionary Key Checking with in

How to check whether a key exists in a Python dictionary, why the in operator beats has_key and try/except, and when .get() is the better answer.

A person working on a laptop

To check whether a key exists in a Python dictionary, use the in operator: if "email" in person:. It is fast, it reads like English, and it never raises. The older has_key() method was removed in Python 3, and wrapping the lookup in a try/except is usually more code for no benefit. This guide covers when in is right and when .get() or setdefault() is better.

The basic check#

Python
person = {"name": "Ana", "age": 34}

if "name" in person:
    print("found:", person["name"])

if "email" not in person:
    print("no email on file")

The one thing to remember: in looks at keys, not values.

Python
print("Ana" in person)            # False - Ana is a value
print("Ana" in person.values())   # True
print("name" in person.keys())    # True, but the .keys() is redundant

Writing in person.keys() is not wrong, just noisy. in person does exactly the same thing and is what other Python programmers expect to read.

Why has_key is gone#

Python
person.has_key("name")
Output
AttributeError: 'dict' object has no attribute 'has_key'

It was removed in Python 3. If you see it in a tutorial, that tutorial is written for Python 2 and everything else in it should be treated with suspicion too. The replacement is in, which is shorter and works on lists, sets, strings and tuples as well.

Often you do not need the check at all#

This pattern is extremely common and slightly wasteful:

Python
if "email" in person:
    email = person["email"]
else:
    email = "not provided"

.get() collapses it to one line, and looks the key up once instead of twice:

Python
email = person.get("email", "not provided")
email = person.get("email")   # None if missing

Use the explicit in check when the two branches do genuinely different things. Use .get() when you just want a fallback value.

Checking several keys at once#

Python
required = {"name", "email", "age"}

if required.issubset(person):
    print("all present")

missing = required - person.keys()
if missing:
    print("missing:", ", ".join(sorted(missing)))

person.keys() behaves like a set, so you can subtract from it directly. That is a much clearer way to report which fields are missing than a loop with a list of names.

Python
if any(k in person for k in ("phone", "mobile")):
    print("some contact number exists")

if all(k in person for k in required):
    print("all present")

Removing the check from loops#

Counting or grouping usually starts out like this:

Python
counts = {}
for word in words:
    if word in counts:
        counts[word] += 1
    else:
        counts[word] = 1

Three shorter versions, in increasing order of how idiomatic they are:

Python
for word in words:
    counts[word] = counts.get(word, 0) + 1
Python
from collections import defaultdict

counts = defaultdict(int)
for word in words:
    counts[word] += 1
Python
from collections import Counter

counts = Counter(words)
print(counts.most_common(3))

For grouping rather than counting, setdefault is the one-liner:

Python
by_city = {}
for person in people:
    by_city.setdefault(person["city"], []).append(person["name"])

The try/except alternative#

Python
try:
    value = person["email"]
except KeyError:
    value = "not provided"

This is valid and, in a loop where the key is almost always present, marginally faster — Python exceptions are cheap to set up and expensive only when raised. For readability, most people reach for .get() instead. Use try/except when several lines inside the block might fail, not for a single lookup.

Nested dictionaries#

Python
config = {"database": {"host": "localhost"}}

# Verbose but explicit
if "database" in config and "port" in config["database"]:
    port = config["database"]["port"]

# Chained get with a fallback at each level
port = config.get("database", {}).get("port", 5432)

The empty dictionary as the first fallback is what stops the second .get() from failing when database is missing entirely.

Why this is fast#

Python
big = {i: i * 2 for i in range(1_000_000)}

999_999 in big     # about as fast as on a ten-item dictionary

Dictionaries are hash tables. Checking a key computes one hash and looks in one place, rather than walking the contents. The same check on a list of a million items compares up to a million values. If you have a membership test inside a loop and your data is in a list, converting it to a set or dictionary first is usually the biggest speed-up available.

Questions people ask#

Does in check keys or values?

Keys. For values use value in d.values(), though be aware that is a linear scan and much slower on large dictionaries.

Is if key in d faster than try/except?

When the key is usually missing, yes — raising an exception costs more than a lookup. When the key is almost always present, try/except edges ahead. The difference rarely matters; write whichever is clearer.

Can I check for a key in a nested dictionary in one call?

Not with a built-in. Chain .get() calls with {} defaults, or write a small helper that splits a dotted path and walks it level by level.

What types can be dictionary keys?

Anything hashable: strings, numbers, tuples of hashables, frozensets. Lists and dictionaries cannot be keys because they can change, which would break the hash table.

Where to go next#

Python dictionaries explained, end to endRead next

Keep reading

Python

Python Basics

The core of Python in one page: variables, types, conditions, loops, functions and lists, each with a runnable example and the mistake…

4 min read

Keep going — pick your next guide

The fastest way to improve is to read one guide, then build the thing it describes. Start with the basics, or jump straight to a project.

Ask a question or share what worked

Your email address will not be published. Required fields are marked *