All 57 lessons
Lesson 11 of 57 · Control Flow
Loop Patterns: enumerate, zip, Accumulators and Nested Loops
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).
By now you know how for and while work. The next step is learning the patterns that experienced programmers reach for again and again. A pattern is a small, reusable shape for solving a common problem: add everything up, count the matches, find the biggest, compare each item with the previous one, pair two lists, number the items, build a filtered copy.
Learning these shapes has two payoffs. They make your Python code short and readable, and they translate directly to pandas, where the same ideas appear as sum, count, max, diff, zip-like column pairing and boolean filtering. This lesson closes Module 2 by assembling everything about control flow into a toolkit, using FreshBasket data.
The pattern toolkit at a glance
| Goal | Pattern | Pandas cousin |
|---|---|---|
| Total | total = 0 then total += x | .sum() |
| Count matches | count += 1 inside an if | (cond).sum() |
| Biggest and where | keep best and best_pos | .max(), .idxmax() |
| Running total | append total each pass | .cumsum() |
| Change vs previous | loop from index 1 | .diff(), .pct_change() |
| Filter | [x for x in items if cond] | df[cond] |
| Transform | [f(x) for x in items] | .apply(), vectorised maths |
| Pair two lists | zip(a, b) | columns of a DataFrame |
Worked examples
We use a small set of real monthly figures for this lesson: orders and revenue (in ₹ thousands) for FreshBasket Pune, first half of 2026.
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
orders = [310, 295, 342, 388, 415, 460]
revenue_k = [158, 149, 176, 201, 224, 249]Example 1: enumerate gives position and value
Question: Print the months numbered from 1, with their orders.
for number, (month, n) in enumerate(zip(months, orders), start=1):
print(f"{number}. {month}: {n} orders")Output
1. Jan: 310 orders
2. Feb: 295 orders
3. Mar: 342 orders
4. Apr: 388 orders
5. May: 415 orders
6. Jun: 460 ordersHow it works: enumerate(iterable, start=1) produces pairs of (counter, item). Here the item is itself a pair from zip, so we unpack the nested structure number, (month, n) in the for line. Without start, counting begins at 0, which matches list positions.
Reading the result: Six numbered lines, January to June. Use enumerate instead of range(len(items)) whenever you need both the position and the value; it removes a whole class of indexing mistakes.
Example 2: zip pairs lists and computes per-item results
Question: Compute the average basket value (₹) for each month from the two lists.
for month, n, rev in zip(months, orders, revenue_k):
avg_basket = rev * 1000 / n
print(f"{month}: Rs. {avg_basket:,.0f}")Output
Jan: Rs. 510
Feb: Rs. 505
Mar: Rs. 515
Apr: Rs. 518
May: Rs. 540
Jun: Rs. 541How it works: zip walks the three lists in step, handing us one element from each per pass. Revenue is in thousands, so we multiply by 1000 before dividing by the number of orders.
Reading the result: Average baskets stay near ₹510 in January and ₹540 in June. If lists have different lengths, zip silently stops at the shortest, so check lengths first when data comes from different sources.
Example 3: Running (cumulative) total
Question: Build the year-to-date orders after each month.
running = []
total = 0
for n in orders:
total += n
running.append(total)
print(running)Output
[310, 605, 947, 1335, 1750, 2210]How it works: The accumulator total grows each pass, and we append a snapshot of it to a new list. A snapshot is needed because total keeps changing; the list keeps each intermediate value.
Reading the result: By June the cumulative total is 2210, which equals sum(orders) (2210). Pandas does this in one call with .cumsum(), but the loop shows what that method does.
Example 4: Comparing each value with the previous one
Question: What was the month-on-month growth in orders, in percent?
growth = []
for i in range(1, len(orders)):
change = (orders[i] - orders[i - 1]) / orders[i - 1] * 100
growth.append(round(change, 1))
print(growth)Output
[-4.8, 15.9, 13.5, 7.0, 10.8]How it works: We start the loop at index 1 because the first month has no previous month. For each position we use orders[i] and its neighbour orders[i - 1]. The formula is the same percentage change you met in the numbers lesson.
Reading the result: The growth list has 5 entries for 6 months. February dipped 4.8%, and the strongest month was Mar at 15.9%. The + 1 is needed because growth[0] describes the second month.
Example 5: Minimum and maximum with position
Question: Which month had the highest orders, and which had the lowest?
best_i = worst_i = 0
for i in range(len(orders)):
if orders[i] > orders[best_i]:
best_i = i
if orders[i] < orders[worst_i]:
worst_i = i
print(months[best_i], orders[best_i])
print(months[worst_i], orders[worst_i])
# The built-in shortcut gives the same answer
print(max(orders), min(orders), orders.index(max(orders)))Output
Jun 460
Feb 295
460 295 5How it works: We track the position of the best and worst values, not just the values, so we can look up the month name. Python's max, min and the list method index do the same work in one line each, and you should use them in practice.
Reading the result: The peak was Jun with 460 orders, and the low was Feb with 295. The loop and the built-ins agree.
Example 6: Filtering into a new list
Question: Collect the months where orders exceeded 350, and the revenue for those months.
strong_months = []
strong_revenue = 0
for month, n, rev in zip(months, orders, revenue_k):
if n > 350:
strong_months.append(month)
strong_revenue += rev
print(strong_months, strong_revenue)Output
['Apr', 'May', 'Jun'] 674How it works: This is the filter pattern: an if inside the loop decides whether to keep an item. We built two results at once, a list of names and a running sum of revenue.
Reading the result: 3 months passed the bar (Apr, May, Jun), together bringing ₹674k. In the comprehension lesson you will write this filter in a single line, and in pandas it becomes df[df["orders"] > 350].
Example 7: Transforming every item
Question: Convert revenue from thousands to lakhs (1 lakh = 100 thousand) and format as text.
revenue_lakh = []
for r in revenue_k:
revenue_lakh.append(round(r / 100, 2))
labels = []
for m, r in zip(months, revenue_lakh):
labels.append(f"{m}: {r} L")
print(labels)Output
['Jan: 1.58 L', 'Feb: 1.49 L', 'Mar: 1.76 L', 'Apr: 2.01 L', 'May: 2.24 L', 'Jun: 2.49 L']How it works: The transform pattern creates an empty list, loops, and appends a changed version of each item. Two chained loops produce a list of numbers and then a list of labels. Starting with an empty list and calling append is the single most common loop pattern in Python.
Reading the result: January's ₹158k becomes 1.58 lakh, and June's becomes 2.49 lakh. We kept the original lists unchanged, which is the safe way to transform data.
Example 8: reversed and sorted
Question: Show the months from latest to earliest, and rank months by orders without changing the original lists.
for m in reversed(months):
print(m, end=" ")
print()
ranked = sorted(zip(orders, months), reverse=True)
print(ranked)
print(months)Output
Jun May Apr Mar Feb Jan
[(460, 'Jun'), (415, 'May'), (388, 'Apr'), (342, 'Mar'), (310, 'Jan'), (295, 'Feb')]
['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']How it works: reversed() walks backwards. sorted() returns a new sorted list, leaving the original alone (unlike the list method .sort(), which changes the list in place). Sorting a list of pairs orders by the first element, so we put orders first, then flip the order with reverse=True to get the highest first.
Reading the result: The months print June to January, and the ranking begins with (460, 'Jun'). The original months list is untouched, as the last line shows.
Example 9: Nested loops for a grid
Question: Create a small matrix showing orders if each store delivered 1x, 2x or 3x of its base volume.
base = [120, 80, 45]
factors = [1, 2, 3]
print("Factor " + " ".join(f"S{i + 1:>5}" for i in range(len(base))))
for f in factors:
row = []
for b in base:
row.append(b * f)
print(f"{f:>6} " + " ".join(f"{v:>6}" for v in row))Output
Factor S 1 S 2 S 3
1 120 80 45
2 240 160 90
3 360 240 135How it works: The outer loop picks a factor, the inner loop multiplies each store's base by it, and we print one formatted row per factor. The " ".join(...) with a generator inside builds the aligned text row.
Reading the result: The grid has 3 rows and 3 columns, and every cell is base × factor. This shape (a loop within a loop producing rows and columns) is the manual version of a pandas pivot table.
Example 10: Counting with a dictionary (a preview)
Question: Count how many orders used each payment method in this short list.
payments = ["UPI", "Card", "UPI", "COD", "UPI", "Wallet", "Card", "UPI"]
counts = {}
for p in payments:
if p in counts:
counts[p] += 1
else:
counts[p] = 1
print(counts)Output
{'UPI': 4, 'Card': 2, 'COD': 1, 'Wallet': 1}How it works: A dictionary stores key-value pairs. For each payment method we either increase its counter or create it with a count of 1. The next module covers dictionaries properly, including shortcuts such as collections.Counter and dict.get.
Reading the result: UPI appears 4 times and is the most popular method. Counting categories is so common that pandas gives it a one-liner, value_counts().
Common mistakes
1. Changing a list while looping over it. Building a new list is safer.
2. Mixing up index and value. for i in orders gives values, not positions. Use enumerate or range(len(...)) when you need positions.
3. Starting a neighbour comparison at 0.
data = [10, 20, 30]
print(data[0 - 1])Output
30Index -1 is the last element, so data[i - 1] at i = 0 silently reads the wrong value instead of raising an error. Start at 1.
4. Using zip on lists of unequal length without noticing. The longer list's extras are dropped.
5. Writing a loop where a built-in exists. sum, max, min, any, all, sorted and len are faster and clearer than manual loops.
Practice questions
Practice 1 (Easy): Numbered cities
Print the list ["Chennai", "Pune", "Kolkata"] as 1) Chennai, 2) Pune, 3) Kolkata.
Show solution
for i, city in enumerate(["Chennai", "Pune", "Kolkata"], start=1):
print(f"{i}) {city}")Output
1) Chennai
2) Pune
3) Kolkataenumerate with start=1 supplies the counter, so there is no manual bookkeeping.
Practice 2 (Medium): Revenue per order and best month
Using months, orders and revenue_k, find the month with the highest revenue per order and print it with the value in rupees, rounded.
Show solution
best_month, best_value = "", 0
for m, n, rev in zip(months, orders, revenue_k):
value = rev * 1000 / n
if value > best_value:
best_month, best_value = m, value
print(best_month, round(best_value))Output
Jun 541We keep the best value seen so far together with its month name, replacing both when a better one appears. The highest basket was in Jun at ₹541.
Practice 3 (Hard): Streak detection
Find the longest streak of consecutive months in which orders grew compared with the previous month, and print its length. Use the orders list.
Show solution
longest = current = 0
for i in range(1, len(orders)):
if orders[i] > orders[i - 1]:
current += 1
longest = max(longest, current)
else:
current = 0
print(longest)Output
4current counts the growth streak that is still running and resets to zero whenever a month fails to grow. longest remembers the best streak. The orders grew from March to June, so the longest streak is 4 months.
Key takeaways
enumerategives position plus value, andzipwalks several lists in step; both remove manual index handling.- The core accumulator patterns are total, count, best-so-far (with position), and running total, always initialised before the loop.
- To compare neighbours, start at index 1 and use
iandi - 1. - Filter and transform by appending to a new list; avoid modifying the list you are looping over.
sorted()andreversed()give new orderings without touching the original list.- Prefer built-ins (
sum,max,min,any,all) when they exist, and remember each pattern has a one-line pandas equivalent coming up soon.
Frequently asked questions
Which is faster, a loop or a list comprehension?
A comprehension is usually a little faster and always shorter for building lists. For numeric work on big data, NumPy and pandas are far faster than either, because they run compiled code on whole arrays.
What does enumerate return?
An iterator of pairs (index, item). Wrap it in list() to see them all. It never builds a big list in memory.
Can I use zip with more than two lists?
Yes, as many as you like, as the three-list example showed. In Python 3.10 and newer, zip(a, b, strict=True) raises an error if the lengths differ, which is a useful safety check.
How do I loop over a list and remove items safely?
Create a new list with the items you want to keep (a filter), then assign it back. Alternatively loop over a copy: for x in items[:].
How can I find a pattern when I do not know where to start?
Describe the result in words: "a total", "a count of matches", "the largest and where it is", "each compared with the one before". Each phrase maps to one pattern in the table at the top of this lesson.
Do I need loops at all once I know pandas?
Yes, less often but still regularly: for calling APIs per row, producing one file per store, running simulations, and for the logic inside functions you pass to apply.
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