range() is Python’s built-in function for generating arithmetic progressions — immutable sequences of integers with start, stop (exclusive), and optional step, used everywhere for looping, indexing, slicing, and memory-efficient iteration. In 2026, range() remains one of the most powerful and lightweight tools in data science (indexing pandas/Polars/Dask DataFrames, chunking large datasets), software engineering (controlled loops, list comprehensions, NumPy array creation), and performance-critical code — zero memory overhead for large ranges, fast, and fully lazy until materialized (via list(), for loop, indexing).
Here’s a complete, practical guide to using range() in Python: basic creation & slicing, step & negative ranges, real-world patterns (earthquake time series indexing, chunked processing, strided access), and modern best practices with type hints, performance, memory, and integration with NumPy/Dask/Polars/pandas/xarray.
Basic range() creation — start (inclusive), stop (exclusive), step (default 1).
# range(stop) ? 0 to stop-1
print(list(range(5))) # [0, 1, 2, 3, 4]
# range(start, stop)
print(list(range(2, 8))) # [2, 3, 4, 5, 6, 7]
# range(start, stop, step)
print(list(range(1, 10, 2))) # [1, 3, 5, 7, 9]
# Negative step (reverse)
print(list(range(10, 0, -2))) # [10, 8, 6, 4, 2]
# Empty ranges return empty
print(list(range(5, 5))) # []
print(list(range(5, 0))) # [] (no step ? positive direction)
Slicing & indexing ranges — range objects support slicing and indexing like lists (Python 3.2+).
r = range(0, 100, 10)
print(r[3]) # 30
print(list(r[2:7:2])) # [20, 40, 60] (slice with step)
# Negative indices work too
print(r[-1]) # 90 (last element)
print(list(r[::-1])) # [90, 80, ..., 10, 0] (reversed)
Real-world pattern: earthquake time series indexing & chunked processing — use range for efficient looping & strided access.
import pandas as pd
df = pd.read_csv('earthquakes.csv', parse_dates=['time'])
# 1. Loop over rows with index (classic)
for i in range(len(df)):
row = df.iloc[i]
if row['mag'] >= 7.0:
print(f"#{i+1}: Mag {row['mag']:.1f} at {row['place']}")
# 2. Strided access (every 100th event)
for i in range(0, len(df), 100):
print(df.iloc[i][['time', 'mag', 'place']])
# 3. Chunked processing (large file simulation)
chunk_size = 100_000
for start in range(0, len(df), chunk_size):
chunk = df.iloc[start:start+chunk_size]
strong = chunk[chunk['mag'] >= 7.0]
print(f"Chunk {start}-{start+len(chunk)}: {len(strong)} strong events")
# Dask equivalent (parallel chunks)
import dask.dataframe as dd
ddf = dd.from_pandas(df, npartitions=8)
print(f"Total partitions: {ddf.npartitions}")
for i, chunk in enumerate(ddf.to_delayed()):
df_chunk = chunk.compute()
print(f"Partition {i}: {len(df_chunk)} rows")
Best practices for range() in Python & data workflows. Prefer range(len(seq)) — over for i in range(len(seq)) when index needed; use enumerate(seq) for index + value. Modern tip: use Polars pl.int_range(start, stop, step) — lazy range expressions; Dask da.arange() for distributed arrays. Use range(start, stop, step) — explicit step for clarity. Use negative step — for reverse iteration. Use range() in slicing — lst[::2] equivalent to lst[list(range(0, len(lst), 2))]. Add type hints — def process_indices(n: int) -> None: for i in range(n): .... Use range(0) — empty range (useful in loops). Use range(1, n+1) — 1-based indexing. Avoid list(range(n)) — unless you need the list (memory waste); iterate range directly. Use range() with zip() — for i, val in zip(range(len(lst)), lst) (but prefer enumerate). Use range() in generators — yield from range(n). Use range() with numpy.arange() — when array needed. Use range() in comprehensions — [f(i) for i in range(n)]. Use range() for countdown — range(n-1, -1, -1). Use range() with tqdm(range(n)) — progress bars. Use range() in multiprocessing — for i in range(mp.cpu_count()). Use range() in asyncio — for i in range(10): await task(i).
range(start=0, stop, step=1) generates immutable sequences of integers — lazy, memory-efficient, supports slicing/indexing, perfect for loops, indexing, chunking, and strided access. In 2026, use directly in loops, combine with enumerate/zip, integrate with pandas/Polars/Dask for row/partition iteration, and prefer over list(range(n)) to save memory. Master range(), and you’ll write efficient, readable, scalable iteration code in any Python workflow.
Next time you need a sequence of numbers — use range(). It’s Python’s cleanest way to say: “Generate numbers from start to stop — lazy, fast, and memory-free.”