Where it goes wrong
The trouble starts when a value can legitimately be 0 or "":
result = find(items, "a") # returns index 0
if not result:
print("not found") # WRONG
Index 0 is falsy, so "found at the first position" and "not found at all" take the same branch. Nothing raises. The program is simply wrong for one input, and that input is the first element, which many test cases skip.
The page runs exactly this, printing the wrong answer and then the fix.
is None asks a different question
if result is None:
This tests for one specific object, not for emptiness. It is true for None and nothing else — not for 0, not for "". When a function returns "the thing, or None if there isn't one", this is the only correct test.
The rule that follows: use truthiness when you mean "empty or zero or missing, and I treat them the same". Use is None when None means something distinct from a legitimate empty value.
Absent is not the same as empty
None means "there is no value here". An empty string, an empty list and zero are all values - they just happen to be falsy. Treating the two as interchangeable is the source of a whole family of quiet bugs.
if not name: # true for None AND for ""
if name is None: # true only for None
The first is right when you mean "nothing useful to show". The second is right when you mean "this was never set", and the difference matters the moment an empty value is legitimate: a comment field left blank, a quantity of zero, a list with nothing in it yet.
The clearest example is a count. if not count: treats zero as missing, so a genuine result of zero takes the "no data" branch. if count is None: does not.
Why is and not ==
None is a singleton: there is exactly one of it in a running program, so identity and equality give the same answer for it, and identity is both faster and impossible to override. A class can define __eq__ to make x == None return anything it likes; nothing can make x is None lie.
That is the whole reason the convention exists, and the same reasoning applies to True and False, although comparing to those at all is usually redundant - if flag: says it better than if flag is True:.
Functions that return None
A function with no return returns None, and so does one whose return has no value. This is why forgetting to return a result produces NoneType errors somewhere further along rather than at the function itself.
The in-place methods do it deliberately - sort, reverse, append, update all return None to signal that they mutated the object rather than producing a new one. x = x.sort() is the classic result, and the None is the library telling you it did the work already.
Defaults that respect zero
The or shortcut for defaults is common and slightly wrong:
timeout = given or 30 # 0 becomes 30
timeout = 30 if given is None else given
The first discards any falsy value, including a deliberate zero or an empty string. The second only fills in when the value is genuinely absent. When the domain includes zero, empty text or an empty list as real values - and it usually does - the second form is the one that behaves.
Checking emptiness
For containers, the truthiness test is idiomatic and preferred:
if not items: # yes
if len(items) == 0: # works, but noisier
The exception is when items might be None, because not None is also true. Then you need to say which case you mean.
The find() trap, run
The classic case, with all three answers printed side by side:
def find(items, target):
for i, item in enumerate(items):
if item == target:
return i
return None
items = ["a", "b"]
for target in ["a", "b", "z"]:
result = find(items, target)
print(target, result,
"not found" if not result else "found",
"|", "not found" if result is None else "found")
a 0 not found | found
b 1 found | found
z None not found | not found
The middle column is the bug. find returned 0 — the item *was* found, at the first position — and not result reported it as missing. The right-hand column, testing is None, is correct for all three.
Notice which case fails: the first element. A test suite that searches for something in the middle of a list passes, and the bug waits for real data where the match happens to be first. That is the characteristic shape of truthiness bugs — they are correct for most inputs, which is why they survive review.
Giving your own objects a truth value
Truthiness is not a fixed list. Python asks the object, and any class can answer.
When you write if obj:, Python calls __bool__ if the class defines one. If it does not, it falls back to __len__ and treats zero as false. If neither exists, the object is always truthy — which is why an instance of a plain class you wrote is true even when it holds nothing.
class Basket:
def __init__(self, items):
self.items = items
def __len__(self):
return len(self.items)
print(bool(Basket([])), len(Basket([])), bool(Basket([1])))
False 0 True
Defining __len__ gave the class a sensible truth value for free, which is why it is the usual choice for anything container-like. __bool__ is for objects that have a notion of empty without a length — a connection that is open or closed, a result that succeeded or failed.
The caution is that this is exactly the mechanism that makes truthiness ambiguous. An object that is falsy when empty cannot be distinguished from None by if obj:, so any code that treats "absent" and "empty" differently still has to use is None. Some libraries make this sharp: a pandas DataFrame raises rather than guessing when you put it in a condition, on the grounds that "empty" and "all values false" are both plausible readings.
When None itself is a valid value
Occasionally None is a legitimate value in the data, and then it cannot also mean "absent". The standard answer is a sentinel: a unique object that means nothing else.
MISSING = object()
def get(mapping, key, default=MISSING):
if key in mapping:
return mapping[key]
if default is MISSING:
raise KeyError(key)
return default
object() creates something that is equal only to itself, so default is MISSING is true exactly when the caller did not pass a default. Now get(d, "k", None) and get(d, "k") mean different things — the first supplies None as the default, the second asks for an error — which is impossible if None is itself the sentinel.
This is not a common need, and it is worth recognising because the standard library uses it. dict.pop distinguishes "no default given" from "default of None" in exactly this way, and several libraries expose their sentinel by name so callers can test against it.
The plainer version of the same idea is a module-level constant with a name that says what it means, which reads better in a traceback than <object object at 0x...>.
Where None comes from
None rarely gets written into your data deliberately. It arrives, and knowing the sources makes it predictable rather than mysterious.
A function that did not return. Any function reaching its end without a return returns None, and so does a bare return. This is the biggest source: a function with a return inside an if and nothing after it returns None for every input that misses the branch, silently.
An in-place method. sort, reverse, append, update and their relatives all return None on purpose, so x = x.sort() replaces a list with nothing.
A lookup with a default. d.get(k) returns None when the key is absent, as do os.environ.get, re.match when the pattern does not match, and a great many library functions that mean "nothing here".
A database or JSON null. null in JSON becomes None, and a nullable column comes back the same way. This is the one where None is genuinely part of the data rather than a signal, and where the "absent versus empty" distinction matters most.
An uninitialised attribute. A class that sets self.result = None in __init__ and fills it in later hands out None to anything that asks too early.
The pattern across all five is that None marks the absence of a value rather than being one. When it turns up somewhere unexpected, the productive question is which of these produced it — and the answer is usually the first.
Saying "might be None" out loud
The failure mode of None is that it travels. A function returns it, the caller passes it on, and the AttributeError: 'NoneType' object has no attribute ... appears three functions away from the one that produced it.
Type hints are the cheapest defence, because they make the possibility part of the signature:
def find_user(name: str) -> User | None:
...
That says, to a reader and to a type checker, that every caller has to handle the None case. A checker will flag find_user(x).email as an error before it runs. On Python before 3.10 the spelling is Optional[User] from typing, which means exactly the same thing.
The hint changes nothing at runtime, and that is fine — the value is in making the contract explicit. A function whose return type is User and which sometimes returns None is lying, and the fix is either to make the hint honest or to raise instead of returning nothing.
Raising is often the better answer. If a caller has no sensible response to "not found", returning None just moves the crash somewhere less informative. Returning None is right when absence is ordinary and the caller will branch on it; raising is right when absence means something has gone wrong.
Questions people ask
Is if x: faster than if len(x) > 0:? Marginally, and that is not the reason to prefer it. It reads better and works on anything.
Why is "False" true? Because it is a non-empty string. Truthiness asks about emptiness, not about meaning.
Should I write if x == None? No. is None is the convention, is faster, and cannot be overridden by a class.
What is falsy that people forget? 0.0, Decimal(0), range(0), and datetime.time(0, 0) in older Python — midnight was falsy until 3.5, which caused real bugs.
How do I test "not None and not empty"? if x: covers both when None and empty should be treated alike. When they should not, say both: if x is not None and len(x):.
Does if not x work on a generator? Not usefully. A generator is always truthy, even if it will produce nothing.
Why does numpy raise on if array:? Because an array with several elements has no single truth value, and guessing would be wrong half the time.
Is None the same as null in other languages? It plays the same role. The difference is that Python's is a real object with a type, so type(None) works and None.__class__ is NoneType.
Can I subclass NoneType or make another None? No. NoneType cannot be instantiated, which is what guarantees is None is reliable.
Why does print(f()) show None for my function? Because the function returned nothing. Usually a return is missing on one branch.
How should a function signal "no result"? Return None when absence is ordinary and the caller will branch on it. Raise when absence means something has gone wrong and the caller has no sensible alternative.
Is there a way to make None falsy checks safe? Not by changing None. The fix is always to say which question you are asking — emptiness or absence.
Recap in one screen
- The falsy values are
None, False, 0, 0.0, "", [], {} and set(); everything else is truthy, including "0" and "False". if not x: means "empty, zero or missing, and I treat them the same".x is None means "this was never set", and is the only correct test when zero or empty is a legitimate value.- Use
is for None because there is exactly one of it and __eq__ cannot interfere. - Your own classes get truthiness from
__bool__, then __len__, and are otherwise always true.