What does Python's NotImplemented mean, and how is it different from NotImplementedError?
answer
- Two similar names, two different jobs
- One is returned, the other raised
- Binary operator dunders use one of them
- The other marks an unwritten abstract method
- Truth-testing the returned one now fails
basics
~20 sNotImplemented is a singleton value that a binary operator dunder returns to decline the other operand, letting Python try the reflected method. NotImplementedError is an exception raised from an abstract or unfinished method. Return one; raise the other.
solid answer
~40 s`NotImplemented` is a *value*: the single instance of `types.NotImplementedType`, exposed as a builtin. Binary operator dunders such as `__add__`, `__mul__` and `__rsub__` return it to say "I do not know how to combine myself with that operand". That is a hand-off, not a failure — the interpreter then tries the other operand's reflected method, and raises `TypeError: unsupported operand type(s)` only if that declines too. `NotImplementedError` is a *class*, a subclass of `RuntimeError`, that you raise from a method with no implementation here, typically a base-class hook a subclass must override. Both mix-ups bite: `raise NotImplemented` fails with "exceptions must derive from BaseException", and `return NotImplementedError` hands the caller a class object. Raising `NotImplementedError` from `__add__` also kills interoperability, because the right operand never gets its turn.
code
python · 24 linesclass Sensor:
def read(self):
raise NotImplementedError("subclasses must implement read()")
class Reading:
def __init__(self, bases):
self.bases = bases
def __add__(self, other):
if not isinstance(other, Reading):
return NotImplemented # decline; let Python try the other operand
return Reading(self.bases + other.bases)
try:
Sensor().read()
except NotImplementedError as exc:
print("raised:", exc)
try:
Reading(120) + "chr1"
except TypeError as exc:
print("declined:", exc)go deeper
Recall the one-line split: NotImplemented is a value you return from operator methods, NotImplementedError is an exception you raise from a method that has no implementation yet. Being able to say which is returned and which is raised is most of the mark here.
Explain the mechanics: returning the sentinel makes the interpreter try the other operand's reflected method before raising TypeError, so declining is a hand-off rather than an error. Mention that raising from a dunder short-circuits that fallback.
Show the debugging angle. Be ready to read a traceback and say whether the sentinel leaked out of a non-operator method, and to explain why a library type that raises instead of declining silently blocks interoperability with types you did not write.
Own the API-design position: operator protocols need an in-band decline signal that is distinguishable from a genuine error, which is why neither None nor an exception would do. Be ready to set the house rule for when a base class uses abc.abstractmethod versus a NotImplementedError stub.
### Two names, two completely different mechanisms Python ships two similarly spelled objects that belong to unrelated mechanisms, and mixing them up is one of the most common self-inflicted bugs in operator-overloading code. `NotImplemented` is a **value**. It is the single instance of `types.NotImplementedType`, exposed as a builtin name, and it exists to be **returned**. `NotImplementedError` is a **class** — a subclass of `RuntimeError`, and therefore of `Exception` — and it exists to be **raised**. One says "not me, try someone else"; the other says "this code does not exist yet, stop". ### What NotImplemented is for `NotImplemented` is the binary-operator protocol's way of declining an operand. The dunders that implement `+`, `-`, `*`, `/`, `%`, `**`, the bitwise operators and their reflected `__r*__` partners are all expected to return it — rather than raise — when they are handed an operand of a type they were not written for: ```python def __add__(self, other): if not isinstance(other, Reading): return NotImplemented return Reading(self.bases + other.bases) ``` Returning it is not an error report; it is a hand-off. The interpreter, seeing that sentinel come back from `type(left).__add__`, gets to try `type(right).__radd__` with the operands swapped, and only if that also declines does it raise `TypeError: unsupported operand type(s) for +: 'X' and 'Y'`. The sentinel never reaches your calling code — the interpreter consumes it and either succeeds by another route or raises. This is exactly why raising `TypeError` yourself from `__add__` for an unknown operand is worse than returning the sentinel: the raise escapes immediately, the right operand never gets its turn, and a type that would happily have interoperated with yours cannot. The same argument applies to `NotImplementedError`: raising it from `__add__` produces a confusing traceback where the correct outcome was either a successful reflected call or a plain `TypeError`. ### What NotImplementedError is for `NotImplementedError` means "this method exists in the interface but has no implementation at this point in the hierarchy". The two idiomatic uses are a base-class hook a subclass is required to override, and a deliberate stub in work-in-progress code: ```python class Sensor: def read(self): raise NotImplementedError("subclasses must implement read()") ``` If you want the failure earlier than "when somebody calls it", the `abc` module is the stronger tool: a class inheriting `abc.ABC` with a method marked `abc.abstractmethod` cannot be instantiated at all, and the error names the missing methods. `NotImplementedError` remains the light-weight convention for cases where you do not want to make the class abstract, and for the "planned, not built" placeholder. ### The four failure modes worth recognising on sight 1. **`raise NotImplemented`** — fails immediately with `TypeError: exceptions must derive from BaseException`. The loudest of the four, and the easiest to fix. 2. **`return NotImplementedError`** — returns the *class object*. Nothing raises; the caller gets a truthy value that is not a number, a string, or anything else it expected, and the real failure appears somewhere far away. 3. **`return NotImplemented` from a method that is not a binary-operator dunder** — nothing intercepts the sentinel, so it becomes an ordinary return value and travels. On Python 3.14 using it in a boolean context (`if result:`) raises `TypeError: NotImplemented should not be used in a boolean context`; that use has been deprecated since Python 3.9, and before the deprecation the sentinel was simply truthy, so the wrong branch was taken in silence. 4. **`raise NotImplementedError` from an operator dunder** — legal, but it destroys the reflected fallback and therefore any chance of interoperating with a type you did not write. ### Why a sentinel value at all The operator machinery needs an answer that is *in band* — cheap, and impossible to confuse with a legitimate result. `None` fails that test, because plenty of methods legitimately return `None`. An exception fails it for a subtler reason: a `TypeError` raised deep inside somebody's buggy `__add__` must not be silently retried against the other operand, so "I decline" has to be distinguishable from "I broke". A dedicated singleton is testable with `is`, cannot collide with real data, and costs nothing to produce. That is also why the interpreter, not your code, is the thing that turns a double decline into a `TypeError` with a message naming both operand types and the operator — you never have to write that message yourself. The short mnemonic that survives an interview: **NotImplemented is returned by operators and means "try the other side"; NotImplementedError is raised by methods and means "not written yet".**
- What happens if you return NotImplemented from a method that is not a binary operator dunder?Nothing intercepts it, so it becomes an ordinary return value and travels through your code. The caller sees an object that is not a number or a string, and on Python 3.14 the first `if result:` raises `TypeError: NotImplemented should not be used in a boolean context`. Before that deprecation (Python 3.9) it was simply truthy, so the wrong branch was taken silently. Only the operator machinery gives the sentinel meaning.
- When would you prefer abc.abstractmethod over raising NotImplementedError in a base-class method?When you want the failure at construction rather than at call time. A class inheriting `abc.ABC` with a method decorated `abc.abstractmethod` cannot be instantiated while any abstract method is unimplemented, and the `TypeError` names them. `NotImplementedError` only fires if someone actually calls the method, which can be far into a run — but it stays useful when you do not want the class to be abstract, or for a deliberate not-built-yet stub.
- Why is `raise NotImplemented` a TypeError rather than a NameError?The name resolves fine — `NotImplemented` is a builtin. It is the `raise` statement that rejects it: Python requires the operand to be an exception class or instance, and `NotImplemented` is an instance of `types.NotImplementedType`, which does not derive from `BaseException`. The message is exactly that: "exceptions must derive from BaseException".
NotImplemented is a player passing the ball — the play continues with someone else. NotImplementedError is a player stopping the game to say this position was never filled.
saying these in an interview costs you the question
- Calls NotImplemented an exception that you raise
- Writes `raise NotImplemented` in an abstract base-class method
- Returns NotImplementedError from __add__ instead of the sentinel
- Thinks returning NotImplemented ends the expression in failure
- Assumes NotImplemented is falsy and tests it with `if`
- Raises TypeError from __add__ rather than declining the operand