range function
Python programming
programming concepts
inclusive vs exclusive
duplicate question

Why does rangestart, end not include end?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Python’s range(start, end) does not include end because it follows the half-open interval convention: include the start, exclude the stop. This is not arbitrary. It makes counting, indexing, slicing, and composing adjacent ranges simpler and less error-prone.

What range Actually Produces

A quick example shows the behavior:

python
print(list(range(0, 5)))
print(list(range(2, 7)))

Output:

python
[0, 1, 2, 3, 4]
[2, 3, 4, 5, 6]

The stop value marks the point where iteration stops, not the last included element.

Half-Open Intervals Match Counting

If you want exactly n values starting at zero, range(n) gives them naturally.

python
print(list(range(5)))  # five values

That produces 0, 1, 2, 3, 4, which is five numbers. If the end were included, range(5) would produce six values and create constant off-by-one confusion.

This is one of the main reasons the design is useful: the stop parameter doubles as the length when the start is zero.

It Aligns with Zero-Based Indexing

Python sequences are zero-indexed, so valid indices for a sequence of length n are 0 through n - 1. That means range(len(sequence)) lines up exactly with the legal index positions.

python
1names = ["ava", "noah", "mia"]
2
3for i in range(len(names)):
4    print(i, names[i])

Because the stop is excluded, range(len(names)) never tries to access names[3], which would be out of bounds.

Adjacent Ranges Fit Together Cleanly

Half-open intervals compose without overlap or gaps.

python
1first = list(range(0, 5))
2second = list(range(5, 10))
3
4print(first)
5print(second)

The second range starts exactly where the first one stops. If both ends were inclusive, you would need extra adjustment logic to avoid duplication.

This becomes especially valuable in chunking, pagination, and interval-based algorithms.

It Matches Slice Semantics

Python slicing also uses a start-inclusive, stop-exclusive convention.

python
1text = "python"
2
3print(text[0:2])  # py
4print(text[2:6])  # thon

This consistency matters. Once you understand range(start, stop), list slices, string slices, and many index-based loops follow the same mental model.

Length Is Simple to Compute

With a half-open interval, the number of elements is just:

stop - start

For example, range(3, 8) has 8 - 3 = 5 values:

python
values = list(range(3, 8))
print(values)
print(len(values))

If the stop were included, length calculations would constantly need + 1, which complicates both reasoning and code.

Inclusive Ranges Are Still Possible

If you genuinely need the endpoint included, just adjust the stop value.

python
print(list(range(1, 6)))  # includes 5

For descending ranges:

python
print(list(range(5, 0, -1)))   # 5 down to 1
print(list(range(5, -1, -1)))  # 5 down to 0

Python does not block inclusive behavior. It simply chooses a more useful default convention and lets you shift the stop boundary when needed.

It Helps Prevent Off-By-One Errors

Paradoxically, many beginners think excluding the end causes off-by-one errors, when in practice it often prevents them. The convention gives you a stable rule:

  • start is included
  • stop is excluded

Once you internalize that rule, loops and slices become more predictable.

The bigger source of bugs is forgetting which convention an API uses and mixing inclusive and exclusive boundaries inconsistently across the same piece of code.

A Practical Example: Chunking a List

Half-open intervals make chunk boundaries straightforward:

python
1items = [10, 20, 30, 40, 50, 60, 70]
2chunk_size = 3
3
4for start in range(0, len(items), chunk_size):
5    end = start + chunk_size
6    print(items[start:end])

Because slicing excludes end, the chunk logic stays clean and does not need manual overlap corrections.

Common Pitfalls

The most common mistake is reading end as “last included value” instead of “stop boundary.” Another is trying to force inclusive reasoning everywhere rather than using Python’s half-open convention consistently. Developers also sometimes forget to add one when they truly want an inclusive endpoint. Finally, mixing APIs that use different interval conventions is a common source of confusion unless you name variables clearly as start and stop.

Summary

  • 'range(start, stop) uses a start-inclusive, stop-exclusive convention.'
  • This matches zero-based indexing and sequence slicing.
  • Adjacent ranges fit together cleanly without overlap.
  • The number of produced values is easy to reason about as stop - start.
  • If you need an inclusive endpoint, adjust the stop value explicitly.

Course illustration
Course illustration

All Rights Reserved.