The stop is never included
range(5) gives five numbers ending at 4. This looks like an off-by-one waiting to happen and is the opposite: it is what makes range(len(items)) produce exactly the valid indices of a list, and what makes range(a, b) produce b - a numbers.
Counting down
Two things must both be true:
range(5, 0, -1) # 5 4 3 2 1
The step is negative and the stop is below the start. Get one wrong and you get an empty range, not an error — range(5, 0) with no step produces nothing at all, because it is counting up from 5 to 0.
The classic mistake is stopping at 0 when you meant to include it:
range(3, 0, -1) # 3 2 1 - misses 0
range(3, -1, -1) # 3 2 1 0
To walk a list backwards by index you need range(len(items) - 1, -1, -1), which is three fiddly numbers in a row and exactly why the alternatives exist:
for x in reversed(items):
for x in items[::-1]:
Both say "backwards" without arithmetic. Reach for a backwards range only when you genuinely need the index.
It does not build a list
range(1_000_000) stores three integers — start, stop, step — and computes each value on demand. It is a few dozen bytes whatever the size, which the page prints beside the list version for contrast.
That laziness is also why x in range(n) is fast: it does arithmetic rather than searching. It is the one in test on a sequence that does not scan.
Only integers
range refuses floats. For a fractional step, build the integers and divide, or use a library. range(0, 1, 0.1) is a TypeError, not a rounding problem.