How does reversed(obj) decide whether it can reverse a custom object?
answer
- There are two routes, tried in order
- The dunder wins if present
- Otherwise it needs random access
- Knowing the end requires a length
- Stricter than forward iteration
basics
~20 sreversed() 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 linesclass 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
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.
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.
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.
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