1.2.19 · D5Introduction to Programming (Python)

Question bank — `range()` — start, stop, step

1,507 words7 min readBack to topic

Recall the two rules that resolve almost everything here. First, what the symbols mean:

  • Half-open rule: the value stop is excluded — see the number line below.
  • Direction rule: with step > 0 we keep terms while ; with step < 0 while .
Figure — `range()` — start, stop, step

Look at the picture. Each dot is one hop of size step starting at the filled square start. The hollow circle at stop is the wall: we produce every dot strictly before it, and never the wall itself. The top track goes forward (step > 0); the bottom track goes backward (step < 0) — same idea, mirror direction.


Where the count formula comes from

Before the traps, earn the count formula so the "empty range" answers below make sense.

Figure — `range()` — start, stop, step

Worked check on the picture (range(2, 9, 2)): distance , step , so terms — matching the four filled dots .


True or false — justify

range(1, 10) includes the number 10.
False — stop is excluded, so you get 1..9. English "stop" sounds inclusive, but Python's is the wall you must not land on.
len(range(n)) always equals n for a positive integer n.
True — range(n) is 0, 1, …, n-1, which is exactly n values. This is the whole reason indexing starts at 0.
A range object is just a list under the hood.
False — it is a lazy immutable sequence that remembers the rule and generates numbers on demand; it never stores them all. Wrapping it with list() (see Lists and list()) is what actually materialises the numbers into memory.
range(5) and range(0, 5) produce the same numbers.
True — the single-argument form defaults start to 0 and step to 1, so both give 0,1,2,3,4.
range(0, 10, -1) counts down from 0.
False — with step < 0 we keep terms while , but is already false, so it is empty.
You can iterate over the same range object twice in two separate loops.
True — a range is re-iterable because it recomputes from its stored rule each time (unlike a generator, which exhausts).
range(2, 9, 2) and range(2, 8, 2) produce the same list.
False — range(2,9,2) is [2,4,6,8] because , but range(2,8,2) is [2,4,6] because (the wall at 8 excludes 8). The excluded endpoint is the whole difference.
range(-3, 3) includes negative numbers.
True — start can be negative; it yields -3,-2,-1,0,1,2, stopping before 3. Nothing special happens at zero.

Spot the error

range(0, 1, 0.1) — what breaks?
TypeErrorrange accepts integers only. For a float step use numpy.arange (step-based) or linspace (count-based) — the float-capable cousins.
range(0, 5, 0) — what breaks?
ValueError: range() arg 3 must not be zero. A zero step would never move past start, so Python forbids it rather than looping forever.
Someone writes range(10, 0, -1) and expects [10, 9, …, 0].
Off-by-one: stop=0 is excluded, so you get 10..1 only. To include 0, write range(10, -1, -1).
for i in range(1, len(mylist)): to touch every element.
Error of coverage — it skips index 0. Use range(len(mylist)), or better enumerate(mylist), which hands you index and value together — see enumerate() and Indexing and slicing.
range(5.0) to make five numbers.
TypeError — even a whole-valued float like 5.0 is rejected; the argument must be a true int.
range(1, N) chosen "to include N".
Wrong endpoint — this stops at N-1. To include N you need range(1, N+1), because stop is always excluded.
list(range(3))[3] to get the last element.
IndexError — the elements are 0,1,2 at positions 0,1,2; index 3 is past the end. The last element sits at index len-1.

Why questions

Why is stop excluded instead of included?
So that len(range(n)) == n and so adjacent ranges tile cleanly: range(0,5) then range(5,10) cover 0..9 with no overlap and no gap.
Why does range count from 0 by default rather than 1?
Because Python indexing starts at 0, so range(len(x)) lines up exactly with the valid positions of a sequence.
Why must the step be non-zero?
A step of 0 keeps producing start forever — an infinite sequence with no next value — so Python rejects it as a ValueError up front.
Why does a huge range(10**9) not run out of memory?
It stores only start, stop, step and computes each term via on demand — three numbers, not a billion. This is the arithmetic progression of Arithmetic sequences evaluated one term at a time.
Why do we round up (ceil) in the count formula ?
Because any leftover distance beyond a whole number of steps still holds one more valid term that lands before the excluded wall.
Why does range support negative steps but not float steps?
Direction (sign) still lands on exact integers, but a float step would create non-integer terms, which range's integer-only contract forbids.

Edge cases

range(5, 1, -1) — an explicitly negative step with start > stop?
[5, 4, 3, 2] — the negative step means "go backward", and the direction rule keeps terms above the wall at 1, so 1 itself is excluded. This is the correct way to count down.
range(5, 5) — what and why?
Empty [] — the first term 5 already fails since , so nothing is produced.
range(5, 1) with no step given — what and why?
Empty [] — default step is +1, but you cannot climb up from 5 to 1, so max(0, negative) = 0 terms.
range(0) — what and why?
Empty [] — zero elements requested; consistent with len(range(n)) == n giving 0 when n=0.
range(-2, -8, -2) — does it work going down through negatives?
Yes — [-2, -4, -6], stopping before -8 since . Negative start, stop, and step all cooperate under the direction rule.
range(0, 10, 20) — a step bigger than the whole span?
Just [0] — only fits (); , so you get exactly one element.
range(1, 2) — smallest non-empty range?
[1] — a single element, because stop=2 excludes 2 and only 1 remains. Shows the half-open rule at its tightest.

Connections

  • for loops — every trap above shows up when range drives a loop.
  • Arithmetic sequences — the term rule is a discrete arithmetic progression.
  • Indexing and slicing — shares the exact same half-open [start, stop) convention.
  • numpy.arange / linspace — reach for these when the "float step" and "float count" traps bite.
  • enumerate() — the fix for the range(len(x)) index-juggling trap.