Python Reverse List: reverse(), reversed(), and Slicing
Choose reverse(), reversed(), or slicing for Python lists, with clear mutation rules, iterator behavior, shallow copies, and runnable tests.
To reverse a list in Python, choose between three standard operations: .reverse(), reversed(), and the slice [::-1]. They can produce the same visible order, but they do different jobs. One mutates an existing list, one returns an iterator, and one creates a new list. Choosing deliberately prevents accidental changes, unexpected None results, and unnecessary copies.
A history list may be shared by several parts of an application. A display function may need the opposite order without changing stored data. A loop may only need to visit each item once, starting at the end. These requirements look similar on screen but call for different Python tools.
The three Python list reversal choices
Use items.reverse() when you own the list and want that object changed in place. Use reversed(items) when you want a reverse iterator, especially for a loop. Use items[::-1] when you want a new list immediately.
This comparison keeps the original list intact while demonstrating all three:
original = ["red", "blue", "green"]
working = original.copy()
result = working.reverse()
print(working) # ['green', 'blue', 'red']
print(result) # None
print(list(reversed(original))) # ['green', 'blue', 'red']
print(original[::-1]) # ['green', 'blue', 'red']
print(original) # ['red', 'blue', 'green']
Calling .reverse() changed working, not original, because the example copied the list first. The other two expressions created new result lists. The list() wrapper matters: without it, reversed(original) is an iterator rather than an eagerly built list.
reverse() changes the existing list and returns None
Python’s list documentation describes .reverse() as reversing elements in place. Code holding another reference to that same list sees its new order:
queue = ["first", "second", "third"]
alias = queue
result = queue.reverse()
print(queue) # ['third', 'second', 'first']
print(alias) # ['third', 'second', 'first']
print(result) # None
The None return is deliberate. List methods such as reverse(), sort(), and append() modify their target rather than returning a replacement list. Do not write queue = queue.reverse() when you need to keep using the list: that assignment replaces the variable’s reference with None. It does not turn the reversed list itself into None; another alias still refers to the list.
Likewise, print(queue.reverse()) prints None, even though the side effect occurred. Use two statements instead:
queue.reverse()
print(queue)
This snippet assumes queue already contains a list. Run each example independently; reversing the same list again restores its previous order. In-place reversal fits a private working buffer or an intentional shared update. It is inappropriate when a caller expects its input order to remain unchanged.
reversed() returns a single-use iterator
The built-in reversed() documentation specifies the reverse-iteration protocol. An object can provide __reversed__(), or support the sequence protocol with __len__() and integer __getitem__() access. Built-in lists meet those requirements.
colors = ["red", "blue", "green"]
backward = reversed(colors)
print(list(backward)) # ['green', 'blue', 'red']
print(colors) # ['red', 'blue', 'green']
print(list(backward)) # []
The iterator produces items only when consumed. Once exhausted, it does not restart automatically. Create a fresh reversed(colors) for another pass, or materialize a result list when repeated reuse is required. Printing the iterator object itself does not print its elements.
For a simple display loop, there is no need to create a second list:
for color in reversed(colors):
print(color)
For a built-in list, creating this iterator uses constant extra container space; visiting all its values still takes linear time. The iterator refers to its source, not an independent snapshot. Avoid changing list contents or length while traversing it. If stable positions are required while other code changes the source, iterate over a copy instead. That outer copy still shares nested objects.
Reversing a finite generator
reversed() also accepts tuples and ranges, but it does not accept every iterable. A generator normally lacks the reverse-iteration protocol, so calling reversed(generator) raises TypeError. For a finite generator, first collect its values:
numbers = (number for number in range(4))
collected = list(numbers)
backward = list(reversed(collected))
print(backward) # [3, 2, 1, 0]
Materialization consumes the generator and needs memory for all collected values. An infinite generator cannot be fully collected and reversed this way. If the consumer only needs one reverse traversal after collection, loop over reversed(collected) without building the additional backward list.
[::-1] makes a new shallow list
A full slice with step negative one visits the source from its final element toward its first. For a built-in list, it creates a separate list and leaves the source order alone:
numbers = [10, 20, 30, 40]
backward = numbers[::-1]
print(backward) # [40, 30, 20, 10]
print(numbers) # [10, 20, 30, 40]
print(backward is numbers) # False
The three slice components are start:stop:step. The stop is exclusive. The sequence operations documentation explains that omitted boundaries are chosen to match the step’s direction. Leaving those boundaries implicit is useful when you want every element.
A common off-by-one mistake is specifying a stop of zero:
letters = ["a", "b", "c", "d"]
print(letters[-1:0:-1]) # ['d', 'c', 'b']
print(letters[::-1]) # ['d', 'c', 'b', 'a']
The first slice excludes index zero; it is not a full reversal. Prefer [::-1] rather than manually spelling out endpoints. Also keep the input type in mind: this section promises a new list because the source is a list. Slicing a string or tuple has that type’s semantics, not a promise to return a list.
Assignment, aliases, and nested objects
Assignment does not copy a list. It binds a name to an object. Rebinding that name to a reversed copy leaves other aliases referring to the original object:
original = [1, 2, 3]
alias = original
original = original[::-1]
print(original) # [3, 2, 1]
print(alias) # [1, 2, 3]
This differs from the earlier .reverse() example, where aliases saw the mutation. Choose based on whether the old object must change, not simply whether the variable has the desired values afterward. list(reversed(original)) also creates a new list rather than modifying the old one.
Neither copying approach recursively duplicates the elements. As Python’s copy documentation explains, a shallow copy creates an outer container but retains references to its contents:
records = [{"status": "new"}, {"status": "queued"}]
backward = records[::-1]
backward[0]["status"] = "sent"
print(records[1]["status"]) # sent
The source list’s order stayed unchanged, but its second dictionary was shared with the copied list. If independent nested records are required, choose a copying strategy appropriate to that data model. Reversing positions alone does not require a deep copy.
Slice assignment provides another distinction: items[:] = items[::-1] writes a temporary reversed list back into the existing object, so aliases see the change. It preserves identity but allocates a temporary list. Ordinary .reverse() is simpler when intentional mutation and identity preservation are already the requirements.
Empty lists, duplicates, None, and sorting
The standard approaches need no special branch for an empty list or one item. Slicing an empty list produces another empty list; an iterator over it produces no values. Duplicate values retain their occurrences. A symmetrical sequence may look unchanged without implying that reversal deduplicated or ignored anything.
None can also be an ordinary element:
values = [None, "ready", None]
print(values[::-1]) # [None, 'ready', None]
Do not confuse an element with the method’s return value. In values, the two None objects are data. In result = values.reverse(), None is the return value. Passing None itself to reversed() is different again: it is not a reversible list.
Reversal does not compare elements, so a list containing numbers, strings, and None can be reversed. Sorting needs a compatible ordering or a suitable key. Descending sort also answers a different question:
values = [3, 1, 2]
print(values[::-1]) # [2, 1, 3]
print(sorted(values, reverse=True)) # [3, 2, 1]
Use reversal for an existing sequence’s opposite order. Use sorting to rank values. A list of events must already be chronological before reversing it means newest first; reversal does not inspect timestamps or establish chronology.
Apply list reversal to word order and functions
A common text task is reversing words while keeping each word’s letters intact. Split the string into tokens, consume them in reverse order, and join them:
text = "Python makes lists practical"
reversed_words = " ".join(reversed(text.split()))
print(reversed_words) # practical lists makes Python
With no separator argument, split() groups runs of whitespace; joining here deliberately replaces them with single spaces. Punctuation stays attached to its token. This is a simple whitespace-based operation, not full linguistic word segmentation or formatting preservation. Empty or whitespace-only input produces an empty result.
For a plain-language explanation, see reversing words without scrambling their letters. For character-level behavior and Unicode caveats, use the separate Python string reversal guide. A string has no .reverse() method; selecting word tokens and selecting characters are different tasks.
A function should also communicate whether it changes its caller’s list. This helper expects a list and returns a separate reversed list:
def reversed_items(items):
return items[::-1]
events = ["opened", "edited", "saved"]
latest_first = reversed_items(events)
print(events) # ['opened', 'edited', 'saved']
print(latest_first) # ['saved', 'edited', 'opened']
If a function only displays or processes values, a loop over reversed(items) avoids building a result list. Merely evaluating reversed(items) and discarding its iterator does nothing to the input and prints nothing. A mutating helper should document its side effect instead of surprising callers who passed data only for reading.
Run a self-contained behavior test
Save this as reverse_list_test.py and run python3 reverse_list_test.py. It uses only Python’s standard unittest module:
import unittest
class ReverseListTests(unittest.TestCase):
def test_mutation_and_return_value(self):
items = [1, 2, 3]
alias = items
self.assertIsNone(items.reverse())
self.assertIs(alias, items)
self.assertEqual(alias, [3, 2, 1])
def test_copy_and_iterator(self):
items = ["red", "blue", "green"]
copied = items[::-1]
self.assertIsNot(copied, items)
self.assertEqual(items, ["red", "blue", "green"])
iterator = reversed(items)
self.assertEqual(list(iterator), copied)
self.assertEqual(list(iterator), [])
def test_shallow_copy(self):
items = [{"label": "first"}, {"label": "second"}]
copied = items[::-1]
copied[0]["label"] = "changed"
self.assertEqual(items[1]["label"], "changed")
def test_boundaries_and_generator(self):
for items in ([], ["only"], [None, "ready", None]):
self.assertEqual(items[::-1], list(reversed(items)))
self.assertEqual(["a", "b", "c"][-1:0:-1], ["c", "b"])
source = (number for number in range(4))
self.assertEqual(list(reversed(list(source))), [3, 2, 1, 0])
if __name__ == "__main__":
unittest.main()
The checks distinguish values from identity and verify that the reverse iterator is exhausted after consumption. Add cases from your application, especially nested records, callbacks that might change the input, and functions that promise to leave their arguments untouched.
Practical cost and final choice
For a built-in list, .reverse() takes linear time and constant extra container space. items[::-1] and list(reversed(items)) take linear time and allocate a result list proportional to the input size. Creating reversed(items) itself is lightweight; consuming it still visits the requested items. These costs describe built-in lists, not every custom object implementing the reverse protocol.
Do not assume that creating a lazy iterator means its source data disappears. The iterator retains access to the list until it is no longer needed. For a finite generator, collecting values introduces its own storage cost before any reverse traversal can begin.
Choose .reverse() for intentional mutation, reversed() for traversal, and [::-1] for a separate list now. If you only need a quick text transformation rather than application code, try the Text Reverser tool. In Python, the reliable choice is the one that makes mutation, copying, and consumption explicit.
Try the Text Reverser tool
Reverse text, words, or letters instantly — free and entirely in your browser.
Open the tool