Reverse Python Lists: Beyond .reverse() and reversed()

Reverse Python Lists: Beyond .reverse() and reversed()

by Leodanis Pozo Ramos Jun 28, 2021 basics python

Are you diving deeper into Python lists and wanting to learn about different ways to reverse them? If so, then this tutorial is for you. Here, you’ll learn about a few Python tools and techniques that are handy when it comes to reversing lists or manipulating them in reverse order. This knowledge will complement and improve your list-related skills and make you more proficient with them.

In this tutorial, you’ll learn how to:

  • Reverse existing lists in place using .reverse() and other techniques
  • Create reversed copies of existing lists using reversed() and slicing
  • Use iteration, comprehensions, and recursion to create reversed lists
  • Iterate over your lists in reverse order
  • Sort your lists in reverse order using .sort() and sorted()

To get the most out of this tutorial, it would be helpful to know the basics of iterables, for loops, lists, list comprehensions, generator expressions, and recursion.

Reversing Python Lists

Sometimes you need to process Python lists starting from the last element down to the first—in other words, in reverse order. In general, there are two main challenges related to working with lists in reverse:

  1. Reversing a list in place
  2. Creating reversed copies of an existing list

To meet the first challenge, you can use either .reverse() or a loop that swaps items by index. For the second, you can use reversed() or a slicing operation. In the next sections, you’ll learn about different ways to accomplish both in your code.

Reversing Lists in Place

Like other mutable sequence types, Python lists implement .reverse(). This method reverses the underlying list in place for memory efficiency when you’re reversing large list objects. Here’s how you can use .reverse():

>>>
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> digits.reverse()
>>> digits
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

When you call .reverse() on an existing list, the method reverses it in place. This way, when you access the list again, you get it in reverse order. Note that .reverse() doesn’t return a new list but None:

>>>
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> reversed_digits = digits.reverse()
>>> reversed_digits is None
True

Trying to assign the return value of .reverse() to a variable is a common mistake related to using this method. The intent of returning None is to remind its users that .reverse() operates by side effect, changing the underlying list.

Okay! That was quick and straightforward! Now, how can you reverse a list in place by hand? A common technique is to loop through the first half of it while swapping each element with its mirror counterpart on the second half of the list.

Python provides zero-based positive indices to walk sequences from left to right. It also allows you to navigate sequences from right to left using negative indices:

Python list with indices

This diagram shows that you can access the first element of the list (or sequence) using either 0 or -5 with the indexing operator, like in sequence[0] and sequence[-5], respectively. You can use this Python feature to reverse the underlying sequence in place.

For example, to reverse the list represented in the diagram, you can loop over the first half of the list and swap the element at index 0 with its mirror at index -1 in the first iteration. Then you can switch the element at index 1 with its mirror at index -2 and so on until you get the list reversed.

Here’s a representation of the whole process:

Reverse Lists in Python

To translate this process into code, you can use a for loop with a range object over the first half of the list, which you can get with len(digits) // 2. Then you can use a parallel assignment statement to swap the elements, like this:

>>>
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> for i in range(len(digits) // 2):
...     digits[i], digits[-1 - i] = digits[-1 - i], digits[i]
...

>>> digits
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

This loop iterates through a range object that goes from 0 to len(digits) // 2. Each iteration swaps an item from the first half of the list with its mirror counterpart in the second half. The expression -1 - i inside the indexing operator, [], guarantees access to the mirror item. You can also use the expression -1 * (i + 1) to provide the corresponding mirror index.

Besides the above algorithm, which takes advantage of index substitution, there are a few different ways to reverse lists by hand. For example, you can use .pop() and .insert() like this:

>>>
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> for i in range(len(digits)):
...     last_item = digits.pop()
...     digits.insert(i, last_item)
...

>>> digits
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

In the loop, you call .pop() on the original list without arguments. This call removes and returns the last item in the list, so you can store it in last_item. Then .insert() moves last_item to the position at index i.

For example, the first iteration removes 9 from the right end of the list and stores it in last_item. Then it inserts 9 at index 0. The next iteration takes 8 and moves it to index 1, and so on. At the end of the loop, you get the list reversed in place.

Creating Reversed Lists

If you want to create a reversed copy of an existing list in Python, then you can use reversed(). With a list as an argument, reversed() returns an iterator that yields items in reverse order:

>>>
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> reversed_digits = reversed(digits)
>>> reversed_digits
<list_reverseiterator object at 0x7fca9999e790>

>>> list(reversed_digits)
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

In this example, you call reversed() with digits as an argument. Then you store the resulting iterator in reversed_digits. The call to list() consumes the iterator and returns a new list containing the same items as digits but in reverse order.

An important point to note when you’re using reversed() is that it doesn’t create a copy of the input list, so changes on it affect the resulting iterator:

>>>
>>> fruits = ["apple", "banana", "orange"]

>>> reversed_fruit = reversed(fruits)  # Get the iterator
>>> fruits[-1] = "kiwi"  # Modify the last item
>>> next(reversed_fruit)  # The iterator sees the change
'kiwi'

In this example, you call reversed() to get the corresponding iterator over the items in fruits. Then you modify the last fruit. This change affects the iterator. You can confirm that by calling next() to get the first item in reversed_fruit.

If you need to get a copy of fruits using reversed(), then you can call list():

>>>
>>> fruits = ["apple", "banana", "orange"]

>>> list(reversed(fruits))
['orange', 'banana', 'apple']

As you already know, the call to list() consumes the iterator that results from calling reversed(). This way, you create a new list as a reversed copy of the original one.

Python 2.4 added reversed(), a universal tool to facilitate reverse iteration over sequences, as stated in PEP 322. In general, reversed() can take any objects that implement a .__reversed__() method or that support the sequence protocol, consisting of the .__len__() and .__getitem__() special methods. So, reversed() isn’t limited to lists:

>>>
>>> list(reversed(range(10)))
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

>>> list(reversed("Python"))
['n', 'o', 'h', 't', 'y', 'P']

Here, instead of a list, you pass a range object and a string as arguments to reversed(). The function does its job as expected, and you get a reversed version of the input data.

Another important point to highlight is that you can’t use reversed() with arbitrary iterators:

>>>
>>> digits = iter([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

>>> reversed(digits)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'list_iterator' object is not reversible

In this example, iter() builds an iterator over your list of numbers. When you call reversed() on digits, you get a TypeError.

Iterators implement the .__next__() special method to walk through the underlying data. They’re also expected to implement the .__iter__() special method to return the current iterator instance. However, they’re not expected to implement either .__reversed__() or the sequence protocol. So, reversed() doesn’t work for them. If you ever need to reverse an iterator like this, then you should first convert it to a list using list().

Another point to note is that you can’t use reversed() with unordered iterables:

>>>
>>> digits = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

>>> reversed(digits)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'set' object is not reversible

In this example, when you try to use reversed() with a set object, you get a TypeError. This is because sets don’t keep their items ordered, so Python doesn’t know how to reverse them.

Reversing Lists Through Slicing

Since Python 1.4, the slicing syntax has had a third argument, called step. However, that syntax initially didn’t work on built-in types, such as lists, tuples, and strings. Python 2.3 extended the syntax to built-in types, so you can use step with them now. Here’s the full-blown slicing syntax:

a_list[start:stop:step]

This syntax allows you to extract all the items in a_list from start to stop − 1 by step. The third offset, step, defaults to 1, which is why a normal slicing operation extracts the items from left to right:

>>>
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> digits[1:5]
[1, 2, 3, 4]

With [1:5], you get the items from index 1 to index 5 - 1. The item with the index equal to stop is never included in the final result. This slicing returns all the items in the target range because step defaults to 1.

If you use a different step, then the slicing jumps as many items as the value of step:

>>>
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> digits[0::2]
[0, 2, 4, 6, 8]

>>> digits[::3]
[0, 3, 6, 9]

In the first example, [0::2] extracts all items from index 0 to the end of digits, jumping over two items each time. In the second example, the slicing jumps 3 items as it goes. If you don’t provide values to start and stop, then they are set to 0 and to the length of the target sequence, respectively.

If you set step to -1, then you get a slice with the items in reverse order:

>>>
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> # Set step to -1
>>> digits[len(digits) - 1::-1]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

>>> digits
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

This slicing returns all the items from the right end of the list (len(digits) - 1) back to the left end because you omit the second offset. The rest of the magic in this example comes from using a value of -1 for step. When you run this trick, you get a copy of the original list in reverse order without affecting the input data.

If you fully rely on implicit offsets, then the slicing syntax gets shorter, cleaner, and less error-prone:

>>>
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> # Rely on default offset values
>>> digits[::-1]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

Here, you ask Python to give you the complete list ([::-1]) but going over all the items from back to front by setting step to -1. This is pretty neat, but reversed() is more efficient in terms of execution time and memory usage. It’s also more readable and explicit. So these are points to consider in your code.

Another technique to create a reversed copy of an existing list is to use slice(). The signature of this built-in function is like this:

slice(start, stop, step)

This function works similarly to the indexing operator. It takes three arguments with similar meaning to those used in the slicing operator and returns a slice object representing the set of indices returned by range(start, stop, step). That sounds complicated, so here are some examples of how slice() works:

>>>
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> slice(0, len(digits))
slice(0, 10, None)

>>> digits[slice(0, len(digits))]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> slice(len(digits) - 1, None, -1)
slice(9, None, -1)

>>> digits[slice(len(digits) - 1, None, -1)]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

The first call to slice() is equivalent to [0:len(digits)]. The second call works the same as [len(digits) - 1::-1]. You can also emulate the slicing [::-1] using slice(None, None, -1). In this case, passing None to start and stop means that you want a slice from the beginning to the end of the target sequence.

Here’s how you can use slice() to create a reversed copy of an existing list:

>>>
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> digits[slice(None, None, -1)]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

The slice object extracts all the items from digits, starting from the right end back to the left end, and returns a reversed copy of the target list.

Generating Reversed Lists by Hand

So far, you’ve seen a few tools and techniques to either reverse lists in place or create reversed copies of existing lists. Most of the time, these tools and techniques are the way to go when it comes to reversing lists in Python. However, if you ever need to reverse lists by hand, then it’d be beneficial for you to understand the logic behind the process.

In this section, you’ll learn how to reverse Python lists using loops, recursion, and comprehensions. The idea is to get a list and create a copy of it in reverse order.

Using a Loop

The first technique you’ll use to reverse a list involves a for loop and a list concatenation using the plus symbol (+):

>>>
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> def reversed_list(a_list):
...     result = []
...     for item in a_list:
...         result = [item] + result
...     return result
...

>>> reversed_list(digits)
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

Every iteration of the for loop takes a subsequent item from a_list and creates a new list that results from concatenating [item] and result, which initially holds an empty list. The newly created list is reassigned to result. This function doesn’t modify a_list.

You can also take advantage of .insert() to create reversed lists with the help of a loop:

>>>
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> def reversed_list(a_list):
...     result = []
...     for item in a_list:
...         result.insert(0, item)
...     return result
...

>>> reversed_list(digits)
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

The call to .insert() inside the loop inserts subsequent items at the 0 index of result. At the end of the loop, you get a new list with the items of a_list in reverse order.

Using .insert() like in the above example has a significant drawback. Insert operations at the left end of Python lists are known to be inefficient regarding execution time. That’s because Python needs to move all the items one step back to insert the new item at the first position.

Using Recursion

You can also use recursion to reverse your lists. Recursion is when you define a function that calls itself. This creates a loop that can become infinite if you don’t provide a base case that produces a result without calling the function again.

You need the base case to end the recursive loop. When it comes to reversing lists, the base case would be reached when the recursive calls get to the end of the input list. You also need to define the recursive case, which reduces all successive cases toward the base case and, therefore, to the loop’s end.

Here’s how you can define a recursive function to return a reversed copy of a given list:

>>>
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> def reversed_list(a_list):
...     if len(a_list) == 0:  # Base case
...         return a_list
...     else:
...         # print(a_list)
...         # Recursive case
...         return reversed_list(a_list[1:]) + a_list[:1]
...

>>> reversed_list(digits)
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

Inside reversed_list(), you first check the base case, in which the input list is empty and makes the function return. The else clause provides the recursive case, which is a call to reversed_list() itself but with a slice of the original list, a_list[1:]. This slice contains all the items in a_list except for the first item, which is then added as a single-item list (a_list[:1]) to the result of the recursive call.

The commented call to print() at the beginning of the else clause is just a trick intended to show how subsequent calls reduce the input list toward the base case. Go ahead and uncomment the line to see what happens!

Using a List Comprehension

If you’re working with lists in Python, then you probably want to consider using a list comprehension. This tool is quite popular in the Python space because it represents the Pythonic way to process lists.

Here’s an example of how to use a list comprehension to create a reversed list:

>>>
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> last_index = len(digits) - 1

>>> [digits[i] for i in range(last_index, -1, -1)]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

The magic in this list comprehension comes from the call to range(). In this case, range() returns indices from len(digits) - 1 back to 0. This makes the comprehension loop iterate over the items in digits in reverse, creating a new reversed list in the process.

Iterating Through Lists in Reverse

Up to this point, you’ve learned how to create reversed lists and also how to reverse existing lists in place, either by using tools specially designed to accomplish that task or by using your own hand-coded solutions.

In day-to-day programming, you might find that iterating through existing lists and sequences in reverse order, typically known as reverse iteration, is a fairly common requirement. If that’s your case, then you have several options. Depending on your specific needs, you can use:

  • The built-in function reversed()
  • The slicing operator, [::]
  • The special method .__reversed__()

In the following few sections, you’ll learn about all these options and how they can help you iterate over lists in reverse order.

The Built-in reversed() Function

Your first approach to iterating over a list in reverse order might be to use reversed(). This built-in function was specially designed to support reverse iteration. With a list as an argument, it returns an iterator that yields the input list items in reverse order.

Here’s how you can use reversed() to iterate through the items in a list in reverse order:

>>>
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> for digit in reversed(digits):
...     print(digit)
...
9
8
7
6
5
4
3
2
1
0

The first thing to note in this example is that the for loop is highly readable. The name of reversed() clearly expresses its intent, with the subtle detail of communicating that the function doesn’t produce any side effects. In other words, it doesn’t modify the input list.

The loop is also efficient in terms of memory usage because reversed() returns an iterator that yields items on demand without storing them all in memory at the same time. Again, a subtle detail to note is that if the input list changes during the iteration, then the iterator sees the changes.

The Slicing Operator, [::-1]

The second approach to reverse iteration is to use the extended slicing syntax you saw before. This syntax does nothing in favor of memory efficiency, beauty, or clarity. Still, it provides a quick way to iterate over a reversed copy of an existing list without the risk of being affected by changes in the original list.

Here’s how you can use [::-1] to iterate through a copy of an existing list in reverse order:

>>>
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> for digit in digits[::-1]:
...     print(digit)
...
9
8
7
6
5
4
3
2
1
0

When you slice a list like in this example, you create a reversed copy of the original list. Initially, both lists contain references to the same group of items. However, if you assign a new value to a given item in the original list, like in digits[0] = "zero", then the reference changes to point to the new value. This way, changes on the input list don’t affect the copy.

You can take advantage of this kind of slicing to safely modify the original list while you iterate over its old items in reverse order. For example, say you need to iterate over a list of numbers in reverse order and replace every number with its square value. In this case, you can do something like this:

>>>
>>> numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> for i, number in enumerate(numbers[::-1]):
...     numbers[i] = number ** 2
...

>>> # Square values in reverse order
>>> numbers
[81, 64, 49, 36, 25, 16, 9, 4, 1, 0]

Here, the loop iterates through a reversed copy of numbers. The call to enumerate() provides ascending zero-based indices for each item in the reversed copy. That allows you to modify numbers during the iteration. Then the loop modifies the numbers by replacing each item with its square value. As a result, numbers ends up containing square values in reverse order.

The Special Method .__reversed__()

Python lists implement a special method called .__reversed__() that enables reverse iteration. This method provides the logic behind reversed(). In other words, a call to reversed() with a list as an argument triggers an implicit call to .__reversed__() on the input list.

This special method returns an iterator over the items of the current list in reverse order. However, .__reversed__() isn’t intended to be used directly. Most of the time, you’ll use it to equip your own classes with reverse iteration capabilities.

For example, say you want to iterate over a range of floating-point numbers. You can’t use range(), so you decide to create your own class to approach this specific use case. You end up with a class like this:

# float_range.py

class FloatRange:
    def __init__(self, start, stop, step=1.0):
        if start >= stop:
            raise ValueError("Invalid range")
        self.start = start
        self.stop = stop
        self.step = step

    def __iter__(self):
        n = self.start
        while n < self.stop:
            yield n
            n += self.step

    def __reversed__(self):
        n = self.stop - self.step
        while n >= self.start:
            yield n
            n -= self.step

This class isn’t perfect. It’s just your first version. However, it allows you to iterate through an interval of floating-point numbers using a fixed increment value, step. In your class, .__iter__() provides support for normal iteration and .__reversed__() supports reverse iteration.

To use FloatRange, you can do something like this:

>>>
>>> from float_range import FloatRange

>>> for number in FloatRange(0.0, 5.0, 0.5):
...     print(number)
...
0.0
0.5
1.0
1.5
2.0
2.5
3.0
3.5
4.0
4.5

The class supports normal iteration, which, as mentioned, is provided by .__iter__(). Now you can try to iterate in reverse order using reversed():

>>>
>>> from float_range import FloatRange

>>> for number in reversed(FloatRange(0.0, 5.0, 0.5)):
...     print(number)
...
4.5
4.0
3.5
3.0
2.5
2.0
1.5
1.0
0.5
0.0

In this example, reversed() relies on your .__reversed__() implementation to provide the reverse iteration functionality. This way, you have a working floating-point iterator.

Reversing Python Lists: A Summary

Up to this point, you’ve learned a lot about reversing lists using different tools and techniques. Here’s a table that summarizes the more important points you’ve already covered:

Feature .reverse() reversed() [::-1] Loop List Comp Recursion
Modifies the list in place ✔/❌
Creates a copy of the list ✔/❌
Is fast
Is universal

A quick look at this summary will allow you to decide which tool or technique to use when you’re reversing lists in place, creating reversed copies of existing lists, or iterating over your lists in reverse order.

Sorting Python Lists in Reverse Order

Another interesting option when it comes to reversing lists in Python is to use .sort() and sorted() to sort them in reverse order. To do that, you can pass True to their respective reverse argument.

The goal of .sort() is to sort the items of a list. The sorting is done in place, so it doesn’t create a new list. If you set the reverse keyword argument to True, then you get the list sorted in descending or reverse order:

>>>
>>> digits = [0, 5, 7, 3, 4, 9, 1, 6, 3, 8]

>>> digits.sort(reverse=True)
>>> digits
[9, 8, 7, 6, 5, 4, 3, 3, 1, 0]

Now your list is fully sorted and also in reverse order. This is quite convenient when you’re working with some data and you need to sort it and reverse it at the same time.

On the other hand, if you want to iterate over a sorted list in reverse order, then you can use sorted(). This built-in function returns a new list containing all the items of the input iterable in order. If you pass True to its reverse keyword argument, then you get a reversed copy of the initial list:

>>>
>>> digits = [0, 5, 7, 3, 4, 9, 1, 6, 3, 8]

>>> sorted(digits, reverse=True)
[9, 8, 7, 6, 5, 4, 3, 3, 1, 0]

>>> for digit in sorted(digits, reverse=True):
...     print(digit)
...
9
8
7
6
5
4
3
3
1
0

The reverse argument to sorted() allows you to sort iterables in descending order instead of in ascending order. So, if you need to create sorted lists in reverse order, then sorted() is for you.

Conclusion

Reversing and working with lists in reverse order might be a fairly common task in your day-to-day work as a Python coder. In this tutorial, you took advantage of a couple of Python tools and techniques to reverse your lists and manage them in reverse order.

In this tutorial, you learned how to:

  • Reverse your lists in place using .reverse() and other techniques
  • Use reversed() and slicing to create reversed copies of your lists
  • Use iteration, comprehensions, and recursion to create reversed lists
  • Iterate through your lists in reverse order
  • Sort lists in reverse order using .sort() and sorted()

All of this knowledge helps you improve your list-related skills. It provides you with the required tools to be more proficient when you’re working with Python lists.

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About Leodanis Pozo Ramos

Leodanis Pozo Ramos Leodanis Pozo Ramos

Leodanis is an industrial engineer who loves Python and software development. He's a self-taught Python developer with 6+ years of experience. He's an avid technical writer with a growing number of articles published on Real Python and other sites.

» More about Leodanis

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python

Join us and get access to hundreds of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Join us and get access to hundreds of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Real Python Comment Policy: The most useful comments are those written with the goal of learning from or helping out other readers—after reading the whole article and all the earlier comments. Complaints and insults generally won’t make the cut here.

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Keep Learning

Related Tutorial Categories: basics python