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:

sales = [420, 180, 675, 310, 675, 90]
Highest: 675
First over 500 at index: 2
Above average count: 3

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.

# Find the maximum with a tracking variable
highest = sales[0]
for amount in sales:
    if amount > highest:
        highest = amount

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:

amountamount > highest?highest after
420420 > 420 → no420
180no420
675yes675
310no675
675675 > 675 → no675
90no675

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:

lowest = sales[0]
for amount in sales:
    if amount < lowest:   # only the operator changed
        lowest = amount

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.

# Locate the index of the first match
first_big_index = None
for i in range(len(sales)):
    if sales[i] > 500:
        first_big_index = i
        break

Three pieces work together here:

  • first_big_index = None is a sentinel: a placeholder meaning "nothing found yet." None is Python’s special value for the absence of a value.
  • for i in range(len(sales)) walks through positions 0 through 5, and sales[i] reads the value stored at each position.
  • break stops 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:

if first_big_index is None:
    print("No sale over 500")
else:
    print("First over 500 at index:", first_big_index)

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:

sales = [420, 180, 675, 310, 675, 90]

highest = sales[0]
for amount in sales:
    if amount > highest:
        highest = amount

first_big_index = None
for i in range(len(sales)):
    if sales[i] > 500:
        first_big_index = i
        break

above_average = 0
average = sum(sales) / len(sales)
for amount in sales:
    if amount > average:
        above_average += 1

print("Highest:", highest)

if first_big_index is None:
    print("No sale over 500")
else:
    print("First over 500 at index:", first_big_index)

print("Above average count:", above_average)

Each printed line comes straight from one tracking variable:

Highest: 675
First over 500 at index: 2
Above average count: 3
PatternInitialize beforeUpdate insideUse after
Maximumhighest = sales[0]replace when biggerthe extreme value
First matchfirst_big_index = Nonestore index, then breakthe position, or None
Countabove_average = 0+= 1 when condition holdshow 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?

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal