For a Python for i in range loop in reverse, either give range() a negative step, as in range(4, -1, -1), or wrap it in reversed(). Both count 4, 3, 2, 1, 0:
for i in range(4, -1, -1):
print(i)
for i in reversed(range(5)):
print(i)
Each loop here ran on Python 3.12.5, and the Command Prompt screenshots show the exact numbers printed.
I’ll show both ways, the off-by-one mistakes that produce empty or short ranges, looping backward with an index, safely deleting items, and why reversed(range()) uses almost no memory.
Two ways to reverse a range in Python
range(start, stop, step) with a negative step counts down from start to just above stop. reversed(range(n)) gives the same numbers and is usually easier to read:
for i in range(4, -1, -1): # start, stop (excluded), step
print(i, end=" ")
print()
for i in reversed(range(5)): # same numbers, easier to read
print(i, end=" ")
print()
print(list(range(10, 0, -2))) # step back by 2
print(list(reversed(range(2, 11, 2))))
Output:
4 3 2 1 0
4 3 2 1 0
[10, 8, 6, 4, 2]
[10, 8, 6, 4, 2]

I reach for reversed(range(n)) when I want 0 to n – 1 backward, since there’s no -1 to get wrong. For forward loops, see for i in range in Python.
Why does my reverse range stop early or come out empty?
The stop value is always excluded, and the default step is +1. Those two rules cause nearly every reverse-range bug:
print(list(range(10, 0, -1))) # stops at 1: 0 is the excluded stop
print(list(range(10, -1, -1))) # include 0 by stopping at -1
print(list(range(0, 10, -1))) # empty: can't count down from 0 to 10
print(list(range(10, 0))) # empty: default step is +1
Output:
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
[]
[]

An empty range doesn’t raise an error; the loop just doesn’t run. If a countdown silently does nothing, check the step first.
Looping backward with an index
Use range(len(items) - 1, -1, -1) for indexes, or reverse an enumerate() if you want index and value together. Slicing with [::-1] is simplest when you only need values:
cities = ["Austin", "Denver", "Boston", "Tampa"]
for i in range(len(cities) - 1, -1, -1):
print(i, cities[i])
print("---")
for i, city in reversed(list(enumerate(cities))): # reversed() needs a sequence
print(i, city)
print("---")
for city in cities[::-1]: # just the values, no index
print(city, end=" ")
print()
Output:
3 Tampa
2 Boston
1 Denver
0 Austin
---
3 Tampa
2 Boston
1 Denver
0 Austin
---
Tampa Boston Denver Austin

reversed() can’t take an enumerate object directly, which is why it’s wrapped in list(). More in what enumerate does in Python and reversing a list in Python.
Deleting items while looping: go backward
Removing items while looping forward shifts the rest of the list left, so you skip elements. Looping from the end avoids that, because earlier indexes never move:
prices = [19.99, 0.00, 4.50, 0.00, 12.00, 0.00]
# deleting while looping forward skips items; looping backward is safe
for i in range(len(prices) - 1, -1, -1):
if prices[i] == 0.00:
del prices[i]
print(prices)
Output:
[19.99, 4.5, 12.0]
A list comprehension like [p for p in prices if p != 0] is usually cleaner still. See Python list comprehension for that approach.
A countdown with a reverse range
A countdown is the classic use. range(5, 0, -1) gives 5 down to 1, and time.sleep() paces it:
import time
for seconds_left in range(5, 0, -1):
print(f"Launch in {seconds_left}...")
time.sleep(0.2)
print("Liftoff!")
Output:
Launch in 5...
Launch in 4...
Launch in 3...
Launch in 2...
Launch in 1...
Liftoff!
Is reversed(range()) memory efficient?
Yes. reversed(range(n)) and range(n)[::-1] don’t build a list; they compute each number when the loop asks for it. Turning the range into a list first costs memory for every number:
import sys
n = 10_000_000
print("reversed(range):", sys.getsizeof(reversed(range(n))), "bytes") # lazy, tiny
print("range[::-1]: ", sys.getsizeof(range(n)[::-1]), "bytes") # still a range object
print("list reversed: ", sys.getsizeof(list(range(n))[::-1]), "bytes") # builds every number
print(range(n)[::-1])
Output:
reversed(range): 32 bytes
range[::-1]: 48 bytes
list reversed: 80000056 bytes
range(9999999, -1, -1)

Slicing a range even returns another range, as the last line shows, so you can store it and reuse it freely.
Reverse range cheat sheet
| You want | Write |
|---|---|
| n – 1 down to 0 | reversed(range(n)) or range(n - 1, -1, -1) |
| 10 down to 1 | range(10, 0, -1) |
| 10 down to 0 | range(10, -1, -1) |
| Every other, downward | range(10, 0, -2) |
| List indexes backward | range(len(a) - 1, -1, -1) |
| Values backward | reversed(a) or a[::-1] |
More loop guides worth bookmarking:
Frequently asked questions
How do I write for i in range in reverse in Python?
Use for i in range(n - 1, -1, -1): or for i in reversed(range(n)):. Both count from n – 1 down to 0.
Why doesn’t range(10, 0) count backward?
The default step is +1. Add a negative step: range(10, 0, -1).
How do I include 0 in a reverse range?
Set the stop to -1, since the stop value is excluded: range(10, -1, -1).
Is reversed(range(n)) slower than a negative step?
No. Both are lazy and run at about the same speed. Pick whichever reads better.
How do I loop over a list backward with indexes?
Use range(len(items) - 1, -1, -1), or reversed(list(enumerate(items))) for index and value pairs.
Where is range documented?
See range in the Python docs.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn