Skip to Content
Range

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.

Note

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).

FormMeaningExample
range(stop)0 up to but not including stoprange(6) → 0, 1, 2, 3, 4, 5
range(start, stop)start up to but not including stoprange(2, 8) → 2, 3, 4, 5, 6, 7
range(start, stop, step)start by step until stop is reached or passedrange(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 2

Parameters 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 included

Step (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 zero

Empty 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)) # 1

Length 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 included

Indexing 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; reversed

Iteration 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, 2

Common Use Cases

Use caseExample
Repeat N timesfor _ in range(4): ...
Loop with index over a sequencefor i in range(len(items)): ... items[i]
Every 2nd indexfor i in range(0, len(items), 2): ...
Count downfor i in range(8, 0, -2): ...
Build a list of integerslist(range(0, 10, 2))
Reverse index loopfor 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

Aspectrangelist of integers (e.g. [0,1,...,N-1])
MemoryO(1), start/stop/step onlyO(n), every integer stored
Creation timeO(1)O(n)
IndexingO(1)O(1)
IterationO(n)O(n)
MutabilityImmutableMutable

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] # True

range 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 integer

Parameters - 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)) # 0

Length 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] # IndexError

Iteration - 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 mutated

Equality 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.

Last updated on