Back to Blog
Python

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.

pythonrangeiterationloopsbuilt-in functions
Illustration of Python range function with start, stop, and step parameters shown as a number line.

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 to 0.
  • stop: The sequence ends before this value. It is never included.
  • step: The difference between each consecutive value. Defaults to 1. 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, enumerate is 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's arange supports floats but has different semantics. For pure Python integer iteration, range is preferred.
ToolType of sequenceMemory behaviorBest use case
rangeInteger arithmeticLazy, constant memoryLoops, index generation
list(range())Integer listAllocates all elementsRandom access, small sequences
itertools.countInfinite arithmeticLazy, unboundedCounting up indefinitely
numpy.arangeFloat or integerAllocates arrayNumerical 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.

python range start stop step: Practical Usage and Code Examp | RYUSLOG DEV