Python’s built-in `range`

function is handy when you need to perform an action a specific number of times. As an experienced Pythonista you’ve most likely used it before. But what does it do?

By the end of this guide, you’ll:

- Understand how the Python
`range`

function works - Know how the implementations differ in Python 2 and Python 3
- Have seen a number of hands-on
`range()`

examples - Be equipped to work around some of its limitations

Let’s get cracking!

**Free Bonus:** Click here to get our free Python Cheat Sheet that shows you the basics of Python 3, like working with data types, dictionaries, lists, and Python functions.

## The History of Python’s `range()`

Function

Although `range()`

in Python 2 and `range()`

in Python 3 may share a name, they are entirely different animals. In fact, `range()`

in Python 3 is just a renamed version of a function that is called `xrange`

in Python 2.

Originally, both `range()`

and `xrange()`

produced numbers that could be iterated over with for-loops, but the former generated a list of those numbers all at once while the latter produced numbers lazily, meaning numbers were returned one at a time as they were needed.

Having huge lists hang around takes up memory, so it’s no surprise that `xrange()`

replaced `range()`

, name and all. You can read more about this decision and the `xrange()`

vs `range()`

background in PEP 3100.

**Note:** PEP stands for Python Enhancement Proposal. PEPs are documents that can cover a wide range of topics, including proposed new features, style, governance, and philosophy.

There are a ton of them. PEP 1 explains how they work and is a great place to start.

For the rest of this article, you’ll be using the function as it exists in Python 3.

Here we go!

## Let’s Loop

Before we dive into seeing how `range()`

works, we need to take a look at how looping works. Looping is a key computer science concept. If you want to be a good programmer, mastering loops is among the first steps you need to take.

Here’s an example of a for-loop in Python:

```
captains = ['Janeway', 'Picard', 'Sisko']
for captain in captains:
print(captain)
```

The output looks like this:

```
Janeway
Picard
Sisko
```

As you can see, a for-loop enables you to execute a specific block of code however many times you want. In this case, we looped through a list of captains and printed each of their names.

Although Star Trek is great and everything, you may want to do more than simply loop through a list of captains. Sometimes, you just want to execute a block of code a specific number of times. Loops can help you do that!

Try the following code with numbers that are divisible by three:

```
numbers_divisible_by_three = [3, 6, 9, 12, 15]
for num in numbers_divisible_by_three:
quotient = num / 3
print(f"{num} divided by 3 is {int(quotient)}.")
```

The output of that loop will look like this:

```
3 divided by 3 is 1.
6 divided by 3 is 2.
9 divided by 3 is 3.
12 divided by 3 is 4.
15 divided by 3 is 5.
```

That’s the output we wanted, so the loop got the job done adequately, but there is another way to get the same result by using `range()`

.

**Note:** That last code example had some string formatting. To learn more on that topic, you can check out Python String Formatting Best Practices and Python 3’s f-Strings: An Improved String Formatting Syntax (Guide).

Now that you’re more familiar with loops, let’s see how you can use `range()`

to simplify your life.

### Python `range()`

Basics

So how does Python’s `range`

function work? In simple terms, `range()`

allows you to generate a series of numbers within a given range. Depending on how many arguments you pass to the function, you can decide where that series of numbers will begin and end as well as how big the difference will be between one number and the next.

Here’s a sneak peek of `range()`

in action:

```
for i in range(3, 16, 3):
quotient = i / 3
print(f"{i} divided by 3 is {int(quotient)}.")
```

In this for-loop, you were able to simply create a range of numbers that are divisible by `3`

, so you didn’t have to provide each of them yourself.

**Note:** While this example shows an appropriate use of `range()`

, it’s usually frowned upon to use `range()`

too often in for-loops.

For example, the following use of `range()`

would generally be considered not Pythonic:

```
captains = ['Janeway', 'Picard', 'Sisko']
for i in range(len(captains)):
print(captains[i])
```

`range()`

is great for creating iterables of numbers, but it’s not the best choice when you need to iterate over data that could be looped over with the `in`

operator.

If you want to know more, check out How to Make Your Python Loops More Pythonic.

There are three ways you can call `range()`

:

`range(stop)`

takes one argument.`range(start, stop)`

takes two arguments.`range(start, stop, step)`

takes three arguments.

`range(stop)`

When you call `range()`

with one argument, you will get a series of numbers that starts at `0`

and includes every whole number up to, but not including, the number you have provided as the `stop`

.

Here’s what that looks like in practice:

```
for i in range(3):
print(i)
```

The output of your loop will look like this:

```
0
1
2
```

That checks out: we have all the whole numbers from `0`

up to but not including `3`

, the number you provided as the `stop`

.

`range(start, stop)`

When you call `range()`

with two arguments, you get to decide not only where the series of numbers stops but also where it starts, so you don’t have to start at `0`

all the time. You can use `range()`

to generate a series of numbers from *A* to *B* using a `range(A, B)`

. Let’s find out how to generate a range starting at `1`

.

Try calling `range()`

with two arguments:

```
for i in range(1, 8):
print(i)
```

Your output will look like this:

```
1
2
3
4
5
6
7
```

So far, so good: you have all the whole numbers from `1`

(the number you provided as the `start`

) up to but not including `8`

(the number you provided as the `stop`

).

But if you add one more argument, then you’ll be able to reproduce the output you got earlier when you were using the list named `numbers_divisible_by_three`

.

`range(start, stop, step)`

When you call `range()`

with three arguments, you can choose not only where the series of numbers will start and stop but also how big the difference will be between one number and the next. If you don’t provide a `step`

, then `range()`

will automatically behave as if the `step`

is `1`

.

**Note:** `step`

can be a positive number or a negative number, but it can’t be `0`

:

```
>>> range(1, 4, 0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: range() arg 3 must not be zero
```

If you try to use `0`

as your step, then you’ll get an error.

Now that you know how to use `step`

, you can finally revisit that loop we saw earlier with division by `3`

.

Try it for yourself:

```
for i in range(3, 16, 3):
quotient = i / 3
print(f"{i} divided by 3 is {int(quotient)}.")
```

Your output will look exactly like the output of the for-loop you saw earlier in this article, when you were using the list named `numbers_divisible_by_three`

:

```
3 divided by 3 is 1.
6 divided by 3 is 2.
9 divided by 3 is 3.
12 divided by 3 is 4.
15 divided by 3 is 5.
```

As you see in this example, you can use the `step`

argument to increase towards a higher number. That’s called incrementing.

### Incrementing With `range()`

If you want to increment, then you need `step`

to be a positive number. To get an idea of what this means in practice, type in the following code:

```
for i in range(3, 100, 25):
print(i)
```

If your `step`

is `25`

, then the output of your loop will look like this:

```
3
28
53
78
```

You got a range of numbers that were each greater than the preceding number by `25`

, the `step`

you provided.

Now that you’ve seen how you can step forwards through a range, it’s time to see how you can step backwards.

### Decrementing With `range()`

If your `step`

is positive, then you move through a series of increasing numbers and are incrementing. If your `step`

is negative, then you move through a series of decreasing numbers and are decrementing. This allows you to go through the numbers backwards.

In the following example, your `step`

is `-2`

. That means that you’ll be decrementing by `2`

for each loop:

```
for i in range(10, -6, -2):
print(i)
```

The output of your decrementing loop will look like this:

```
10
8
6
4
2
0
-2
-4
```

You got a range of numbers that were each smaller than the preceding number by `2`

, the absolute value of the `step`

you provided.

The most Pythonic way to create a range that decrements is to use `range(start, stop, step)`

. But Python does have a built-in `reversed`

function. If you wrap `range()`

inside `reversed()`

, then you can print the integers in reverse order.

Give this a try:

```
for i in reversed(range(5)):
print(i)
```

You’ll get this:

```
4
3
2
1
0
```

`range()`

makes it possible to iterate over a decrementing sequence of numbers, whereas `reversed()`

is generally used to loop over a sequence in reverse order.

**Note:** `reversed()`

also works with strings. You can learn more about the functionality of `reversed()`

with strings in How to Reverse a String in Python.

### Advanced Usage Examples for Python’s `range()`

Function

Now that you know the basics of how to use `range()`

, it’s time to dig a little deeper.

`range()`

is mainly used for two purposes:

- Executing the body of a for-loop a specific number of times
- Creating more efficient iterables of integers than can be done using lists or tuples

The first use is probably the most common, and you could make the case that itertools gives you a more efficient way to construct iterables than `range()`

does.

Here are a few more points to keep in mind when you use range.

`range()`

is a type in Python:

```
>>> type(range(3))
<class 'range'>
```

You can access items in a `range()`

by index, just as you would with a list:

```
>>> range(3)[1]
1
>>> range(3)[2]
2
```

You can even use slicing notation on a `range()`

, but the output in a REPL may seem a little strange at first:

```
>>> range(6)[2:5]
range(2, 5)
```

Although that output may look odd, slicing a `range()`

just returns another `range()`

.

The fact that you can access elements of a `range()`

by index and slice a `range()`

highlights an important fact: `range()`

is lazy, unlike a list, but isn’t an iterator.

## Floats and `range()`

You may have noticed that all of the numbers we have been dealing with so far have been whole numbers, which are also called integers. That’s because `range()`

can take only integers as arguments.

### A Word on Floats

In Python, if a number is not a whole number, then it is a float. There are some differences between integers and floats.

An integer (`int`

data type):

- Is a whole number
- Does not include a decimal point
- Can be positive, negative, or
`0`

A floating point number (`float`

data type):

- Can be any number that includes a decimal point
- Can be positive or negative

Try calling `range()`

with a float and see what happens:

```
for i in range(3.3):
print(i)
```

You should get the following error message:

```
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'float' object cannot be interpreted as an integer
```

If you need to find a workaround that will allow you to use floats, then you can use NumPy.

### Using `range()`

With NumPy

NumPy is a third-party Python library. If you are going to use NumPy, your first step is to check if you have it installed.

Here’s how you can do that in your REPL:

```
>>> import numpy
```

If you get a `ModuleNotFoundError`

, then you need to install it. To do so, go to your command line and enter `pip install numpy`

.

Once you have it installed, put in the following:

```
import numpy as np
np.arange(0.3, 1.6, 0.3)
```

It will return this:

```
array([0.3, 0.6, 0.9, 1.2, 1.5])
```

If you want to print each number on its own line, you can do the following:

```
import numpy as np
for i in np.arange(0.3, 1.6, 0.3):
print(i)
```

This is the output:

```
0.3
0.6
0.8999999999999999
1.2
1.5
```

Where did `0.8999999999999999`

come from?

Computers have trouble saving decimal floating-point numbers in binary floating-point numbers. This leads to all sorts of unexpected representations of numbers.

**Note:** To learn more about why there are issues representing decimals, you can check out this article and the Python docs.

You might also want to take a look at the decimal library, which is a bit of a downgrade in terms of performance and readability but allows you to represent decimal numbers exactly.

Another option is to use `round()`

, which you can read more about in How to Round Numbers in Python. Keep in mind that `round()`

has its own quirks that might generate some surprising results!

Whether or not these floating point errors are an issue for you depends on the problem you’re solving. The errors are going to be in something like the 16th decimal place, which is insignificant most of the time. They are so small that, unless you’re working on calculating satellite orbital trajectories or something, you don’t need to worry about it.

Alternatively, you could also use `np.linspace()`

. It does essentially the same thing but uses different parameters. With `np.linspace()`

, you specify `start`

and `end`

(both inclusive) as well as the length of the array (instead of `step`

).

For instance, `np.linspace(1, 4, 20)`

gives 20 equally spaced numbers: `1.0, ..., 4.0`

. On the other hand, `np.linspace(0, 0.5, 51)`

gives `0.00, 0.01, 0.02, 0.03, ..., 0.49, 0.50`

.

**Note:** To learn more, you can read Look Ma, No For-Loops: Array Programming With NumPy and this handy NumPy reference.

## Go Forth and Loop

You now understand how to use `range()`

and work around its limitations. You also have an idea of how this important function has evolved between Python 2 and Python 3.

The next time you need to perform an action a specific number of times, you’ll be all set to loop your heart out!

Happy Pythoning!