Python range() Function: start, stop and step Explained

Learn Python range() with start, stop and step, negative steps, why range uses little memory, indexing and slicing, and enumerate() vs range(len()).

Python

The Python range() type represents an immutable sequence of integers from a start value up to, but not including, a stop value, moving by a step. Its most common use is running a loop a fixed number of times or over a series of indexes, and it stores only three numbers, no matter how long the sequence is.

nums = list(range(5))            # [0, 1, 2, 3, 4]
evens = list(range(2, 10, 2))    # [2, 4, 6, 8]

We cover the three forms of range(), negative steps, why range uses almost no memory, indexing and slicing, and the common replacements for range(len(…)). Results are from Python 3.14.

1. Syntax and Return Value

The constructor takes one, two or three integer arguments and returns a range object.

range(stop) -> range
range(start, stop[, step]) -> range
  • start is the first number, 0 by default, and it is included.
  • stop is the end, and it is never included.
  • step is the distance between numbers, 1 by default; it can be negative but not zero.
a = list(range(3))          # [0, 1, 2]
b = list(range(2, 6))       # [2, 3, 4, 5]
c = list(range(0, 10, 3))   # [0, 3, 6, 9]
d = list(range(5, 2))       # [], start is already past stop

Because stop is excluded, range(1, 11) gives the numbers 1 to 10, and range(n) gives n numbers. Off-by-one errors mostly come from forgetting that rule.

2. Counting Down with a Negative Step

A negative step counts down, and the start must be larger than the stop. Reversing an ascending range with reversed() is often easier to read than working out the right start and stop.

down = list(range(10, 0, -3))      # [10, 7, 4, 1]
same = list(reversed(range(4)))    # [3, 2, 1, 0]
zero_step = range(1, 5, 0)         # ValueError: range() arg 3 must not be zero

3. A range Is Lazy and Small

A range object calculates each number when it is needed. Its size in memory is the same for ten numbers or ten billion, while a list stores a reference to every element, plus the integer objects themselves.

import sys

big = range(0, 10**12, 3)
count = len(big)                         # 333333333334
size_range = sys.getsizeof(range(10**6)) # 48 bytes
size_list = sys.getsizeof(list(range(10**6)))   # about 8 MB for the references alone

Membership tests with in and len() are calculated with arithmetic for integers, so 10**12 – 2 in big returns False at once without looping, because that number is not a multiple of 3.

4. Indexing, Slicing and Comparing Ranges

A range supports the same read operations as a tuple. Slicing returns another range, and two ranges are equal when they produce the same numbers.

r = range(0, 20, 3)          # 0 3 6 9 12 15 18
first = r[0]                 # 0
last = r[-1]                 # 18
sub = r[2:5]                 # range(6, 15, 3)
pos = r.index(9)             # 3
has = 12 in r                # True
equal = range(0) == range(5, 2)   # True, both are empty

A range is immutable, so r[0] = 1 raises TypeError; we convert it with list() when we need to change the numbers.

5. Floats and Better Alternatives to range(len())

The arguments must be integers, so range(0, 1, 0.1) raises TypeError. For decimal steps, we scale an integer range, and NumPy users have numpy.arange().

steps = [i / 10 for i in range(0, 5)]    # [0.0, 0.1, 0.2, 0.3, 0.4]

Looping over range(len(items)) to get both the index and the value works, but enumerate() is shorter and harder to get wrong. Its start argument sets the first index, here 1 instead of 0.

items = ["tea", "milk", "rice"]
for i, item in enumerate(items, start=1):
    print(i, item)
1 tea
2 milk
3 rice

6. Splitting a List into Batches

A step argument makes range() a simple tool for processing data in batches, such as sending records to an API 100 at a time.

records = list(range(1, 11))          # 10 records
batches = [records[i:i + 4] for i in range(0, len(records), 4)]
# [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]

The last batch is shorter because the slice stops at the end of the list. Since Python 3.12, itertools.batched(records, 4) does the same for any iterable.

7. range() FAQs

Developers moving from C-style for loops ask about the type, the end value and Python 2.

7.1. Is range() a function or a type?

It is a type. range(5) creates a range object, and type(range(5)) is <class ‘range’>, but it is called like a function, so most tutorials name it one.

7.2. Why does range(10) not include 10?

Excluding the stop value makes range(n) produce n numbers and matches list indexes, which also start at 0 and end at len – 1.

7.3. What is the difference between range in Python 2 and 3?

Python 2’s range() built a full list, and xrange() was the lazy version. Python 3’s range() is lazy, and xrange() no longer exists.

8. Conclusion

A range produces integers from start up to, but not including, stop, and it stores only its three arguments. It supports indexing, slicing and fast in checks, and for index-and-value loops enumerate() is the better choice than range(len()).

9. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.