Of course! This is a classic and very important topic in Python, especially for those who have worked with Python 2 or are concerned with performance and memory usage.

Here’s a detailed breakdown of range and xrange.
The Short Answer (TL;DR)
- In Python 3: There is only
range. It is a smart, memory-efficient object that generates numbers on the fly when you iterate over it. It behaves like Python 2'sxrange. - In Python 2: There were two types:
range: Created a full list of numbers in memory, which could be very memory-intensive for large ranges.xrange: A memory-efficient object similar to Python 3'srange. It's the one you should have used for large numbers.
If you are only using Python 3, you can forget about xrange entirely. Just use range.
Detailed Comparison
Let's break down the differences in a table and then explore the concepts in more detail.
| Feature | range (Python 3) |
xrange (Python 2) |
|---|---|---|
| Type | A sequence type (specifically, an immutable sequence). |
A sequence type, but not a full list. |
| Memory Usage | Low and Constant. It stores only the start, stop, and step values. |
Low and Constant. Same as Python 3's range. |
| Performance | Excellent for iteration. Very fast because it doesn't pre-calculate values. | Excellent for iteration. Same as Python 3's range. |
| List Creation | list(range(10**6)) creates a real list with 1,000,000 elements in memory. |
range(10**6) creates the xrange object. list(xrange(10**6)) creates the full list. |
| Indexing/Slicing | Supported. You can access elements by index (e.g., r[5]) or slice (e.g., r[1:10]). |
Supported. Same as Python 3's range. |
| Main Use Case | The standard, preferred way to generate a sequence of numbers for loops and other purposes. | The memory-efficient way to generate a sequence of numbers. The standard range was often avoided for large numbers. |
| Existence in Python 3 | Does not exist. This is the most important point. | Removed. Its functionality was merged into range. |
range in Python 3 (The Modern Way)
In Python 3, range is a versatile and highly optimized object. It represents an immutable sequence of numbers and doesn't actually store all the numbers in memory.

How it Works (Lazy Evaluation)
range is "lazy." It calculates the next number in the sequence only when it's needed (i.e., during a loop or when you ask for a specific element by index).
Example: Creating a range object
# This does NOT create a list of 1 million numbers in memory r = range(1_000_000) print(type(r)) # <class 'range'> # You can check its properties, but it's tiny in memory print(r) # range(0, 1000000) print(len(r)) # 1000000 # You can access elements by index (it calculates it on the fly) print(r[500_000]) # 500000
Notice that range(1_000_000) is an object that takes up very little space, regardless of how large the range is.
Example: Using range in a Loop
This is the most common use case. The loop iterates over the range object, and one number at a time is generated and assigned to i.

# This loop is very memory-efficient
total = 0
for i in range(1, 1_000_001):
total += i
print(f"The sum of numbers from 1 to 1,000,000 is: {total}")
# The sum of numbers from 1 to 1,000,000 is: 500000500000
Example: Creating a List from a range
You can explicitly convert a range object into a list using the list() constructor. This is the only time the full list of numbers is created in memory.
# This WILL create a list of 10 elements in memory small_list = list(range(10)) print(small_list) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # Be careful! This will consume a lot of memory # large_list = list(range(10_000_000)) # Not recommended on machines with limited RAM
xrange in Python 2 (The Old Way)
In Python 2, the situation was different. The default range function was not memory-efficient.
range in Python 2
When you called range(1000), Python 2 would immediately create a list containing all 1000 integers.
# Python 2 r = range(10) print(type(r)) # <type 'list'> print(r) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # This would use a lot of memory for large ranges # r = range(10000000) # Creates a list with 10 million integers!
This was fine for small ranges but could crash your program or consume all available memory for very large ranges.
xrange in Python 2 (The Solution)
xrange was introduced to solve this exact problem. It worked just like the modern range in Python 3.
- It was a memory-efficient object.
- It generated numbers on the fly.
- It supported indexing and slicing.
# Python 2 xr = xrange(10) print(type(xr)) # <type 'xrange'> print(xr) # xrange(10) # It also supported indexing print(xr[5]) # 5 # To get a list, you had to convert it print(list(xr)) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Key Takeaways and Best Practices
- You are using Python 3. You only need to know about
range. Thexrangefrom Python 2 is gone. - Use
rangefor all your loops. It's fast, memory-efficient, and the standard way to do it.for i in range(10): print(i) - Use
list(range(...))only when you explicitly need a list. If you need to perform list operations like multiple indexing, sorting, or passing to a function that requires a list, then convert it. But be mindful of the memory cost for very large ranges. - Don't worry about performance for iteration. The lazy evaluation of
rangemakes it extremely fast forforloops because it avoids the overhead of creating and managing a huge list in memory. - Understand the difference between the object and the list.
range(10)is an object.list(range(10))is a list. This distinction is crucial for writing memory-efficient code.
