All 57 lessons
Lesson 9 of 57 · Control Flow
for Loops and range(): Repeating Work Automatically
By Skillancy Editorial Team
Run the code in the free Python Compiler or on your own computer. Lessons that read files use the FreshBasket practice data: download the dataset (ZIP).
The real power of programming is repetition. Calculating the revenue of one order is arithmetic. Calculating the revenue of 6,000 orders, or sending an alert to each of 40 store managers, or checking every delivery time against a promise, is a loop. A loop says "do this block of code once for each item", and the computer never gets bored or makes a typing mistake.
In this lesson you will learn Python's for loop, the range() function that generates number sequences, and the loop patterns analysts use most: building totals, counting, producing small tables and nesting one loop inside another. Later, in pandas, you will often replace loops with whole-column operations, but understanding loops is essential, because pandas works on the same ideas and many tasks (such as calling an API for each order) still need an explicit loop.
The anatomy of a for loop
for item in collection:
# indented block: runs once per item
do_something(item)The word after for is the loop variable. On each pass, Python assigns the next element of the collection to it and then runs the indented block. When the collection is exhausted, the loop ends and execution continues with the first line that is not indented. The collection can be anything that can be stepped through one item at a time: a list, a string, the characters of a word, the numbers produced by range(), the rows of a file, and more. Such objects are called iterables.
Worked examples
Example 1: Looping over a list
Question: FreshBasket runs stores in four cities. Print a status line for each.
cities = ["Bengaluru", "Mumbai", "Delhi", "Hyderabad"]
for city in cities:
print(f"Checking stock in {city}...")
print("All cities checked.")Output
Checking stock in Bengaluru...
Checking stock in Mumbai...
Checking stock in Delhi...
Checking stock in Hyderabad...
All cities checked.How it works: The list cities has four elements. The loop assigns each one to city in turn and runs the indented print. After the fourth pass, the loop ends and the final unindented print runs once.
Reading the result: There are four "Checking stock" lines, one per city, followed by a single closing line. The closing line is not indented, so it is outside the loop. Move it under the loop (indent it) and it would print four times, a good experiment to try.
Example 2: Looping over a string
Question: Count how many digits are in the phone number "+91 98765-43210".
phone = "+91 98765-43210"
digit_count = 0
for ch in phone:
if ch.isdigit():
digit_count += 1
print(digit_count)Output
12How it works: A string is a sequence of characters, so a for loop visits one character at a time. We start a counter at zero, and each time the current character is a digit, add one to it. This "start at zero, update in the loop" shape is the accumulator pattern.
Reading the result: The phone number has 12 digits, which is the 2 of the country code plus the 10 of the number itself. The isdigit() method ignores the plus sign, space and hyphen.
Example 3: range() basics
Question: What numbers does range produce?
print(list(range(5)))
print(list(range(1, 6)))
print(list(range(0, 50, 10)))
print(list(range(10, 0, -3)))
print(len(range(100)))Output
[0, 1, 2, 3, 4]
[1, 2, 3, 4, 5]
[0, 10, 20, 30, 40]
[10, 7, 4, 1]
100How it works: range(stop) counts from 0 up to, but not including, stop. range(start, stop) begins at start, and range(start, stop, step) moves by step each time, which can be negative to count down. range is lazy: it does not store all the numbers, which is why range(100) costs almost nothing. We wrapped it in list() only so you can see its contents.
Reading the result: range(5) gives 0 to 4, and range(1, 6) gives 1 to 5. The stepping version produces 0, 10, 20, 30, 40, and the countdown gives 10, 7, 4, 1. The stop value is never included, and this single rule explains most "off by one" bugs.
Example 4: Running total
Question: Daily orders for a week were 118, 124, 131, 120, 160, 188, 175. Find the weekly total and the daily average.
daily_orders = [118, 124, 131, 120, 160, 188, 175]
total = 0
for n in daily_orders:
total += n
average = total / len(daily_orders)
print(total, round(average, 1))Output
1016 145.1How it works: The accumulator total starts at 0 and grows by each day's orders. After the loop we divide by the number of days using len(). (Python also provides sum(daily_orders), which does the same in one call, and you should prefer it in practice; writing the loop once is how you learn what sum does.)
Reading the result: The week had 1016 orders, an average of 145.1 a day. A quick cross-check: sum(daily_orders) also gives 1016.
Example 5: Counting and finding the maximum
Question: Which day had the highest orders, and how many days beat 150?
days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
busy_days = 0
best_orders = 0
best_day = ""
for i in range(len(daily_orders)):
if daily_orders[i] > 150:
busy_days += 1
if daily_orders[i] > best_orders:
best_orders = daily_orders[i]
best_day = days[i]
print(busy_days, best_day, best_orders)Output
3 Sat 188How it works: Looping with range(len(...)) gives us the position i, which we use to look in two parallel lists. Two accumulators are updated: a counter for days over 150, and a "best so far" pair that is replaced whenever a larger value appears.
Reading the result: 3 days exceeded 150 orders, and the peak was Sat with 188 orders. This pattern of keeping the best value seen so far is how max works internally. The next example shows a neater way to get the position and the value together.
Example 6: enumerate and zip, a preview
Question: Print a numbered list of days with their orders, without using range(len(...)).
for number, (day, n) in enumerate(zip(days, daily_orders), start=1):
print(f"{number}. {day}: {n}")Output
1. Mon: 118
2. Tue: 124
3. Wed: 131
4. Thu: 120
5. Fri: 160
6. Sat: 188
7. Sun: 175How it works: zip(days, daily_orders) pairs up elements from the two lists: ("Mon", 118), ("Tue", 124) and so on. enumerate(..., start=1) numbers the pairs starting from 1. The loop variable (day, n) in brackets unpacks each pair into two names. Both functions are covered fully in the loop-patterns lesson, but they are so useful that you should start seeing them early.
Reading the result: Seven numbered lines appear, each pairing a weekday with its orders. This is cleaner and less error-prone than indexing two lists by hand.
Example 7: A multiplication table
Question: Print the 7 times table up to 10 using range and an f-string.
for i in range(1, 11):
print(f"7 x {i:>2} = {7 * i:>2}")Output
7 x 1 = 7
7 x 2 = 14
7 x 3 = 21
7 x 4 = 28
7 x 5 = 35
7 x 6 = 42
7 x 7 = 49
7 x 8 = 56
7 x 9 = 63
7 x 10 = 70How it works: range(1, 11) yields 1 to 10. Each pass computes 7 * i and formats both numbers in two-character columns (:>2) so the results line up.
Reading the result: Ten lines, ending with 7 x 10 = 70. Use the same shape to generate a column of cumulative targets, a series of dates, or a ladder of discount slabs.
Example 8: A text bar chart
Question: Show the weekly orders as a simple chart made from characters.
for day, n in zip(days, daily_orders):
bar = "#" * (n // 10)
print(f"{day} {bar} {n}")Output
Mon ########### 118
Tue ############ 124
Wed ############# 131
Thu ############ 120
Fri ################ 160
Sat ################## 188
Sun ################# 175How it works: Multiplying a string by an integer repeats it, so "#" * 13 gives thirteen hash marks. We scale each value down by dividing by 10 using floor division, so the bars fit on screen.
Reading the result: The bars grow towards the weekend and peak on Saturday, which matches the data. Real charts come in the visualization module, but this trick is very useful for quick checks in a terminal.
Example 9: Nested loops
Question: Create every combination of 3 stores and 2 delivery slots to plan a staffing grid.
stores = ["S01", "S02", "S03"]
slots = ["Morning", "Evening"]
combos = 0
for store in stores:
for slot in slots:
print(f"{store} - {slot}")
combos += 1
print(combos)Output
S01 - Morning
S01 - Evening
S02 - Morning
S02 - Evening
S03 - Morning
S03 - Evening
6How it works: For each store (outer loop), the inner loop runs completely through all slots. The inner body therefore runs 3 × 2 = 6 times. Indentation again shows the structure: the inner for is indented under the outer one, and its body is indented one level more.
Reading the result: All 6 combinations appear, grouped by store. Be careful with nesting: two loops of 1,000 items each do a million passes, and three levels do a billion. When data is large, pandas operations are far faster than nested Python loops.
Example 10: break and continue in a for loop
Question: From the daily orders, stop at the first day above 180, and separately skip days below 120 when summing.
for day, n in zip(days, daily_orders):
if n > 180:
print("First day above 180:", day)
break
total_without_slow = 0
for n in daily_orders:
if n < 120:
continue
total_without_slow += n
print(total_without_slow)Output
First day above 180: Sat
898How it works: break ends the loop immediately. continue skips the rest of the current pass and jumps to the next item. Together they let you stop early when you find what you need, and ignore items that do not qualify.
Reading the result: The loop stopped at Sat (the first day above 180). The skipped day was the one with 118 orders, so the filtered total is 898, which is the full week's 1016 minus 118.
Common mistakes
1. Forgetting that range(5) stops at 4. If you need 1 to 5, write range(1, 6).
2. Modifying the list you are looping over. Removing items from a list during a loop causes items to be skipped. Build a new list instead.
3. Resetting the accumulator inside the loop.
total = 0
for n in [10, 20, 30]:
total = 0
total += n
print(total)Output
30The answer is 30, not 60, because total was set back to zero on every pass. Initialise accumulators before the loop.
4. Wrong indentation of the summary line. A print(total) indented under the loop prints a running total each time rather than one final result.
5. Using for i in range(len(x)) when you do not need the index. Loop over the items directly, and use enumerate if you need both.
for i in range(3)
print(i)Output
File "example.py", line 1
for i in range(3)
^
SyntaxError: expected ':'Practice questions
Practice 1 (Easy): Sum of squares
Print the sum of the squares of the numbers 1 to 10.
Show solution
total = 0
for n in range(1, 11):
total += n ** 2
print(total)Output
385range(1, 11) gives 1 through 10, each squared with ** and added to the accumulator. The result is 385.
Practice 2 (Medium): Revenue with GST
A list of basket values is [240, 575, 1180, 310]. Print each basket with 5% GST added, and then the grand total, rounded to 2 decimals.
Show solution
baskets = [240, 575, 1180, 310]
grand_total = 0
for b in baskets:
with_gst = b * 1.05
grand_total += with_gst
print(f"{b:>5} -> {with_gst:>8.2f}")
print("Total:", round(grand_total, 2))Output
240 -> 252.00
575 -> 603.75
1180 -> 1239.00
310 -> 325.50
Total: 2420.25Each pass computes the taxed amount, prints it aligned in a column, and adds it to the accumulator. The grand total of 2420.25 could also be obtained as sum(baskets) * 1.05, which is a useful check.
Practice 3 (Hard): Pyramid and primes
Print a pyramid of stars with 4 rows (1, 2, 3 and 4 stars). Then print the prime numbers below 30 using nested loops.
Show solution
for row in range(1, 5):
print("*" * row)
primes = []
for n in range(2, 30):
is_prime = True
for d in range(2, n):
if n % d == 0:
is_prime = False
break
if is_prime:
primes.append(n)
print(primes)Output
*
**
***
****
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]The pyramid multiplies the star by the row number. For primes, the inner loop tests divisors; the first divisor that goes in evenly proves the number is not prime, so break exits early. The result is 10 primes below 30.
Key takeaways
- A
forloop runs its indented body once for each item of an iterable, assigning the item to the loop variable. range(stop),range(start, stop)andrange(start, stop, step)generate integer sequences that never includestop.- Create accumulators (totals, counters, "best so far") before the loop and update them inside it.
enumerategives positions andzippairs lists, which is cleaner than indexing by hand.- Nested loops multiply the work, so avoid them on large datasets and prefer pandas operations.
breakleaves the loop early, andcontinueskips to the next item.
Frequently asked questions
What is the difference between iterable and iterator?
An iterable is something you can loop over (a list, string, range). An iterator is the object that remembers where you are during the loop. for handles that automatically, and we meet explicit iterators in the advanced Python lesson.
Why does the loop variable still exist after the loop?
Python does not create a separate scope for loops, so the loop variable keeps the last value afterwards. This is why day in Example 10 still holds Sat, the value from the pass where the loop stopped. It is mostly harmless, but avoid relying on it.
When should I use a loop in pandas?
Almost never for column calculations, because vectorised operations are much faster and shorter. Use loops for things pandas cannot do in bulk, such as calling an API per row or writing a separate file per store.
Can I loop over two lists together?
Yes, with zip(list_a, list_b). It stops at the shorter list. Use itertools.zip_longest if you need to continue to the longest one.
How do I loop in reverse?
Use reversed(items) or a negative-step range, for example range(10, 0, -1). For lists you can also loop over items[::-1].
Is there a for loop that counts down by decimals?
range only works with integers. For decimals, loop over an integer range and scale it, e.g. x = i / 10, or use NumPy's arange and linspace, which we meet in the NumPy module.
Keep learning Python
One email a week with Python and analytics guides, new practice questions and cohort dates. No spam, unsubscribe anytime.
Ready to practise? Try the Python interview questions