If you’ve been around Python long enough, you’ve probably written something like this:
r = range(10, 100)
print(r)
And then Python hits you with:
range(10, 100)
No list, no numbers, just… vibes. But don’t be fooled — there’s some real wizardry happening here. Let’s peel back the curtain and see how range
works under the hood.
🛋️ Lazy by Design
The range
object in Python is a lazy sequence. That means:
- It doesn’t generate all values upfront.
- It computes values on demand.
- It sips memory politely instead of guzzling it like a list.
For example:
r = range(1, 10**12)
print(r[0]) # 1
print(r[-1]) # 999999999999
Boom. You just represented a trillion numbers without setting your computer on fire. 🔥
📦 What Does range
Actually Store?
When you write:
r = range(10, 100, 2)
Python isn’t secretly hiding [10, 12, 14, ..., 98]
in your RAM. Instead, it only stores:
start = 10
stop = 100
step = 2
length = 45
That’s it. Four numbers. That’s like fitting an elephant into a matchbox.
đź§® How Indexing Works
Want the 5th element?
print(r[5])
Python does the math:
value = start + (index * step)
value = 10 + (5 * 2) = 20
No loops, no wasted time, just pure arithmetic magic.
Output:
20
📏 The Length Trick
range
can even tell you its length instantly:
print(len(r))
How? Formula time:
$$
length = max(0, \lceil (stop - start) / step \rceil)
$$
Example:
len(range(10, 100, 2)) # 45
No iteration needed. It just knows. đź§™
🔄 Iteration, The Lazy Way
When you do:
for x in range(10, 20, 3):
print(x)
Python starts at 10, then keeps adding the step until it hits (but doesn’t pass) the stop:
Output:
10
13
16
19
Just enough work to keep you happy, nothing more.
🛠️ DIY: A Pure Python range
Let’s re-create a baby range
to see the trick in action:
class MyRange:
def __init__(self, start, stop=None, step=1):
if stop is None:
start, stop = 0, start
self.start = start
self.stop = stop
self.step = step
self.length = max(0, (stop - start + (step - 1)) // step)
def __len__(self):
return self.length
def __getitem__(self, index):
if index < 0:
index += self.length
if index < 0 or index >= self.length:
raise IndexError("MyRange index out of range")
return self.start + index * self.step
def __iter__(self):
value = self.start
for _ in range(self.length):
yield value
value += self.step
Test it:
r = MyRange(10, 20, 3)
print(len(r)) # 4
print(r[2]) # 16
print(list(r)) # [10, 13, 16, 19]
Output:
4
16
[10, 13, 16, 19]
Yup, we just cloned range
(but worse, because Python’s version is C-powered and lightning fast ⚡).
🕰️ A Brief History Lesson
-
In Python 2, there were two players:
-
range()
→ made a full list (not lazy). -
xrange()
→ the lazy version.
-
In Python 3 (2008),
xrange
was retired, andrange
became the cool lazy one.
So when you use range
in Python 3 today, you’re really using an evolved xrange
.
🎤 Mic Drop
So the next time someone asks you what range(10, 100)
is, you can say:
"It’s not a list, it’s a lazy, memory-efficient sequence object that only stores start, stop, step, and calculates values on demand. Also, it’s basically
xrange
in disguise."
And then walk away dramatically. 🕶️
💡 Have you ever run into a Python behavior that surprised you like this? Drop it in the comments — let’s geek out together!
Top comments (3)
Your writing style is crisp, witty, and incredibly approachable—you turn internals into a fun, memorable read. The metaphors and tight examples (elephant in a matchbox!) made everything click instantly.
I am not good writer yet! (AI Inside)
Help chahiye hai bhai