Here is the basic use of range():

In :
for i in range(5):
print(i)

0
1
2
3
4


We can output 1...5 instead using

In :
for i in range(5):
print(i+1)

1
2
3
4
5


But that's not the clearest thing. Instead, range lets us specify where we want to start counting:

In :
for i in range(1, 6):
print(i)

1
2
3
4
5


for i in range(a, b) assigns a, a+1, a+2, ..., b-1 to the variable i. We go up to, but not including, b.

Suppose now that we want to print 2, 4, 6, 8 -- go up by increments of 2 instead of increments of 1. We could do it like this:

In :
for i in range(1, 4):
print(2*i)

2
4
6


range() actually allows us to specify the increment/step size:

In :
for i in range(2, 7, 2):
print(i)

2
4
6


In general, range(a, b, step) assigns a a + step a + 2step a + 3step ... (up to the last possible number < b)

to the variable i.

Note that the following also produces 2, 4, 6:

In :
for i in range(2, 8, 2):
print(i)

2
4
6


That's because the upper limit, which should not be reached, was 8, so we didn't print 8 as well.

We can also go backwards, by specifying a negative step size:

In :
for i in range(5, 2, -1):
print(i)

5
4
3


We went down from 5 to 2, without including 2. The following is also possible:

In :
for i in range(10, 1, -3):
print(i)

10
7
4


How could we accomplish the same kind of thing with the basic use of range? Let's count from 4 down to 0.

In :
n = 5
for i in range(n):
print(n-1-i)

4
3
2
1
0


Note that the following will note print anything:

In :
for i in range(5, 2):
print(i)

In :
for i in range(1, 10, -1):
print(i)


That's because, if we're using range(a, b, step), if step positive, we need a < b, and if step size is negative, we need a > b in order for the range to not be empty.

Recall also that

In :
for i in range(5, 5):
print(i)


prints nothing.