Essential Loop Patterns
Introduction: From Loop Mechanics to Loop Patterns
Welcome to Solving Problems with Loop Patterns in Python! In the previous courses, we focused on the mechanics of repetition: how a for loop walks through a sequence, how a while loop keeps going until its condition becomes false, and how break and continue bend the flow. In this first unit, we shift the focus from mechanics to shapes: small, reusable loop structures that solve entire families of problems.
At the heart of almost every one of these shapes is a tracking variable: a variable we create before the loop, update inside the loop, and read after the loop finishes. It carries the answer forward across iterations so that, when the loop ends, the result is waiting for us.
We will build three patterns in this lesson: finding an extreme value, locating the first match, and counting elements that satisfy a condition. Here is the data we will work with, along with the output we are aiming for:
Pattern 1: Finding the Maximum with a Tracking Variable
Imagine flipping through a stack of receipts, keeping one finger on the biggest amount seen so far. Every time a larger amount appears, we move the finger. When the stack runs out, our finger is on the maximum. That "current best" idea is exactly the tracking variable pattern.
Here, highest starts as the first sale, 420. On each iteration, we compare the current amount against the best value found so far, and we overwrite highest only when the comparison succeeds. Let us trace it:
amount | amount > highest? | highest after |
|---|---|---|
| 420 | 420 > 420 → no | 420 |
| 180 | no | 420 |
| 675 | yes | 675 |
| 310 | no | 675 |
| 675 | 675 > 675 → no | 675 |
| 90 | no | 675 |
Notice the duplicate 675: because the test is strictly greater than, the second copy changes nothing, and highest ends at 675.
Choosing the Right Starting Value (and Flipping to Minimum)
The initializer deserves a moment of thought. A tempting choice is highest = 0, but that quietly breaks on data where every value is negative: a list of temperatures like [-5, -12, -3] would report a maximum of 0, a number that never appeared. Starting from sales[0] avoids this entirely because the starting value is guaranteed to be a real element of the list.
As the trace showed, element 0 is then compared against itself on the first iteration. That comparison is harmless: it can never be true, so it never changes anything, and skipping it would only complicate the code.
The best part is how little it takes to flip the goal. Searching for the smallest value uses the very same skeleton with the comparison reversed:
One caution: this pattern assumes the list has at least one element, since sales[0] would fail on an empty list.
Pattern 2: Locating the Index of the First Match
Sometimes we do not want the value itself; we want to know where it sits. "Which day did sales first cross 500?" is a question about a position, and positions live in indices. As you may recall from the for loops course, range(len(...)) gives us those indices directly.
Three pieces work together here:
first_big_index = Noneis a sentinel: a placeholder meaning "nothing found yet."Noneis Python’s special value for the absence of a value.for i in range(len(sales))walks through positions0through5, andsales[i]reads the value stored at each position.breakstops the loop the instant the answer is known, since a first match cannot be improved upon.
Tracing it: index 0 holds 420, and index 1 holds 180; both are too small. Index 2 holds 675, so first_big_index becomes 2, and break ends the loop immediately; indices 3, 4, and 5 are never examined.
This course uses range(len(sales)) because it builds directly on beginner-friendly tools introduced earlier. In other Python code, you may later see enumerate(), which can provide an element and its index together. Both forms can solve this problem.
Handling the "Not Found" Case
The sentinel must be assigned before the loop, and the reason is subtle: if no element matches, the matching part of the loop body never runs, so a variable assigned only there would not exist afterward. Initializing first guarantees that first_big_index always holds something readable.
None is a useful sentinel because it cannot be confused with any integer index. A search should be followed by a check:
You may also see -1 used as a not-found marker. range(len(sales)) never produces -1, but Python does allow negative indexing: sales[-1] reads the last element. That means a -1 sentinel must always be checked before it is used as an index. Using None helps prevent a failed search from silently selecting the final element.
The classic bug is skipping the sentinel check and printing or using the sentinel as though it were a real answer.
Pattern 3: Counting Elements That Satisfy a Condition
Putting the Three Patterns Together
Each pattern is an independent block that leaves exactly one answer behind in its tracking variable, which makes the patterns easy to stack in a single program:
Each printed line comes straight from one tracking variable:
| Pattern | Initialize before | Update inside | Use after |
|---|---|---|---|
| Maximum | highest = sales[0] | replace when bigger | the extreme value |
| First match | first_big_index = None | store index, then break | the position, or None |
| Count | above_average = 0 | += 1 when condition holds | how many matched |
Conclusion and Next Steps
We covered three loop patterns that all share one skeleton: initialize a tracking variable before the loop, update it conditionally inside, and read it afterward. What distinguishes them are three decisions: what to initialize (a real element, a sentinel, or zero), whether to loop over values or indices, and whether to stop early with break or scan the entire sequence.
These shapes will keep coming back. In the units ahead, we will use them to build new lists, pair up elements with nested loops, and hunt for extremes inside grids of data, where the same "current best" logic works just as well across rows and columns.
Next up is your practice: finding a maximum, flipping it into a minimum, locating a first-match index, and combining an average with a filtered count. Start each task with one question, and the rest of the code tends to fall into place: what is my tracking variable, and what should it start as?
