range
range produces a sequence of integers without storing them all. It computes each value on demand, so it stays cheap even for huge spans like 0 to 10⁸. Typical uses: for loops by index, repeating something N times, or building lists/tuples of numbers.
stop is never included. range(2, 8) gives 2 through 7, not 8. Step can’t be zero, and if the step goes the wrong way (e.g. counting up when start > stop), the result is an empty range or a TypeError for step 0.
What is range?
range is a built-in type. It takes start, stop, and optionally step; it behaves like a sequence (loop over it, index it, slice it, use len() and in), but it doesn’t build the whole list in memory. So range(0, 10**8) is fine; a list of that many integers would not be.
Type: type(range(4)) → <class 'range'>. A range is immutable: it cannot be changed after creation.
Creating a Range
Three forms, and all arguments must be integers (no floats).
| Form | Meaning | Example |
|---|---|---|
range(stop) | 0 up to but not including stop | range(6) → 0, 1, 2, 3, 4, 5 |
range(start, stop) | start up to but not including stop | range(2, 8) → 2, 3, 4, 5, 6, 7 |
range(start, stop, step) | start by step until stop is reached or passed | range(0, 10, 2) → 0, 2, 4, 6, 8 |
With one argument, counting always starts at 0. So range(6) is 0, 1, 2, 3, 4, 5.
# One argument: 0 through stop - 1
list(range(6)) # [0, 1, 2, 3, 4, 5]
# Two arguments: start through stop - 1
list(range(2, 8)) # [2, 3, 4, 5, 6, 7]
list(range(0, 10, 2)) # [0, 2, 4, 6, 8] - evens in 0..9
# Three: start, stop, step (stop still not included)
list(range(2, 12, 2)) # [2, 4, 6, 8, 10]
list(range(8, 0, -2)) # [8, 6, 4, 2] - count down by 2Parameters in Detail
Start (Optional)
With one argument there’s no start; it’s always 0. With two or three arguments, start is the first value in the sequence (included).
Stop (Required)
stop is never included. The sequence runs up to but not including stop. So range(2, 8) gives 2, 3, 4, 5, 6, 7 (no 8).
list(range(2, 8)) # [2, 3, 4, 5, 6, 7] - 8 not included
list(range(0, 4)) # [0, 1, 2, 3] - 4 not includedStep (Optional)
Defaults to 1. Can be positive (count up) or negative (count down), but not zero. With a positive step, start must be < stop for a non-empty range; with a negative step, start must be > stop. Step 0 raises TypeError.
list(range(0, 8, 2)) # [0, 2, 4, 6] - step 2
list(range(8, 0, -2)) # [8, 6, 4, 2] - count down
list(range(0, 8, -2)) # [] - step goes wrong way
list(range(8, 0, 2)) # [] - same
# range(2, 10, 0) # TypeError: arg 3 must not be zeroEmpty and Single-Value Ranges
A range is empty when the step doesn’t move from start toward stop: positive step with start ≥ stop, or negative step with start ≤ stop. No error; the range is simply empty.
A single-value range happens when the first step already reaches or passes stop. Example: range(4, 6, 2) gives only 4; 6 is still excluded.
list(range(0)) # [] - 0 to 0 (exclusive) = nothing
list(range(2, 2)) # [] - start equals stop
list(range(4, 2)) # [] - start > stop with step 1 (wrong direction)
list(range(2, 8, -2)) # [] - negative step but start < stop (wrong direction)
list(range(4, 6, 2)) # [4] - only 4; 6 not included
len(range(2, 2)) # 0
len(range(4, 6, 2)) # 1Length and Membership
len(r) is the number of integers in the range. Python works it out from start, stop, and step without iterating (constant time).
in tests whether an integer is in the range. The value must lie on the “grid” (start + k×step) and stay on the correct side of stop. Constant time.
r = range(0, 10, 2)
len(r) # 5 - 0, 2, 4, 6, 8
4 in r # True
5 in r # False - 5 isn’t 0 + k×2
10 in r # False - stop is never includedIndexing and Slicing
A range supports indexing and slicing like any sequence. r[i] is the value at that position; negative indices count from the end. Slicing returns another range, not a list, so it stays memory-efficient.
r = range(2, 12, 2) # 2, 4, 6, 8, 10
r[0] # 2
r[2] # 6
r[-1] # 10
r[-2] # 8
r[1:4] # range(4, 10, 2) - 4, 6, 8
r[::2] # range(2, 12, 4) - 2, 6, 10
r[::-1] # range(10, 0, -2) - 10, 8, 6, 4, 2; reversedIteration and Conversion
range is iterable: use it in a for loop or anywhere an iterable is accepted. Wrap in list() or tuple() when an actual list or tuple is needed.
for i in range(0, 8, 2):
print(i) # 0, 2, 4, 6
evens = list(range(0, 10, 2)) # [0, 2, 4, 6, 8]
t = tuple(range(2, 8, 2)) # (2, 4, 6)reversed(range(...)) walks the range backward. No need to turn it into a list first.
list(reversed(range(0, 8, 2))) # [6, 4, 2, 0]
for i in reversed(range(2, 12, 2)):
print(i) # 10, 8, 6, 4, 2Common Use Cases
| Use case | Example |
|---|---|
| Repeat N times | for _ in range(4): ... |
| Loop with index over a sequence | for i in range(len(items)): ... items[i] |
| Every 2nd index | for i in range(0, len(items), 2): ... |
| Count down | for i in range(8, 0, -2): ... |
| Build a list of integers | list(range(0, 10, 2)) |
| Reverse index loop | for i in range(len(lst)-1, -1, -1): ... lst[i] |
Use range(len(...)) only when the index is actually needed (e.g. to change an element or line up two sequences). When only the values matter, use for x in items. When both index and value are needed, enumerate(items) is clearer and less error-prone than range(len(...)).
Range vs List of Integers
| Aspect | range | list of integers (e.g. [0,1,...,N-1]) |
|---|---|---|
| Memory | O(1), start/stop/step only | O(n), every integer stored |
| Creation time | O(1) | O(n) |
| Indexing | O(1) | O(1) |
| Iteration | O(n) | O(n) |
| Mutability | Immutable | Mutable |
For large N, prefer range(N) over list(range(N)) unless a list is required (e.g. to change elements or pass to code that expects a list).
Equality and Hashing
Two ranges are equal if they represent the same sequence of integers; they don’t have to be built the same way.
range(0, 6, 2) == range(0, 6, 2) # True
range(0, 6, 2) == range(0, 7, 2) # False - different stop
list(range(0, 6, 2)) == [0, 2, 4] # Truerange is hashable (immutable), so it can be used in sets and as a dict key.
s = {range(0, 4), range(2, 6)}
d = {range(0, 4): "first", range(4, 8): "second"}Tricky behaviors
Creating - Integer-only arguments
range only takes integers. Floats like range(2.0, 8.0) or range(2, 8, 1.5) raise TypeError. For fractional steps, use a loop with a float variable or something like numpy.arange.
# range(2.0, 8.0) # TypeError: 'float' object cannot be interpreted as an integer
# range(0, 6, 0.4) # TypeError: 'float' object cannot be interpreted as an integerParameters - stop is exclusive
stop is never included. range(2, 8) gives 2 through 7, not 8. For “up to and including” the end, use range(start, stop + 1) (or range(start, stop + 1, step) with a step).
Parameters - step must not be zero
range(2, 10, 0) raises TypeError: step can’t be zero.
Empty range when step opposes direction
If the step goes the wrong way (positive step but start ≥ stop, or negative step but start ≤ stop), the range is empty. No error. Examples: range(8, 2, 2) and range(2, 8, -2) are both empty. The step must actually move from start toward stop.
range(0) and range(n, n)
range(0) is empty (nothing from 0 to 0 exclusive). range(n, n) is also empty for any n. Handy for “zero iterations” without special cases.
list(range(0)) # [] - one arg: 0 to 0 exclusive
list(range(4, 4)) # [] - start equals stop
len(range(0)) # 0Length and membership - len() and in are O(1)
len(r) and x in r are computed from start, stop, and step without iterating. Constant time. For in, the value must lie on the “grid” (start + k×step) and on the correct side of stop.
Indexing and slicing return range, not list
r[i] returns an integer. r[i:j] returns another range, not a list. Use list(r[i:j]) when a list is needed.
Negative indices and IndexError
Negative indices work: r[-1] is the last element, r[-2] the second-to-last. Out-of-range indices raise IndexError.
r = range(2, 12, 2) # 5 values: 2, 4, 6, 8, 10
r[-1] # 10 - last
r[-2] # 8 - second-to-last
# r[10] # IndexError
# r[-10] # IndexErrorIteration - reversed() without converting to list
reversed(range(...)) works directly; no need to call list(range(...)) first. Useful for counting down in a loop.
Use cases - range(len(…)) vs enumerate()
Use range(len(...)) only when the index alone is needed (e.g. in-place updates). When both index and value are needed, enumerate(seq) is clearer and avoids off-by-one errors.
range is not a list
range is its own type. .append, .sort, and other list methods are not available. Convert with list(r) when a mutable sequence is needed.
r = range(0, 6, 2)
r.append(4) # AttributeError: 'range' object has no attribute 'append'
r.sort() # AttributeError
list(r) # [0, 2, 4] - then the list can be mutatedEquality and hashing - same sequence, hashable
Two ranges are equal if they represent the same sequence of integers; they don’t have to be the same object. range is hashable (immutable), so it can be used in sets and as a dict key.
Interview questions
What is the type of range(5)? Can it be used in a set or as a dict key?
type(range(5)) is range (<class 'range'>). Ranges are immutable and hashable, so they can go in sets and work as dict keys.
What are the three forms of range()?
range(stop) (0 up to stop exclusive), range(start, stop) (start up to stop exclusive), and range(start, stop, step) (start by step until stop is reached or passed). All arguments must be integers.
Can floats be used in range()?
No. range only takes integers. range(0.0, 4.0) or range(0, 4, 0.5) raises TypeError. For fractional steps, use a loop with a float or a library like numpy.
Is the stop value in range(start, stop) included?
No. stop is always exclusive. range(2, 8) gives 2, 3, 4, 5, 6, 7. To include 8, use range(2, 9) (or range(2, 8 + 1)).
What happens with range(2, 10, 0)?
TypeError: step can’t be zero.
What does range(4, 4) produce? What about range(4, 2)?
range(4, 4) is empty (start equals stop). range(4, 2) is empty too: with step 1 the sequence would need to go from 4 down to 2, but the step is positive. Both have length 0.
What is the time complexity of len(r) and x in r?
Both O(1). len(r) is computed from start, stop, and step without iterating. x in r checks whether the value lies on the grid (start + k×step) and on the correct side of stop; no iteration.
Does slicing a range return a range or a list?
A range. r[1:4] is another range object. Use list(r[1:4]) when a list is needed.
How to iterate over a sequence in reverse by index?
range(len(seq)-1, -1, -1) gives indices from last to 0. Alternatively reversed(range(len(seq))). When only values in reverse order are needed, reversed(seq) is simpler.
When to use enumerate() instead of range(len(…))?
When both the index and the value are needed. enumerate(seq) yields (index, item) pairs and avoids off-by-one mistakes. Use range(len(...)) only when the index alone is needed (e.g. to change an element in place).
How to get a list of even numbers from 0 to 10 (inclusive of 10)?
list(range(0, 12, 2)) gives [0, 2, 4, 6, 8, 10]. Stop has to be 12 so 10 is included (stop is exclusive). range(0, 11, 2) also ends at 10, since 10 < 11.
Why use range instead of a list of integers?
range uses O(1) memory (it only stores start, stop, and step) and is O(1) to create. A list of the same numbers uses O(n) memory and takes O(n) time to build. For large N, range(N) is the right choice unless an actual list is required.
When are two ranges equal? Is range hashable?
Two ranges are equal if they represent the same sequence of integers; they don’t have to be the same object. range is hashable (immutable), so it can be used in sets and as a dict key.