The range() Function
range() generates a sequence of numbers, and it's the tool you'll reach for most often when you need to loop a specific number of times — "do this 10 times," "count from 5 to 1," "process every third index." It comes in three forms depending on how many arguments you give it, and understanding how it behaves under the hood (it's lazy, not a list) will help you write more efficient loops.
range(stop) — counting from zero
With a single argument, range(stop) produces numbers starting at 0 up to (but not including) stop.
range(stop)
for i in range(5):
print(i)
# Output:
# 0
# 1
# 2
# 3
# 4range(start, stop) — choosing a starting point
With two arguments, counting begins at start instead of 0, and still stops just before stop.
range(start, stop)
for i in range(2, 7):
print(i)
# Output:
# 2
# 3
# 4
# 5
# 6range(start, stop, step) — controlling the increment
The third argument controls how much to add each time. It can skip values (a step greater than 1) or, with a negative value, count downward.
range(start, stop, step)
for i in range(0, 20, 5):
print(i)
# Output:
# 0
# 5
# 10
# 15Negative step — counting down
for i in range(10, 0, -1):
print(i)
# Output: 10 9 8 7 6 5 4 3 2 1
# Note: stop (0) is excluded, and start (10) is included.
for i in range(5, -1, -1):
print(i)
# Output: 5 4 3 2 1 0range is lazy — it's not a list
A common misconception is that range(1000000) builds a million-item list in memory. It doesn't. range returns a special range object that only stores three numbers — start, stop, and step — and computes each value on demand as the loop asks for it. That's why it stays cheap no matter how large the range is.
range vs a real list, in memory
import sys r = range(1_000_000) lst = list(range(1_000_000)) print(sys.getsizeof(r)) # 48 bytes — same for range(10) or range(10**9) print(sys.getsizeof(lst)) # roughly 8,000,000+ bytes
Because range doesn't hand you a list directly, things like slicing or printing it won't show individual numbers unless you materialize it. Use list() when you actually need the values as a list — for printing, for passing to something expecting a list, or for reusing the same sequence multiple times.
Materializing a range into a list
print(range(5)) # range(0, 5) -- the object itself, not the numbers print(list(range(5))) # [0, 1, 2, 3, 4]
range() in a loop vs. a list comprehension
range() is usually paired directly with a for loop when you just need to repeat an action or need the index values themselves. A list comprehension like [x**2 for x in range(5)] is what you reach for when the goal is to actually build a new list of transformed values.
Looping vs. building a list
# Looping: we just want to run some code 5 times, no list needed
for x in range(5):
print(f"processing item {x}")
# Building a list: we want the *results*, collected
squares = [x**2 for x in range(5)]
print(squares) # [0, 1, 4, 9, 16]Goal | Idiomatic choice |
|---|---|
Repeat an action N times / iterate over indices |
|
Produce a new list of computed values | List comprehension: |
Need the sequence of numbers itself, reused multiple times |
|