skip to content

What does Python's NotImplemented mean, and how is it different from NotImplementedError?

level: juniorimportance: should knowfreq 40%

answer

  1. Two similar names, two different jobs
  2. One is returned, the other raised
  3. Binary operator dunders use one of them
  4. The other marks an unwritten abstract method
  5. Truth-testing the returned one now fails

basics

~20 s

NotImplemented 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 lines
python
class 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

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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

context