skip to content

How does reversed(obj) decide whether it can reverse a custom object?

level: middleimportance: nice to knowfreq 18%

answer

  1. There are two routes, tried in order
  2. The dunder wins if present
  3. Otherwise it needs random access
  4. Knowing the end requires a length
  5. Stricter than forward iteration

basics

~20 s

reversed() calls reversed if the type defines one. Otherwise it needs the full sequence protocol — both len and getitem — and walks indices from len(obj) - 1 down to 0. With neither route available it raises TypeError.

solid answer

~40 s

`reversed(obj)` looks up `__reversed__` on the type first; if present it is called and whatever it returns is handed straight back, so by contract it should return an iterator. With no `__reversed__`, the built-in falls back to the sequence protocol, which needs **both** `__len__` and `__getitem__`: it reads the length once, then yields `obj[n-1]`, `obj[n-2]` … down to `obj[0]`. That is stricter than `iter()`, which needs only `__getitem__` — a class with subscription but no length iterates forward yet raises `TypeError: object of type 'X' has no len()` under `reversed()`, and a class with neither route reports that the object is not reversible. Two details worth knowing: the length is snapshotted when `reversed()` is called, so later appends are never visited, and `__reversed__ = None` disables reversal outright.

code

python · 19 lines
python
class Ring:
    def __init__(self, size):
        self.size = size

    def __len__(self):
        return self.size

    def __getitem__(self, index):
        return index * 2

print(list(reversed(Ring(3))))

class Blocked(Ring):
    __reversed__ = None

try:
    reversed(Blocked(3))
except TypeError as exc:
    print("TypeError:", exc)

go deeper

for a junior

Remember that reversed() is pickier than a for loop: it needs a real sequence — a length plus indexing — or a class that implements reversed itself. It does not work on every iterable.

for a middle

Explain both routes in order, and why the fallback needs len when forward iteration does not: you cannot start at the end without knowing where the end is. Naming the resulting TypeError messages is a nice touch.

for a senior

Bring the operational edges: the length is snapshotted at creation so concurrent growth is invisible, reversed() is a lazy view rather than a copy, and reversed earns its place only when backward traversal is cheaper than indexing.

for a principal

Treat it as protocol design: which reversal semantics your container should promise, whether to expose one at all, and when disabling an operation with an explicit None beats letting a technically-valid but meaningless default stand.

## Two routes into `reversed()` The built-in `reversed()` is not a generic algorithm over iterables — it is deliberately narrow, because reversing something requires either the object's cooperation or random access. It tries exactly two routes. **Route one: `__reversed__`.** If the type defines it, `reversed(obj)` calls it and returns the result unchanged. The contract says the method should return a new iterator over the items in reverse, and nothing enforces it: if you return a `list`, `reversed(obj)` hands you that list, `for x in reversed(obj)` still works because a list is iterable, and `next(reversed(obj))` fails with `TypeError: 'list' object is not an iterator`. Return an iterator. **Route two: the sequence protocol.** With no `__reversed__`, `reversed()` requires the object to support integer subscription **and** to have a length. It calls `len(obj)` once, then produces a `reversed` object that yields `obj[n-1]`, `obj[n-2]`, down to `obj[0]`. If neither route is available, you get `TypeError` reporting that the object is not reversible. ## Why this is stricter than iteration This is the detail the question is really about. Forward iteration has its own legacy fallback that needs only `__getitem__` — it starts at index 0 and counts up until `IndexError`. Reversal cannot do that, because it has to know where the end *is* before it can start. So a class defining only `__getitem__` iterates forward happily and fails under `reversed()` — and the error you get is not "not reversible" but `TypeError: object of type 'X' has no len()`, because the missing length is what the fallback tripped on first. Adding `__len__` is the whole fix. ## The length is a snapshot The `reversed` object records the starting index when it is created, not on each step. If you build one and then grow the underlying container, the new items are never visited: ```python r = reversed(container) # start index fixed here container.append("late") # never seen by r ``` Shrinking the container is worse: the walk can index past the new end and raise, or silently skip. As with every iterator in Python, do not mutate the container you are traversing. ## The ABC does not agree with the built-in `collections.abc.Reversible`'s structural check tests for `__reversed__` **and** `__iter__`. So a class with `__len__` and `__getitem__` and nothing else is reversible in practice while `isinstance(obj, collections.abc.Reversible)` returns `False` — the same mismatch that the `Iterable` ABC has with the forward `__getitem__` fallback, for the same reason: the ABCs describe the modern protocols and do not model the compatibility fallbacks. And a class that defines `__reversed__` but no `__iter__` fails that check too, while `reversed()` on it works fine. ## Turning it off Setting a special method to `None` is the documented way to declare an operation unavailable. `__reversed__ = None` in a class body makes `reversed(obj)` raise `TypeError` even when `__len__` and `__getitem__` are both present — useful for a container whose integer keys are identifiers rather than positions, where reversing is meaningless and silently "working" would be worse than an error. The same trick with `__iter__ = None` blocks forward iteration and its `__getitem__` fallback. ## When to write `__reversed__` yourself Most of the time you do not need it — if your class is genuinely index-addressable, the fallback is correct and costs nothing to inherit. Write `__reversed__` when the fallback would be wrong or wasteful: * the underlying storage traverses backwards more cheaply than by index — a doubly linked structure, or a wrapper around a `collections.deque`, where `obj[i]` is not constant-time; * the container is index-addressable but reversing should mean something specific, such as newest-first over a log window; * you want `reversed()` to be lazy over a computed range rather than materializing anything. And remember that `reversed()` never copies. It returns a lazy view that reads the container as you consume it, which is the reason mutation during traversal bites and the reason `list(reversed(x))` is the idiom when you actually want a new reversed list. ## `reversed(obj)` versus `obj[::-1]` The two are not interchangeable and an interviewer may push on the difference. Slicing with a negative step is not a separate mechanism: `obj[::-1]` is one `__getitem__` call with `slice(None, None, -1)`, so it works only if your `__getitem__` handles slice objects, and it returns whatever that method builds — typically a new, fully materialized container of your own type. `reversed(obj)` never touches `__getitem__` with a slice, never allocates the result, and yields items one at a time. Use the slice when you want a reversed copy of the same kind of object; use the built-in when you want to walk backwards without paying for a copy, which on a large container is the difference between constant and linear extra memory.

  • Why is a class with `__len__` and `__getitem__` not an instance of `collections.abc.Reversible`?
    That ABC's structural check looks for `__reversed__` and `__iter__`, and this class has neither. The abstract base classes model the modern protocols and deliberately ignore the compatibility fallbacks that the built-ins still honour, so `reversed(obj)` works while the check says `False`. It is the same mismatch as an object that iterates through the `__getitem__` fallback yet fails the `Iterable` check.
  • How do you make `reversed()` refuse a class that has both `__len__` and `__getitem__`?
    Put `__reversed__ = None` in the class body. Setting a special method to `None` is the documented way to mark an operation unavailable, so the built-in stops at route one and raises `TypeError` instead of walking indices downwards. It is the right move when the integer keys are identifiers rather than positions and reversing them would produce a plausible-looking but meaningless result.
  • What must `__reversed__` return, and what happens if it returns a list?
    It should return a fresh iterator over the items in reverse order. Nothing enforces that: `reversed()` returns whatever the method returns, so a list comes straight back. A `for` loop over it still works, since lists are iterable, but `next(reversed(obj))` raises `TypeError: 'list' object is not an iterator`, and the laziness callers expect from `reversed()` is gone because you built the whole result eagerly.

Reading a book backwards needs either an index at the back that lists the pages in reverse, or the page count plus the ability to open any page directly; being able to turn pages forward one at a time is not enough.

saying these in an interview costs you the question

  • Saying reversed() works on any iterable
  • Claiming reversed() returns a list or a copy
  • Thinking __getitem__ alone is enough for reversed()
  • Assuming reversed() re-reads the length on every step
  • Believing __reversed__ may return a plain list
  • Expecting the Reversible ABC to accept the fallback

context