Devpuff
Pricing
Log InStart Free
  1. Home
  2. Blog
  3. How to reverse a list in Python (and a string)
Learn

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.

By Max Arthur
Co-Founder & Content Marketer·October 26, 2026·8 Min read
White text reading 'How to reverse a list in Python' on a dark purple dotted background

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.

Keep reading

More from the blog

White text reading 'What if name equals main does in Python' on a dark purple dotted background
October 27, 2026·8 min readLearn

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
White text reading '*args and **kwargs in Python' on a dark purple dotted background
October 25, 2026·8 min readLearn

*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 more
{ }
✦

Ready to write some code?

Put this into practice - start your first free lesson. No setup, no credit card.

Start learning free
Devpuff

Learn to code by doing. One tiny, playful lesson at a time.

Learn
ProgramsCoursesPricing
Company
AboutBlogResourcesAffiliates
Support
Help CenterContactStatus
Programs
Frontend DevelopmentFull-Stack DevelopmentReact DeveloperPython Developer
Courses
Advanced ReactAsync JavaScriptAsync PythonCSS BasicsCSS LayoutDSA Basics
Learn to Code
Learn JavaScriptLearn PythonLearn SQLLearn HTMLLearn CSSLearn ReactBrowse All Topics
Platform Comparisons
Devpuff vs CodecademyDevpuff vs Mimo
© 2026 Devpuff. All rights reserved.Privacy PolicyTerms and ConditionsCookies PolicyRefund Policy
Devpuff vs Sololearn
Devpuff vs freeCodeCamp
Read All Comparisons