Back to Blog
Python

Python Reverse Iterator: Using reversed() and More

python reverse iterator: Learn how to iterate over sequences in reverse in Python using reversed(), slicing, and custom iterators, with practical performance and memor...

pythoniterationreversedcustom iteratorperformancememory
Illustration of a Python reverse iterator showing a sequence traversed from end to start.

python reverse iterator requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

When you need to process a sequence from its last element to its first, Python offers several ways to implement a reverse iterator. The most straightforward is the built-in reversed() function, but the right choice depends on whether you're working with a sequence, an iterator, or a custom class. This article covers the common approaches, their runtime behavior, and when each one makes sense in production code.

The Built-in reversed() Function

For any object that supports the sequence protocol—lists, tuples, strings, and ranges—reversed() returns a reverse iterator that yields elements from the end to the beginning. It does not copy the sequence; it walks backward using the object's __len__ and __getitem__ methods.

numbers = [1, 2, 3, 4, 5] for n in reversed(numbers): print(n)

nThe output is 5 4 3 2 1. Because reversed() returns an iterator, you can use it in a for loop, convert it to a list with list(), or pass it to any function that accepts an iterable.

This approach is ideal when you only need to to traverse the sequence once. It is memory-efficient because no copy is created, and the overhead is minimal. The main limitation is that reversed() only works with objects that have a defined sequence length and support indexing. It will not work on arbitrary iterators like generators.

Reversing with Slicing

A common alternative is slicing with a step of -1:

numbers = [1, 2, 3, 4, 5] for n in numbers[::-1]: print(n)

Slicing creates a new list containing all elements in reverse order. This is a shallow copy: for lists of mutable objects, the elements themselves are not copied, but the list container is. The result is a materialized sequence, not an iterator.

The key difference from reversed() is memory usage. For a list of n items, slicing allocates a additional list of size n. If you only need to to iterate once, that allocation is wasteful. However, slicing is useful when you need the reversed sequence multiple times or when you want to pass it to code that expects a list rather than an iterable.

Slicing also works on strings and tuples, but it returns the same type as the original. For example, "abc"[::-1] returns "cba".

Building a Custom Reverse Iterator

If you have a custom class that is not a sequence but still needs reverse iteration, you can implement __reversed__ or __iter__ together with __len__ and __getitem__. The simpler path is to to define __reversed__ to return a dedicated iterator.

class Playlist: def __init__(self, tracks): self.tracks = tracks def __len__(self): return len(self.tracks) def __getitem__(self, index): return self.tracks[index] def __reversed__(self): return reversed(self.tracks)

Now reversed(playlist) will call your __reversed__ method. If you do not define __reversed__, Python falls back to using __len__ and __getitem__ automatically, as long as they are present. That fallback is exactly how reversed() works for built-in sequences.

For a class that cannot support random access, you can build a custom iterator that stores the data and yields in reverse:

class ReverseIterator: def __init__(self, data): self.data = data self.index = len(data) - 1 def __iter__(self): return self def __next__(self): if self.index < 0: raise StopIteration value = self.data[self.index] self.index -= 1 return value

This gives you full control over the iteration logic, which is useful when the reverse order is not simply the opposite of the forward order—for example, when you need to skip certain elements or apply a transformation.

Memory and Performance Tradeoffs

The choice between reversed() and slicing has direct memory and performance implications. reversed() creates an iterator that holds a reference to the original sequence and an index. It consumes O(1) extra memory. Slicing creates a new sequence of size n, so it uses O(n) additional memory.

In terms of speed, reversed() is generally faster for a single pass because it avoids the copy. Slicing requires allocating a new container and copying references, which takes time proportional to the sequence length. For small sequences the difference is negligible, but for large lists—especially those holding many elements—the copy can cause noticeable latency and memory pressure.

There is also a subtle performance difference when you use slicing on a list but then iterate multiple times. With slicing, the reversed list is created once and can be reused; with reversed(), each call creates a fresh iterator, but you could store the list of items if you need to iterate multiple times. Recreating a reversed iterator every loop is cheap, so the tradeoff is mainly about memory.

Reversing Without Creating a Copy

When you need to reverse a sequence but avoid the memory cost of slicing, reversed() is the standard tool. However, there are cases where you might want to reverse a sequence in place—for example, when you have a list and want to mutate it to reverse order. The list.reverse() method does this in place, returning None and modifying the original list.

numbers = [1, 2, 3, 4, 5] numbers.reverse() print(numbers) # [5, 4, 3, 2, 1]

If you need to keep the original list unchanged, you must copy it first, either with numbers[::-1] or by calling list(reversed(numbers)). The latter creates a new list from the reverse iterator, which is equivalent to slicing but uses a different code path.

For large lists where you need a reversed copy, list(reversed(numbers)) is slightly more memory-efficient than slicing because it builds the list incrementally, but both ultimately allocate a list of the same size. The difference is negligible in practice.

Common Pitfalls and Edge Cases

A common mistake is assuming reversed() works on any iterable. It does not work on generators or other one-pass iterators because they have no length and no indexing. For example, reversed(generator_object) raises TypeError. If you have a generator, you must first convert it to a list or tuple, which defeats the memory advantage.

Another pitfall is using slicing on a string when you need to compare it to its reverse. Slicing a string creates a new string, which is fine, but if the string is very large, the copy can be expensive. For palindrome checks, s == s[::-1] is idiomatic, but for extremely long strings you might consider a manual loop that compares characters from both ends to avoid the copy.

When working with custom classes, forgetting to implement __len__ and __getitem__ will cause reversed() to fail. If your class is not a sequence, you must provide a __reversed__ method. Otherwise, Python raises a TypeError because it cannot determine the length or access elements by index.

Finally, be aware that reversed() returns an iterator, not a list. If you need to index into the reversed result, you must materialize it first. This is a common source of confusion for developers coming from languages where reversing always produces a collection.

Choosing the Right Approach for Your Use Case

The decision boils down to whether you need a one-time reverse traversal or a reusable reversed collection. Use reversed() when you only need to iterate once and want to avoid the memory overhead. Use slicing when you need a reversed copy that you can pass around or index into. Use a custom iterator when the reverse order is not a simple index decrement, or when you need to encapsulate complex reverse logic inside a class.

For in-place reversal, list.reverse() is the most efficient because it operates on the existing list without allocating new memory. However, it mutates the original, so it is only appropriate when you no longer need the original order.

In performance-sensitive code, prefer reversed() over slicing for large sequences. The O(1) memory footprint and lack of copy make it the safer default. If you later discover that you need the reversed sequence multiple times, you can always materialize it once with list(reversed(data)) and reuse that list.

Understanding how reversed() interacts with the sequence protocol also helps you design your own classes to support reverse iteration cleanly. By implementing __len__ and __getitem__, you get reverse support for free. By adding __reversed__, you can customize the behavior when the default is not optimal.

A final edge case: when the sequence is empty, reversed() returns an iterator that immediately raises StopIteration, and slicing returns an empty sequence. Both behave gracefully, so you do not need special handling for empty inputs.

Whether you are processing logs, implementing undo stacks, or analyzing time series, knowing how to build a python reverse iterator efficiently will help you write code that is both clear and performant. The next time you need to traverse a collection backward, start with reversed() and only reach for slicing or custom iterators when the situation demands it.

python reverse iterator: Practical Usage and Code Examples | RYUSLOG DEV