NotImplementedError vs NotImplemented in Python (tested)

NotImplementedError and NotImplemented look alike but do opposite jobs: one is raised, one is returned. We measured what each does, and what breaks when you swap them, on Python 3.13.5.

The difference in one line

NotImplementedError is an exception you raise for "this method must be overridden". NotImplemented is a value you return from __eq__, __lt__, __add__ and friends for "I don't know how to handle this other type, try the other operand". Mixing them up gives the bugs below.

1. NotImplementedError: a subclass didn't override the method

class Shape:
    def area(self):
        raise NotImplementedError("subclasses must implement area()")

class Square(Shape):
    def __init__(self, s): self.s = s
    def arae(self): return self.s ** 2      # typo: area() never overridden

print(Square(3).area())

Output:

Traceback (most recent call last):
  File "/tmp/ni/b1.py", line 9, in <module>
    print(Square(3).area())
          ~~~~~~~~~~~~~~^^
  File "/tmp/ni/b1.py", line 3, in area
    raise NotImplementedError("subclasses must implement area()")
NotImplementedError: subclasses must implement area()

Measured on Python 3.13.5: a typo (arae) means area() was never overridden, and the error only appears when the method is called, possibly deep in production. Check the subclass method name and signature against the base class.

2. Catch it at construction with abc.abstractmethod

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self): ...

class Square(Shape):
    def __init__(self, s): self.s = s
    def arae(self): return self.s ** 2

try:
    Square(3)
except TypeError as e:
    print("TypeError:", e)

Output:

TypeError: Can't instantiate abstract class Square without an implementation for abstract method 'area'

Measured: with ABC + @abstractmethod, the same typo fails as soon as you create the object, with a message naming the missing method.

3. Raising NotImplementedError from __eq__ breaks ==, in, and list.remove

class Money:
    def __init__(self, c): self.c = c
    def __eq__(self, other):
        if not isinstance(other, Money):
            raise NotImplementedError      # wrong
        return self.c == other.c

try:
    print(Money(5) == 5)
except NotImplementedError:
    print("NotImplementedError from ==")
try:
    print(Money(5) in [1, 2, Money(5)])
except NotImplementedError:
    print("NotImplementedError from 'in'")

Output:

NotImplementedError from ==
NotImplementedError from 'in'

Measured: comparing to an int raises instead of returning False, and so does a plain in check on a list that holds other types, because in calls == on each item.

4. Fix: return NotImplemented

class Money:
    def __init__(self, c): self.c = c
    def __eq__(self, other):
        if not isinstance(other, Money):
            return NotImplemented          # right: let Python try the other side
        return self.c == other.c
    def __lt__(self, other):
        if not isinstance(other, Money):
            return NotImplemented
        return self.c < other.c

print(Money(5) == 5, Money(5) in [1, 2, Money(5)])
try:
    Money(5) < 5
except TypeError as e:
    print("TypeError:", e)

Output:

False True
TypeError: '<' not supported between instances of 'Money' and 'int'

Measured: returning NotImplemented lets Python try the reflected operation and fall back. For == that ends in an identity comparison (False); for ordering it ends in the standard TypeError: '<' not supported between instances, which is what callers expect.

5. NotImplemented is not an exception, and it's truthy

import warnings
warnings.simplefilter("always")
print(bool(NotImplemented))       # truthy: 'if a == b' can't catch a mistake
try:
    raise NotImplemented          # NotImplemented is a value, not an exception
except TypeError as e:
    print("TypeError:", e)

Output:

/tmp/ni/b5.py:3: DeprecationWarning: NotImplemented should not be used in a boolean context
  print(bool(NotImplemented))       # truthy: 'if a == b' can't catch a mistake
True
TypeError: exceptions must derive from BaseException

Measured on 3.13.5: raise NotImplemented fails with TypeError: exceptions must derive from BaseException, so a typo here hides the real message. bool(NotImplemented) is still True on 3.13 with a DeprecationWarning, so if you call a.__eq__(b) directly and test the result, a "don't know" reads as equal. Use the operator (a == b), not the dunder method.

Frequently asked questions

What is the difference between NotImplemented and NotImplementedError?

NotImplementedError is an exception raised for methods a subclass must override. NotImplemented is a value returned from comparison/arithmetic dunders to let Python try the other operand. We measured both on Python 3.13.5.

Should __eq__ raise NotImplementedError?

No. We measured it breaking == against an int and even the in operator on a mixed list. Return NotImplemented instead.

How do I fix NotImplementedError in Python?

The subclass didn't override the method, often because of a typo in the name. We measured a misspelled area() reaching the base class; abc.abstractmethod catches it when the object is created.

Can I raise NotImplemented?

No, it's not an exception. We measured TypeError: exceptions must derive from BaseException. Raise NotImplementedError.

Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5 on Linux: an un-overridden method, abc.abstractmethod, NotImplementedError raised from __eq__ (== and in), returning NotImplemented from __eq__ and __lt__, raise NotImplemented and bool(NotImplemented). Every block was run verbatim and its output pasted from that run. PlainHeaders is our extension; everything else here is built-in Python.