Question
Why Membership Testing in Python range() Is So Fast
Question
In Python 3, range() returns a range object rather than a fully built list. Since it produces values lazily, I would expect checking membership in a huge range to take a very long time.
For example, I would assume Python would need to generate many values before deciding whether this number is present:
1_000_000_000_000_000 in range(1_000_000_000_000_001)
However, this evaluates almost instantly, even with much larger numbers.
The same seems true for stepped ranges:
1_000_000_000_000_000_000_000 in range(0, 1_000_000_000_000_000_000_001, 10)
I also tried writing a simple generator-based version of range(), and membership checks are much slower:
def my_crappy_range(n):
i = 0
while i < n:
yield i
i += 1
What is the Python 3 range() object doing internally that makes membership testing so fast?
Short Answer
By the end of this page, you will understand that Python 3's range is not just a lazy generator-like object. It is a full sequence type that stores only start, stop, and step, and it can test membership with arithmetic instead of iterating through every value. That is why x in range(...) is usually extremely fast, even for enormous ranges.
Concept
Python 3's range is a compact arithmetic sequence, not a generator that computes values one by one for every operation.
A range object stores just three core pieces of information:
startstopstep
From those three numbers, Python can determine:
- the length of the range
- the value at a given index
- whether a value belongs to the range
For membership testing, Python does not need to generate all values. Instead, it uses math.
For a value x to be in:
range(start, stop, step)
all of these must be true:
xmust be within the range boundsxmust land exactly on one of the stepping points
That second rule can be checked with modulo arithmetic:
(x - start) % step == 0
So instead of checking one value after another, Python can answer the question in roughly constant time.
Mental Model
Think of range as a mathematical rule, not a bucket of numbers.
For example:
range(0, 20, 5)
is not really storing:
- 0
- 5
- 10
- 15
Instead, it is more like a rule that says:
"Start at 0, keep adding 5, and stop before 20."
Now imagine someone asks, "Is 15 in there?"
Python does not walk through the whole sequence. It asks:
- Is 15 between the bounds?
- Starting from 0, can I reach 15 by adding 5 some whole number of times?
If yes, then 15 is in the range.
So range behaves more like a formula than a generated list.
Syntax and Examples
Basic syntax
range(stop)
range(start, stop)
range(start, stop, step)
Examples:
range(5) # 0, 1, 2, 3, 4
range(2, 8) # 2, 3, 4, 5, 6, 7
range(0, 20, 5) # 0, 5, 10, 15
Membership testing
print(10 in range(0, 20, 5)) # True
print(11 in range(0, 20, 5)) # False
Python can answer these quickly because it checks the arithmetic pattern.
Example: huge range
x =
(x (x + ))
Step by Step Execution
Consider this example:
x = 14
r = range(2, 20, 3)
print(x in r)
The range represents:
2, 5, 8, 11, 14, 17
Python can determine membership without generating all of them.
Step 1: Check the bounds
The range starts at 2 and stops before 20, so valid values must be:
- greater than or equal to
2 - less than
20
14 passes this test.
Step 2: Check the step pattern
The step is 3, so every valid number must be reachable by repeatedly adding 3 to 2.
Python effectively checks:
Real World Use Cases
range membership logic is useful anywhere you are working with predictable numeric patterns.
Input validation
def is_valid_even_port(port):
return port in range(1024, 65536, 2)
This checks whether a port number is in an allowed even-numbered range.
Scheduling or batching
minute = 30
if minute in range(0, 60, 15):
print("Run scheduled task")
Useful for time slots, polling intervals, or repeating jobs.
Game development
if player_x in range(100, 201):
print("Player is inside the zone")
This can be used for numeric zones, score bands, or level thresholds.
Data processing
Real Codebase Usage
In real codebases, developers often use range as a lightweight sequence object, not just in for loops.
Validation and guard clauses
def set_retry_count(n):
if n not in range(1, 6):
raise ValueError("retry count must be between 1 and 5")
This is clean and expressive for integer validation.
Stepped rules
def is_valid_page_size(size):
return size in range(10, 101, 10)
Good for values that must follow a pattern.
Index and slicing support
range supports indexing and length efficiently:
r = range(10, 100, )
((r))
(r[])
Common Mistakes
Mistake 1: Thinking range is just a generator
range is lazy in the sense that it does not store all numbers, but it is not a generator.
r = range(5)
print(type(r))
It is a special sequence type.
Mistake 2: Assuming membership always iterates
Beginners often assume:
x in range(...)
must check each item one by one. For integer membership in range, Python uses arithmetic instead.
Mistake 3: Forgetting that stop is excluded
Broken expectation:
print(10 in range(0, 10)) # False
Why? Because range(0, 10) includes 0 through 9, not .
Comparisons
| Concept | Stores all values? | Supports indexing? | Membership speed | Reusable? |
|---|---|---|---|---|
range | No | Yes | Very fast for integers | Yes |
list(range(...)) | Yes | Yes | Fast, but uses more memory | Yes |
generator (yield) | No | No | Usually linear scan | Often one-pass |
range vs generator
rangeis a sequence with structure.- A generator produces values over time.
rangecan often answer questions with math.
Cheat Sheet
Quick facts
rangeis a sequence type, not a generator.- It stores
start,stop, andstep. - It does not build the whole list in memory.
- Membership for integers is fast because Python uses arithmetic.
Syntax
range(stop)
range(start, stop)
range(start, stop, step)
Membership idea
For:
x in range(start, stop, step)
Python checks roughly:
- Is
xwithin the bounds? - Does it fit the stepping rule?
Equivalent idea:
start <= x < stop and (x - start) % step == 0
For negative steps, the bounds logic is reversed.
Examples
10 ()
(, , )
(, , )
(, , -)
FAQ
Why is x in range(...) so fast in Python?
Because Python does not iterate through every value. For integer membership, it checks bounds and step alignment using arithmetic.
Does range generate all numbers lazily like a generator?
Not exactly. It does not store all numbers, but it is a sequence object, not a generator.
Is range membership always constant time?
For normal integer membership checks, it is typically very fast because it uses math instead of scanning values.
Why is my custom generator-based range much slower?
A generator usually has to yield values one by one until it finds a match or reaches the end.
Does range use less memory than a list?
Yes. A range stores only the arithmetic rule, while a list stores every element.
Can I index into a range?
Yes.
r = range(10, 20)
print(r[3]) # 13
Does range work with negative steps?
Yes.
Mini Project
Description
Build a small validator that checks whether user input belongs to allowed numeric ranges. This demonstrates how range can be used for fast membership testing in practical code, such as validating menu choices, retry counts, or stepped configuration values.
Goal
Create a Python script that validates integers against multiple range rules and prints whether each value is allowed.
Requirements
- Create at least two
rangeobjects with different steps. - Check several test values using the
inoperator. - Print clear output showing whether each value is valid.
- Include one descending range example.
- Do not convert the ranges to lists.
Keep learning
Related questions
@staticmethod vs @classmethod in Python Explained
Learn the difference between @staticmethod and @classmethod in Python with clear examples, use cases, mistakes, and a mini project.
Catch Multiple Exceptions in One except Block in Python
Learn how to catch multiple exceptions in one Python except block using tuples, with examples, mistakes, and real-world usage.
Convert Bytes to String in Python 3
Learn how to convert bytes to str in Python 3 using decode(), text mode, and proper encodings with practical examples.