How to reverse a list in Python (and a string)
To reverse a list in Python, use reverse() to change it in place, [::-1] for a reversed copy, or reversed() to loop backwards. Which to use and why.
Python gives you three ways to reverse a list, and the right one depends on whether you want to change the original:
nums = [3, 1, 2]
print(nums[::-1]) # [2, 1, 3] a reversed copy
print(list(reversed(nums))) # [2, 1, 3] from an iterator
nums.reverse() # reverses nums itself
print(nums) # [2, 1, 3]
| Method | Changes the original? | Gives you back |
|---|---|---|
nums.reverse() |
yes | None |
nums[::-1] |
no | a new list |
reversed(nums) |
no | an iterator, for looping |
Use reverse() when you no longer need the original order, [::-1] when you need both, and reversed() when you only want to loop backwards. The rest of this post covers the bug each one invites, how fast they are, and how the same tools reverse a string. Every output below was measured in Pyodide running Python 3.13.2.
list.reverse() changes the list and returns None
nums = [3, 1, 2]
result = nums.reverse()
print(result) # None
print(nums) # [2, 1, 3]
reverse() rearranges the list it is called on and returns nothing. That design is deliberate: a method that changes an object in place returns None, so that nobody mistakes it for a method that makes a new one. list.sort() follows the same rule, as the sorting guide explains.
The bug it invites is writing nums = nums.reverse(). The list is reversed and then immediately thrown away, because nums now holds None. The error shows up later, somewhere else, usually as a NoneType error the first time you index into it.
[::-1] makes a reversed copy
The slice [::-1] means "from start to end, stepping by -1", which walks the list backwards and builds a new one:
nums = [3, 1, 2]
backwards = nums[::-1]
print(backwards) # [2, 1, 3]
print(nums) # [3, 1, 2]
The original is untouched. That matters when other variables point at the same list:
x = [1, 2, 3]
y = x
x = x[::-1]
print(x, y) # [3, 2, 1] [1, 2, 3]
x = [1, 2, 3]
y = x
x.reverse()
print(x, y) # [3, 2, 1] [3, 2, 1]
In the first half, x is pointed at a new reversed list and y keeps the old one. In the second, both names share one list, so reversing it in place changes what y sees too.
The copy is shallow
[::-1] copies the list, not the things inside it:
a = [[1], [2]]
b = a[::-1]
b[0].append(99)
print(a) # [[1], [2, 99]]
b[0] is the very same inner list as a[1], so changing it through b changes it in a. For lists of numbers and strings this never matters. For lists of lists or dictionaries, use copy.deepcopy if the copies must be independent.
reversed() for looping backwards
reversed() does not build a list at all. It returns an iterator that walks the original from the end:
nums = [3, 1, 2]
for n in reversed(nums):
print(n)
2
1
3
Because it does not copy, it costs almost nothing however big the list is. sys.getsizeof reported a million-item list at 4,000,028 bytes and the reversed() object over it at 24 bytes.
The catch is that an iterator can only be used once:
it = reversed(nums)
print(list(it)) # [2, 1, 3]
print(list(it)) # []
The second pass is silently empty. zip() behaves the same way. If you need the reversed values more than once, turn them into a list once and keep it.
reversed() works on anything with an order: lists, tuples, strings, range objects, and dictionaries since Python 3.8. It refuses sets, because a set has no order to reverse: TypeError: 'set' object is not reversible.
Which is fastest?
Reversing a million-item list, 20 times each:
| Method | Time |
|---|---|
nums.reverse() |
7.2ms |
nums[::-1] |
103.4ms |
list(reversed(nums)) |
228.4ms |
reverse() was about 14 times faster than slicing, because it swaps items in place and never allocates a new list. If you are going to throw the original away anyway, it is the obvious choice. Looping with reversed() without wrapping it in list() costs no allocation at all, which is why it is the right tool for a backwards loop.
Reversed is not the same as sorted backwards
nums = [3, 1, 2]
print(sorted(nums, reverse=True)) # [3, 2, 1]
print(nums[::-1]) # [2, 1, 3]
reverse=True sorts from largest to smallest. Reversing keeps the current order and flips it. They only agree when the list was already sorted.
Looping backwards by index
Sometimes you need the positions rather than the items. Reverse a range:
print(list(reversed(range(5)))) # [4, 3, 2, 1, 0]
print(list(range(4, -1, -1))) # [4, 3, 2, 1, 0]
Both give the indexes of a five-item list from last to first. The second form needs a stop of -1 because range() stops before its end value, and getting that wrong by one is easy. reversed(range(len(items))) says what it means.
Walking backwards has one practical use worth knowing: deleting items by index while you loop. Going forwards, each deletion shifts every later item down a place, so the loop skips items or runs off the end. Going backwards, deletions only affect positions you have already visited:
nums = [1, 2, 3, 4, 5, 6]
for i in reversed(range(len(nums))):
if nums[i] % 2 == 0:
del nums[i]
print(nums) # [1, 3, 5]
A list comprehension, [n for n in nums if n % 2], is usually clearer still. The forward version of this bug is one route to list index out of range.
Reverse only part of a list
Slicing with a negative step can also pick out part of a list backwards, but the start and stop work from the right-hand end, which takes a moment to read:
nums = [0, 1, 2, 3, 4, 5]
print(nums[2::-1]) # [2, 1, 0] from index 2 back to the start
print(nums[:2:-1]) # [5, 4, 3] from the end back to (not including) index 2
print(nums[4:1:-1]) # [4, 3, 2] from index 4 back to (not including) index 1
To reverse a section in place and leave the rest alone, assign a reversed slice back to the same slice:
nums = [0, 1, 2, 3, 4, 5]
nums[1:4] = nums[1:4][::-1]
print(nums) # [0, 3, 2, 1, 4, 5]
Positions 1 to 3 were reversed and everything else stayed where it was.
Reverse a list without reverse(): the two-pointer swap
Interviewers often ask for this to see whether you can do it by hand. Put one pointer at each end, swap the items, and move the pointers towards each other until they meet:
def reverse_in_place(items):
left, right = 0, len(items) - 1
while left < right:
items[left], items[right] = items[right], items[left]
left += 1
right -= 1
nums = [1, 2, 3, 4, 5]
reverse_in_place(nums)
print(nums) # [5, 4, 3, 2, 1]
The same function gave [4, 3, 2, 1] for an even-length list and left an empty list empty. The condition left < right handles both cases: with an odd length, the middle item is never swapped because it is already in place. Python's tuple assignment swaps two items in one line without a temporary variable. This is essentially what list.reverse() does, except that the built-in runs in C. If you want to practise the while loop on something real, this is a good one.
Lists of lists, tuples and ranges
Reversing a list of lists reverses the order of the rows, not what is inside them. To reverse each row, reverse inside a comprehension:
grid = [[1, 2, 3], [4, 5, 6]]
print(grid[::-1]) # [[4, 5, 6], [1, 2, 3]]
print([row[::-1] for row in grid]) # [[3, 2, 1], [6, 5, 4]]
Slicing keeps the type of what you slice. A reversed tuple is still a tuple, (1, 2, 3)[::-1] gives (3, 2, 1), and a tuple has no reverse() method, because tuples cannot be changed. A reversed range is still a range: range(5)[::-1] is range(4, -1, -1), which costs no memory however large it is.
How to reverse a string in Python
Strings have no reverse() method, since strings cannot be changed in place:
"hello".reverse()
# AttributeError: 'str' object has no attribute 'reverse'
The slice works exactly as it does on a list, and returns a new string:
print("hello"[::-1]) # olleh
print("".join(reversed("hello"))) # olleh
The second form is reversed() plus join() to glue the characters back together. The slice is shorter and faster, and it is what most Python code uses.
Python strings are sequences of Unicode code points, so an ordinary emoji survives reversal intact, which is not true in every language. Characters built from more than one code point do not. Flags are two letters each, so they re-pair:
print("🇺🇸🇫🇷"[::-1]) # 🇷🇫🇸🇺
The US and French flags became two different letter pairs. JavaScript has the same problem and a fix built into the browser; reversing a string in JavaScript covers both. In Python, the third-party grapheme and regex packages can split text into user-visible characters when you need that.
Quick reference
nums.reverse() # in place; returns None
backwards = nums[::-1] # new list; original unchanged
for n in reversed(nums): ... # loop backwards, no copy
list(reversed(nums)) # reversed copy from the iterator
sorted(nums, reverse=True) # largest first (not the same thing)
reversed(range(len(nums))) # indexes, last to first
text[::-1] # reverse a string
Want to practise lists and slicing in lessons where the Python runs in your browser? Start the Python track.
More from the blog
What if __name__ == '__main__' actually does in Python
In Python, if __name__ == '__main__' runs code only when you run the file directly, not when it is imported. What __name__ holds and when you need it.
Read more*args and **kwargs in Python, demystified
In Python, *args collects extra positional arguments into a tuple and **kwargs collects keyword arguments into a dict. Order rules, unpacking and errors.
Read moreReady to write some code?
Put this into practice - start your first free lesson. No setup, no credit card.