range() in Python 2026: Efficient Sequence Generation + Modern Patterns & Best Practices
The built-in range() function generates an immutable sequence of integers — perfect for loops, indexing, slicing, and creating arithmetic progressions without materializing large lists in memory. In 2026 it remains one of the most memory-efficient and frequently used built-ins for controlled iteration, batch processing, array indexing, ML epoch/step counting, and algorithm implementation.
With Python 3.12–3.14+ delivering faster range iteration, better type hinting (improved generics for range), and free-threading compatibility for concurrent loops, range() is more performant and type-safe than ever. This March 24, 2026 update covers how range() works today, real-world patterns, performance advantages over list(range()), and best practices for clean, efficient, and lazy sequence generation in modern Python.
TL;DR — Key Takeaways 2026
range(start=0, stop, step=1)→ immutable sequence [start, start+step, ...] < stop- Lazy & memory-efficient — O(1) space, O(n) time only when consumed
- 2026 best practice: Prefer range() directly in for-loops; use list(range()) only when materialization is needed
- Main use cases: controlled loops, indexing, slicing, ML epoch/step counters, pagination
- Type-safe pattern:
range[int]from typing - Performance: Extremely fast creation & iteration — far better than list(range()) for large ranges
1. Basic Usage — Generating Sequences
# Simple range
for i in range(5):
print(i) # 0 1 2 3 4
# With start & step
print(list(range(10, 20, 2))) # [10, 12, 14, 16, 18]
# Reverse (negative step)
print(list(range(10, 0, -2))) # [10, 8, 6, 4, 2]
# Empty range
print(list(range(5, 5))) # []
2. Real-World Patterns in 2026
ML Epoch / Batch Indexing
def train_loop(epochs: int, batch_size: int):
for epoch in range(1, epochs + 1):
print(f"Epoch {epoch}/{epochs}")
for batch_idx in range(0, 1000, batch_size):
# process batch
pass
Pagination & Slicing Large Data
def paginate(items: list, page: int, size: int = 20):
start = (page - 1) * size
return items[start : start + size]
items = list(range(45))
size = 20
print(paginate(items, 3)) # [40, 41, 42, 43, 44]
# Or generate page numbers lazily
for page_num in range(1, len(items) // size + 2):
print(f"Page {page_num}")
Indexed Parallel Processing (with zip & enumerate)
names = ["Alice", "Bob", "Charlie"]
scores = [95, 87, 92]
for idx, (name, score) in enumerate(zip(names, scores), start=1):
print(f"Rank {idx}: {name} ({score})")
3. range() vs Alternatives – Comparison 2026
| Approach | Memory | Laziness | Best For |
|---|---|---|---|
| range(start, stop, step) | O(1) | Lazy | Loops, indexing, large ranges |
| list(range(n)) | O(n) | Eager | When you need full list |
| numpy.arange() | O(n) | Eager array | Numeric arrays, ML |
| range in for-loop | O(1) | Lazy | Standard iteration |
4. Best Practices & Performance in 2026
- Use range() directly in for-loops — no need for list(range())
- Type hints 2026:
from typing import Iterator def count_up_to(n: int) -> Iterator[int]: return iter(range(n)) - Performance: range() is O(1) space — ideal for very large ranges
- Free-threading (3.14+): Safe for read-only iteration
- Avoid: list(range(large_n)) — wastes memory; use range() lazily
Why range() Uses Almost No Memory
range(start, stop, step) does not store its numbers. It stores only the three arguments and calculates each value when asked. That is why range(10**12) is created instantly, why len() and indexing are fast, and why x in range(...) is answered with arithmetic instead of a scan. The stop value is always excluded, and a negative step counts down.
Worked Example: Splitting Work into Ranges
This example creates index ranges to process a dataset in fixed-size pieces.
def batches(n_items, size):
return [range(i, min(i + size, n_items)) for i in range(0, n_items, size)]
for r in batches(10, 4):
print(r, list(r))
# range(0, 4) [0, 1, 2, 3]
# range(4, 8) [4, 5, 6, 7]
# range(8, 10) [8, 9]
big = range(0, 10**12, 2)
print(len(big), 999_999 in big, big[-1]) # 500000000000 False 999999999998
Each range object is tiny, so even millions of batches cost almost nothing to describe.
Common Mistakes
- Off-by-one errors:
range(1, 10)stops at 9 - Writing
for i in range(len(items)):enumerate()is clearer - Converting to a list unnecessarily: it loses the memory advantage
Quick Summary
rangeis lazy and memory-light- Stop is exclusive
- Supports fast
len, indexing andin
Conclusion — range() in 2026: Efficient Sequence Generator
range() is Python’s go-to for creating arithmetic sequences without wasting memory — the foundation of controlled loops, indexing, and batch processing. In 2026, use it directly in for-loops, combine it with enumerate() and zip() for powerful iteration, and rely on it in ML epoch counters, pagination, and data pipelines. It’s fast, memory-efficient, and one of Python’s most essential built-ins for performant, readable code.
Next steps:
- Replace any list(range()) in loops with plain range()