Python range start stop step: Syntax and Behavior
python range start stop step: Learn how Python's range() works with start, stop, and step parameters, including examples, edge cases, and performance considerations.
The range() function is one of Python's most commonly used built-ins, and its three parameters—start, stop, and step—control exactly which integers it produces. Understanding python range start stop step behavior is essential for writing clear loops, generating index sequences, and avoiding off-by-one errors.
Consider the simplest form:
for i in range(5): print(i)
This prints 0, 1, 2, 3, 4. The stop value is exclusive, so 5 is not included. The start defaults to 0, and step defaults to 1. When you supply all three arguments, you gain precise control over the sequence.
Understanding the start, stop, and step Parameters
range(start, stop, step) accepts three integer arguments, but only stop is required. The full signature is range(stop) or range(start, stop[, step]). Here is what each parameter does:
start: The first integer in the sequence. Defaults to0.stop: The sequence ends before this value. It is never included.step: The difference between each consecutive value. Defaults to1. Must be non-zero.
All arguments must be integers. Passing floats raises a TypeError. This is a deliberate design choice—range is meant for integer arithmetic, and using floats introduces rounding issues that would break predictable iteration.
How range() Generates Values
The values produced by range follow a simple arithmetic progression:
start, start + step, start + 2*step, ...
The sequence continues as long as the current value is less than stop when step is positive, or greater than stop when step is negative. The stop value is always exclusive.
For example:
list(range(2, 10, 2))
produces [2, 4, 6, 8]. The value 10 is not included because it equals stop. Similarly, list(range(10, 2, -2)) produces [10, 8, 6, 4]. Note that 2 is not included because the sequence stops when the value would go below stop.
This behavior is deterministic and does not depend on any external state. You can rely on it in tight loops, algorithm implementations, and test assertions.
Common Usage Patterns for range()
Iterating Over Indices
The most frequent use of range is to iterate over indices of a sequence:
colors = ["red", "green", "blue"] for i in range(len(colors)): print(i, colors[i])
This works, but Python's enumerate is often more readable when you need both index and value. However, range remains useful when you need to modify elements in place or when you need to skip indices using a step.
Generating Arithmetic Sequences
range is handy for creating numeric sequences without building a list:
for value in range(100, 0, -10): print(value)
This prints 100, 90, 80, ... 10. The negative step makes it easy to count down.
Converting to a List
When you need an actual list of integers, pass range to list():
numbers = list(range(5))
This creates [0, 1, 2, 3, 4]. For large ranges, be aware that converting to a list consumes memory proportional to the number of elements, whereas the range object itself uses constant memory.
Edge Cases and Common Pitfalls
Zero Step Raises ValueError
If you set step to 0, range raises a ValueError because an infinite loop would otherwise result:
range(0, 10, 0) # ValueError: range() arg 3 must not be zero
Always ensure your step is non-zero, especially when it comes from user input or a variable.
Empty Ranges
A range can be empty if the sequence condition is never met. For example, range(5, 0) is empty because step defaults to 1 and the start is already greater than the stop. Similarly, range(0, 5, -1) is empty because a negative step cannot move from 0 upward to 5. This is not an error; it simply produces no values.
Negative Step with Positive Start
When using a negative step, the start must be greater than stop to produce any values. For instance, range(0, 5, -1) yields nothing. This is a common source of confusion for beginners.
Large Ranges and Memory
range objects are lazy. They do not store all values; they compute each value on demand. This means range(10**9) is perfectly safe to create—it uses a small, fixed amount of memory. Iterating over it would take a long time, but the object itself is lightweight.
Performance and Memory Considerations
Because range is lazy, it is almost always more efficient than creating a list of indices manually. For example, for i in range(1000): does not allocate a 1000-element list; it generates each integer as the loop advances. This is particularly important when working with very large ranges or when you only need to iterate once.
If you need to iterate over the same range multiple times, you can reuse the same range object. It is immutable and hashable, so it can also be used as a dictionary key or stored in a set.
In contrast, converting a range to a list forces the entire sequence into memory. Use list(range(...)) only when you actually need random access to the values or need to pass the sequence to a function that requires a list.
Comparing range() with Other Iteration Tools
range is not always the best choice. Here are some common alternatives and when they make sense:
enumerate: When you need both the index and the value from an iterable,enumerateis more direct and avoids indexing errors.itertools.count: For an infinite arithmetic sequence,itertools.count(start, step)is more appropriate because it never stops.numpy.arange: If you need floating-point sequences or vectorized operations, NumPy'sarangesupports floats but has different semantics. For pure Python integer iteration,rangeis preferred.
| Tool | Type of sequence | Memory behavior | Best use case |
|---|---|---|---|
range | Integer arithmetic | Lazy, constant memory | Loops, index generation |
list(range()) | Integer list | Allocates all elements | Random access, small sequences |
itertools.count | Infinite arithmetic | Lazy, unbounded | Counting up indefinitely |
numpy.arange | Float or integer | Allocates array | Numerical computing, vectorization |
Practical Examples and Advanced Usage
Using range to Build Slices
You can use range to generate index sequences for slicing, but Python's slice syntax is usually more readable. However, range is useful when you need to compute indices dynamically:
def every_nth(sequence, n): return [sequence[i] for i in range(0, len(sequence), n)]
This returns every n-th element. The range object here is used to generate the indices, and the list comprehension collects the values.
Reversing a Sequence with range
To iterate over a sequence in reverse, you can use range(len(seq)-1, -1, -1). This is a common pattern:
for i in range(len(items)-1, -1, -1): print(items[i])
Note the -1 for stop because the sequence must include index 0. This pattern is less readable than reversed(items), but it gives you direct control over the index if you need to modify the list while iterating backward.
Using range with Conditional Steps
Sometimes you need a step that changes based on a condition. Since range requires a fixed step, you would need a while loop for such cases. range is best suited for fixed arithmetic progressions.
Compatibility and Version Notes
In Python 2, range returned a list and xrange was the lazy version. In Python 3, range behaves like Python 2's xrange—it is lazy and returns a range object. If you are maintaining legacy code, be aware that xrange no longer exists in Python 3, and range in Python 2 consumes memory for large sequences. For new code, always use Python 3's range.
The range object supports membership tests efficiently. For example, 5 in range(10) is a constant-time operation because range can compute whether a value falls within its bounds without iterating. This is a subtle but useful performance advantage over checking membership in a list.
When you need to iterate over a range with a step that is not an integer, you must switch to a different tool. range only accepts integers, so for float steps, consider numpy.arange or a generator expression. This limitation is intentional and keeps range predictable for integer arithmetic.
Understanding the exact behavior of python range start stop step allows you to write loops that are correct, efficient, and easy to read. The key points to remember are that stop is exclusive, step must be non-zero, and the resulting object is lazy. Apply these rules directly in your code, and you will avoid the most common range-related bugs.