To check if a dictionary is empty in Python, put it straight into an if:
if not my_dict:
print("the dictionary is empty")
An empty dict is falsy, so nothing else is needed. The catch is that None is falsy too, which means that test quietly passes for a dictionary that was never built at all.
Below are the four ways people write this check, which one to prefer, and the three cases where the obvious answer is wrong. Output comes from a run on Python 3.12.5.

not d answers True for an empty dict and for None alike.Four ways to check if a dictionary is empty
All four agree on a plain dictionary:
settings = {}
print("four ways to ask the same question:")
print(" not settings ->", not settings)
print(" len(settings) == 0 ->", len(settings) == 0)
print(" settings == {} ->", settings == {})
print(" not bool(settings) ->", not bool(settings))
print()
settings = {"theme": "dark"}
print("and once it has something in it:")
print(" not settings ->", not settings)
print(" len(settings) == 0 ->", len(settings) == 0)
print(" settings == {} ->", settings == {})
print(" not bool(settings) ->", not bool(settings))
Output:
four ways to ask the same question:
not settings -> True
len(settings) == 0 -> True
settings == {} -> True
not bool(settings) -> True
and once it has something in it:
not settings -> False
len(settings) == 0 -> False
settings == {} -> False
not bool(settings) -> False

| Check | Reads as | Verdict |
|---|---|---|
if not d: | “if there is nothing in d” | Preferred. Shortest and idiomatic. |
if len(d) == 0: | “if d has zero keys” | Fine, and explicit. Useful in code reviewed by beginners. |
if d == {}: | “if d equals an empty dict” | Works, but builds a throwaway dict to compare against. |
if not bool(d): | “if d is falsy” | Redundant. if already calls bool. |
Why not my_dict is the Pythonic choice
Python treats empty containers as false, so the conversion is automatic:
config = {}
# the idiomatic form
if not config:
print("no config loaded")
# the same test, spelled out
if len(config) == 0:
print("still no config loaded")
# an empty dict is falsy, so this works too
if config:
print("this line never runs")
else:
print("empty dicts are falsy")
print()
print("bool of an empty dict ->", bool({}))
print("bool of a full dict ->", bool({"a": 1}))
print()
print("that falsiness is why `if not config:` is enough on its own.")
Output:
no config loaded
still no config loaded
empty dicts are falsy
bool of an empty dict -> False
bool of a full dict -> True
that falsiness is why `if not config:` is enough on its own.
if d: already runs bool(d) behind the scenes, which is why wrapping it in bool() adds nothing. The same rule covers empty lists, tuples, sets and strings, so the habit carries across.
Style guides favour the short form for exactly this reason. It behaves the same for dictionaries you have just looked a key up in and for ones built from scratch.
Why the check passes when the dictionary is None
This is where the short check goes wrong:
empty = {}
missing = None
print("both of these pass the same test:")
print(" not empty ->", not empty)
print(" not missing ->", not missing, " <- None is falsy too")
print()
print("so `if not d:` cannot tell 'the dict is empty'")
print("apart from 'the dict was never built'.")
print()
try:
len(missing)
except TypeError as err:
print("len(missing) -> TypeError:", err)
print()
print("these do tell them apart:")
print(" missing == {} ->", missing == {})
print(" missing is None ->", missing is None)
print(" isinstance(missing, dict) ->", isinstance(missing, dict))
print()
print("if a function can return None, check for that first:")
for value in ({}, None, {"a": 1}):
if value is None:
print(" None -> never loaded")
elif not value:
print(" {} -> loaded but empty")
else:
print(f" {value} -> has data")
Output:
both of these pass the same test:
not empty -> True
not missing -> True <- None is falsy too
so `if not d:` cannot tell 'the dict is empty'
apart from 'the dict was never built'.
len(missing) -> TypeError: object of type 'NoneType' has no len()
these do tell them apart:
missing == {} -> False
missing is None -> True
isinstance(missing, dict) -> False
if a function can return None, check for that first:
{} -> loaded but empty
None -> never loaded
{'a': 1} -> has data

not None is True as well, so the short check cannot tell the two apart.An empty dictionary and a missing dictionary mean different things. One says the data loaded and there was none; the other says the data never arrived. If a function can return None, test for that first.
len(None) raises TypeError: object of type 'NoneType' has no len(), which at least fails loudly. d == {} and isinstance(d, dict) both return False for None, so either one separates the cases cleanly.
A dictionary full of falsy values is not empty
Emptiness is about keys, not about what those keys hold:
record = {"name": None, "count": 0, "note": ""}
print("record ->", record)
print("not record ->", not record, " it is NOT empty")
print("len(record) ->", len(record))
print()
print("every VALUE is falsy, but the dict still has three keys.")
print("emptiness is about keys, not about what the keys hold.")
print()
print("to ask whether every value is falsy, say so explicitly:")
print(" all(not v for v in record.values()) ->", all(not v for v in record.values()))
print()
print("and to drop the empty entries:")
print(" {k: v for k, v in record.items() if v} ->", {k: v for k, v in record.items() if v})
Output:
record -> {'name': None, 'count': 0, 'note': ''}
not record -> False it is NOT empty
len(record) -> 3
every VALUE is falsy, but the dict still has three keys.
emptiness is about keys, not about what the keys hold.
to ask whether every value is falsy, say so explicitly:
all(not v for v in record.values()) -> True
and to drop the empty entries:
{k: v for k, v in record.items() if v} -> {}
Three keys means three keys, even when every value is None, 0 or an empty string. If what you actually care about is whether anything useful is stored, say that with all(not v for v in d.values()).
Stripping the blank entries is a one-line comprehension, which is often what people were reaching for. There is more on walking a dictionary in iterating through a dictionary.
Checking a defaultdict can fill it
This one bites quietly:
from collections import defaultdict
counts = defaultdict(list)
print("a fresh defaultdict is empty:")
print(" not counts ->", not counts, "| len:", len(counts))
print()
print("now inspect one key to see what is there:")
print(" counts['users'] ->", counts["users"])
print()
print("and check again:")
print(" not counts ->", not counts, "| len:", len(counts))
print(" the lookup CREATED the key, so the check changed the answer")
print()
print("use .get() or `in` to look without creating:")
other = defaultdict(list)
print(" other.get('users') ->", other.get("users"))
print(" 'users' in other ->", "users" in other)
print(" not other ->", not other, "| len:", len(other))
Output:
a fresh defaultdict is empty:
not counts -> True | len: 0
now inspect one key to see what is there:
counts['users'] -> []
and check again:
not counts -> False | len: 1
the lookup CREATED the key, so the check changed the answer
use .get() or `in` to look without creating:
other.get('users') -> None
'users' in other -> False
not other -> True | len: 0

defaultdict creates it, so the dictionary is no longer empty.A defaultdict manufactures a value the moment you read a missing key. Printing one to see what is in there therefore adds a key, and the emptiness check that follows returns the wrong answer.
.get() and the in operator both look without writing, so use those while inspecting. Counting keys afterwards is covered in counting the keys in a dictionary.
Is not d faster than len(d) == 0?
Not in a way that matters:
import timeit
setup_empty = "d = {}"
setup_big = "d = {i: i for i in range(1000000)}"
print("2,000,000 checks on an EMPTY dict:")
for label, snippet in [("not d ", "not d"),
("bool(d) ", "bool(d)"),
("len(d) == 0 ", "len(d) == 0"),
("d == {} ", "d == {}")]:
print(f" {label} {timeit.timeit(snippet, setup_empty, number=2000000):.3f}s")
print()
print("2,000,000 checks on a dict holding 1,000,000 keys:")
print(f" len(d) == 0 {timeit.timeit('len(d) == 0', setup_big, number=2000000):.3f}s")
print()
print("len() reads a stored count, so the size of the dict makes no difference.")
print("All four are constant time; pick the one that reads best.")
Output:
2,000,000 checks on an EMPTY dict:
not d 0.034s
bool(d) 0.049s
len(d) == 0 0.064s
d == {} 0.066s
2,000,000 checks on a dict holding 1,000,000 keys:
len(d) == 0 0.129s
len() reads a stored count, so the size of the dict makes no difference.
All four are constant time; pick the one that reads best.
