In Python, how does `if obj:` decide truthiness for an instance of a custom class?
answer
- An `if` converts before it branches
- Two hooks are tried in a fixed order
- A missing hook falls back to a count
- Neither hook defined has a default answer
- The lookup happens on the type
basics
~20 sif obj: calls bool(obj), which looks for __bool__ on the object's type. If there is none, Python falls back to __len__, where a length of 0 means false. With neither defined, every instance is true.
solid answer
~40 sTruth-testing is a conversion: `if obj:` runs `bool(obj)`, and `bool()` resolves in three steps. It looks for `__bool__` on the **type** and uses its return value; failing that it calls `__len__` and treats `0` as false; failing both it answers **true**. `object` defines no `__bool__`, so a class that opts into neither hook is truthy no matter how empty its state is — which is why a wrapper or a generator object is always truthy. The lookup is on the type, never the instance, so assigning `obj.__bool__` at runtime changes nothing. If a class defines both hooks, `__bool__` wins and `__len__` is never consulted for truth.
code
python · 15 linesclass Basket:
def __init__(self, items):
self._items = list(items)
def __len__(self):
return len(self._items)
class Plain:
pass
print(bool(Basket([]))) # False: __len__ returned 0
print(bool(Basket(["sensor"]))) # True
print(bool(Plain())) # True: neither hook definedgo deeper
Recall the order — __bool__, then __len__, then true by default — and be able to say that a class defining neither is always truthy. That single fact catches the most common bug in this area.
Explain that the lookup is on the type, not the instance, and that __bool__ short-circuits __len__ completely. Be ready to name where the conversion happens besides if: while, not, and/or, comprehension guards.
Show the consequence in real code: wrapper and proxy classes that are always truthy make emptiness checks silently pass. Be ready to say which hook you would define for a given type and why one is exact while the other is a coarser question.
Own the API-design angle: whether a type should be truth-testable at all. Implicit conversion is invisible at the call site, so a type whose emptiness is ambiguous or costly is often better served by an explicit predicate method than by opting into the protocol.
### What "truthy" actually means Every place Python needs a yes/no answer about an object — `if obj:`, `while obj:`, `not obj`, the operands of `and` and `or`, a comprehension's `if` clause, `filter(None, seq)` — runs the same conversion: it turns the object into a real boolean. In pure-Python terms that conversion is `bool(obj)`; `operator.truth(obj)` is the same operation exposed as a plain function. Nothing about `if` is special: the statement is `bool()` plus a jump. ### The three-step resolution For an instance of a class you wrote, `bool()` resolves in exactly this order: 1. Look for `__bool__` **on the type**. If it exists, call it with no arguments and use what it returns — which must be an actual `bool`. 2. Otherwise look for `__len__` on the type. If it exists, call it; the object is false when the length is `0`, true for any other (non-negative) length. 3. If the type defines neither, the object is **true**. Always. Step 3 is the step candidates get wrong. There is no inherited `__bool__` to fall back on: `hasattr(object, "__bool__")` is `False`, so a bare class opts into nothing and every instance — freshly constructed, half-initialised, holding an empty list, holding nothing at all — is truthy. `bool(Plain())` is `True` for `class Plain: pass`, and no amount of empty state changes that. ### The lookup happens on the type, not the instance Implicit special-method invocation bypasses the instance dictionary. Python looks up `__bool__` on `type(obj)` and its MRO, never on `obj.__dict__`. So this does nothing: ```python obj = Plain() obj.__bool__ = lambda: False print(bool(obj)) # still True ``` The same rule governs `__len__`, `__iter__`, `__eq__` and the rest of the protocol methods. If you want per-instance truthiness, the *class* must define a hook that consults per-instance state. ### Choosing between the two hooks Define `__len__` when the object genuinely has a size. You get truthiness for free, it matches how every stdlib container behaves, and it registers structurally as `collections.abc.Sized`. Define `__bool__` when there is no meaningful count (a connection that is open or closed, a result that succeeded or failed) or when emptiness is far cheaper to determine than the exact count. If a class defines both, `__bool__` wins unconditionally — `__len__` is never consulted for truth. That is occasionally deliberate: a buffer with five queued items can still report itself as "not ready" through `__bool__` while `len()` stays exact. It is more often an accident, and it is worth saying out loud in a review. ### Where this bites in real code * **Wrapper and proxy objects are always truthy.** A `Response`, `Result` or lazy-handle class that wraps a payload but defines no hooks answers `True` even when the payload is empty. `if response:` then tests "did I get an object back", not "is there anything in it" — two very different claims that read identically. * **Generator objects are always truthy**, exhausted or not: they define neither hook, so `if gen:` is `True` after the last value has been consumed. Probe with `next(gen, sentinel)` or materialise the values instead. * **A class that models zero** — a duration, an offset, an amount — is truthy at zero unless its author says otherwise. The stdlib itself changed its mind here: `datetime.time(0, 0)` (midnight) was falsy before Python 3.5 and has been truthy since, precisely because "a valid time that happens to be midnight" reading as false caused real bugs. * **Inheriting from a sized base changes truthiness for free.** Subclass something that defines `__len__` and your instances become falsy when empty whether you thought about it or not. ### Saying it in an interview The compact answer is: *"`if x:` calls `bool(x)`, which tries `__bool__` on the type, falls back to `__len__ != 0`, and defaults to true when neither exists — so my own classes are truthy until I opt in."* Then show that you know the consequence: an emptiness check written against a class that defines no hooks silently never fires. The follow-up an interviewer usually reaches for is the exhausted generator, or "what if `__bool__` returns `1`" — different question, same protocol. ### The naming history The hook was called something else in Python 2 and was renamed to `__bool__` in Python 3; you will still meet the old name in very old code and in blog posts. On any supported Python — 3.14 included — the only name that is consulted is `__bool__`.
- Why is a generator object still truthy after it has been exhausted?A generator object defines neither `__bool__` nor `__len__`, so it takes the default: true. Exhaustion is not visible to a truth test at all. To ask whether anything is left you must actually pull — `next(gen, sentinel)` and compare against the sentinel — or materialise the values into a container and test that.
- Does assigning `obj.__bool__` on an instance change how `if obj:` behaves?No. Implicit special-method invocation looks the method up on `type(obj)` and its MRO, bypassing the instance dictionary entirely. An instance attribute named `__bool__` is simply ignored by the `if`, though calling `obj.__bool__()` explicitly would find it. The same rule applies to `__len__`, `__iter__` and the rest of the protocol methods.
- If a class defines both `__bool__` and `__len__`, when is that a reasonable design?When truth and size answer different questions. A buffer holding five queued items can legitimately report `False` because it is not yet ready to flush, while `len()` stays an exact five. It is deliberate only if you can state that distinction; otherwise it is usually an accident worth flagging in review, since readers assume an empty-means-false container.
A doorman checks two lists in order: first the explicit yes/no list, then the guest count. If your name is on neither list, you are waved through — which is why an object that defines nothing is always let in.
saying these in an interview costs you the question
- Assumes any custom object with empty state is falsy
- Thinks `__len__` is checked before `__bool__`
- Believes an exhausted generator object becomes falsy
- Looks up `__bool__` on the instance rather than the type
- Claims defining `__eq__` affects truthiness
- Says `if obj:` never calls user code