JavaRush /Courses /Go SELF /Algorithmic patterns in loops

Algorithmic patterns in loops

Go SELF
Level 4, Lesson 5
Available

1. Introduction

When you first see a task like “compute a sum”, “find a maximum”, or “check whether a number occurs”, it can feel like you have to reinvent the wheel every time. In reality, almost all such tasks are solved with the same blueprint: we create a result variable, initialize it correctly, and update it in a loop. These are algorithmic patterns.

Imagine that a loop is a conveyor belt carrying boxes (input numbers). You stand next to it and do the same action for each box: add to the sum, increment a counter, update the maximum, set a “found” flag, etc. It’s easier for the brain when it recognizes a familiar scheme: “aha, this is an accumulator”, “aha, this is a search with a flag and break”.

To keep the examples connected, we’ll create a mini app that we’ll evolve: numstat — a console number analyzer. It reads numbers and prints simple statistics. Today we’ll add “skills” to it: sum, counting, maximum, search.

2. The “sum” pattern: an accumulator that doesn’t forget

Sum is the kindest pattern because it usually doesn’t require tricky initialization: it’s enough to start from zero and then add carefully. It’s important to understand the role of the sum variable: it stores an intermediate result, and on each loop iteration we update it. Such variables are often called accumulators.

Let’s start with the basic scenario: n is entered, then n integers. You need to output their sum.

package main

import "fmt"

func main() {
	var n int
	fmt.Scan(&n)

	sum := 0
	for i := 0; i < n; i++ {
		var x int
		fmt.Scan(&x)
		sum = sum + x
	}

	fmt.Println(sum) // for example: 15
}

It’s important to notice the “skeleton” of the pattern:

  • sum := 0 — the starting value,
  • inside the loop sum = sum + x — the update rule.

Sum with a condition: sum only what you need

Now a bit more interesting: suppose we want to sum only even numbers. An if appears in the loop, but the pattern remains the same — the sum is just not updated every time.

package main

import "fmt"

func main() {
	var n int
	fmt.Scan(&n)

	sumEven := 0
	for i := 0; i < n; i++ {
		var x int
		fmt.Scan(&x)
		if x%2 == 0 {
			sumEven = sumEven + x
		}
	}

	fmt.Println(sumEven) // for example: 12
}

Notice a good habit: the name sumEven immediately explains what we’re computing. If you name the variable s, it will be like Schrodinger’s cat: it exists, but it’s unclear what’s inside.

Mini diagram of the sum pattern

flowchart TD
    A[sum = 0] --> B{is there another number?}
    B -->|yes| C[read x]
    C --> D[sum = sum + x]
    D --> B
    B -->|no| E[print sum]

3. The “counting” pattern: count by a condition

After sum usually comes counting: “how many numbers are positive?”, “how many times did a letter occur?”, “how many values are divisible by 3?”. It’s almost the same as sum, except we add not x but 1, and most often — only when a condition is met.

There is also an accumulator here; it’s just not a “sum”, but a “count”. And the starting value is usually 0, because before processing the data we haven’t found anything yet.

Let’s count how many of the entered n numbers are strictly greater than zero.

package main

import "fmt"

func main() {
	var n int
	fmt.Scan(&n)

	countPos := 0
	for i := 0; i < n; i++ {
		var x int
		fmt.Scan(&x)
		if x > 0 {
			countPos++
		}
	}

	fmt.Println(countPos) // for example: 3
}

Pay attention to countPos++. It’s just a short form of “add 1”. Inside a Go program, it’s like “plus one confidence”.

Counting in a string: loop form

Since you’ve already seen range over strings, we can do a mini example: count the number of 'a' characters in a string. What matters here is the idea “in a loop, check the element and increment the counter”.

package main

import "fmt"

func main() {
	s := "bananas"

	countA := 0
	for _, ch := range s {
		if ch == 'a' {
			countA++
		}
	}

	fmt.Println(countA) // 3
}

4. The “maximum” pattern: correct initialization

Finding a maximum (or minimum) seems simple: “if the current one is greater than the maximum — update the maximum”. And it is… until you run into negative numbers and initialize the maximum to zero “out of habit”. Then the maximum suddenly becomes 0 even when there isn’t a single zero in the data. Magic? No, just bad initialization.

The main idea: for a maximum, where the initial value comes from matters. There are two reasonable approaches for a beginner. The simplest and most reliable is to read the first element separately and use it as the starting max. But then you must handle the case n <= 0, otherwise you’ll try to read something that isn’t there.

Maximum of n numbers: initialize with the first element

package main

import "fmt"

func main() {
	var n int
	fmt.Scan(&n)

	if n <= 0 {
		fmt.Println(0) // let’s agree: for empty input we print 0
		return
	}

	var max int
	fmt.Scan(&max) // the first element becomes the starting maximum

	for i := 1; i < n; i++ {
		var x int
		fmt.Scan(&x)
		if x > max {
			max = x
		}
	}

	fmt.Println(max) // for example: 42
}

Here you can clearly see the “maximum rule”:

  • start: max = first element,
  • update: if x > max { max = x }.

Maximum and “where it was”: remember the index

Sometimes in problems you need not only the maximum value but also the position (which number it was when it appeared). This is the same pattern; the maximum just gets a “companion” — the index.

package main

import "fmt"

func main() {
	var n int
	fmt.Scan(&n)

	if n <= 0 {
		fmt.Println(-1) // no elements — no index
		return
	}

	var max int
	fmt.Scan(&max)
	maxIndex := 0

	for i := 1; i < n; i++ {
		var x int
		fmt.Scan(&x)
		if x > max {
			max = x
			maxIndex = i
		}
	}

	fmt.Println(maxIndex) // for example: 4
}

The meaning of maxIndex is simple: “if you update the maximum — update the place where it was found too”. Otherwise it’s like a detective story: the culprit is found, but the address got lost.

5. The “search” pattern: a flag and break

Search is when you want to know whether something occurs in the data, or to find the first suitable value. The most common form for beginners is “yes/no”: found or not found. The combination found := false and break works perfectly here, to exit the loop when the answer is already known.

Important: break is not “speed for speed’s sake”, but a way to make the code more honest. If the answer is found, continuing the loop is like reading an instruction manual after you’ve already assembled the cabinet and realized there are extra parts left (and they clearly weren’t yours).

Search for target among n numbers

package main

import "fmt"

func main() {
	var n, target int
	fmt.Scan(&n, &target)

	found := false
	for i := 0; i < n; i++ {
		var x int
		fmt.Scan(&x)
		if x == target {
			found = true
			break
		}
	}

	fmt.Println(found) // true or false
}

The pattern here is almost always like this:

  • found := false before the loop,
  • inside on success: found = true; break,
  • after the loop: print/use found.

Search for the first matching value: an example with a negative

Sometimes instead of a bool you need a value. But it’s still convenient to have a flag to know whether anything was found at all.

package main

import "fmt"

func main() {
	var n int
	fmt.Scan(&n)

	found := false
	firstNeg := 0

	for i := 0; i < n; i++ {
		var x int
		fmt.Scan(&x)
		if x < 0 {
			firstNeg = x
			found = true
			break
		}
	}

	if found {
		fmt.Println(firstNeg) // for example: -7
	} else {
		fmt.Println(0) // let’s agree: if there are no negatives, we print 0
	}
}

6. Several patterns in a single pass

When solving a problem, you often want to do: “first I’ll compute the sum”, then “in a separate loop I’ll find the maximum”, then “in another loop I’ll count positives”. Technically this is possible, but usually it’s extra work: the data can be processed in one pass if you carefully maintain several result variables.

It helps to think of patterns as independent “modules”: sum lives in sum, counting lives in count, maximum lives in max. And they all get updated on each iteration (or almost each one).

Let’s build our numstat: read n, then n numbers. We want to output:

  • the sum of all numbers,
  • the maximum,
  • the number of positives.
package main

import "fmt"

func main() {
	var n int
	fmt.Scan(&n)

	if n <= 0 {
		fmt.Println(0) // sum
		fmt.Println(0) // max
		fmt.Println(0) // countPos
		return
	}

	var x int
	fmt.Scan(&x)

	sum := x
	max := x
	countPos := 0
	if x > 0 {
		countPos = 1
	}

	for i := 1; i < n; i++ {
		fmt.Scan(&x)

		sum = sum + x
		if x > max {
			max = x
		}
		if x > 0 {
			countPos++
		}
	}

	fmt.Println(sum)      // for example: 15
	fmt.Println(max)      // for example: 9
	fmt.Println(countPos) // for example: 3
}

See how three patterns are “embedded” here:

  • sum: sum = sum + x,
  • maximum: if x > max { max = x },
  • counting: if x > 0 { countPos++ }.

And all of that is inside one loop.

Table of variable roles

Variable Role Start Update in loop
sum
sum first element
sum = sum + x
max
maximum first element
if x > max
countPos
counting 0 or 1 (based on the first x)
if x > 0

If you learn to compose such a table “in your head” before writing code, you’ll suddenly discover that loop problems have become much calmer.

Nuance: read numbers until end of input

Sometimes input data can end unexpectedly. In learning tasks, most often “everything is fine”, but the habit of checking the result of fmt.Scan makes the code more mature. In Go, it’s generally customary to treat errors as ordinary values and handle them explicitly.

Here is the minimal form of “read while it reads” — useful when n is not given and numbers go until end of input:

package main

import "fmt"

func main() {
	sum := 0

	for {
		var x int
		_, err := fmt.Scan(&x)
		if err != nil {
			break
		}
		sum = sum + x
	}

	fmt.Println(sum) // sum of all numbers read
}

Here the sum pattern stayed the same; the loop is just controlled not by a counter but by “while input is successful”.

7. Common mistakes

Mistake #1: initializing the maximum to zero “on autopilot”.
This breaks tasks where all numbers can be negative. The program will cheerfully print 0, even though there was no zero in the data. A more reliable habit is to initialize the maximum with the first element, and for empty input to agree in advance what to print (and add if n <= 0 { ... return }).

Mistake #2: mixing up variable roles and starting to “use a counter to compute a sum”.
Sometimes people write sum++ instead of sum = sum + x, or use i both as the loop index and as the result counter. This usually happens when variables are named too briefly. If you feel like you’re starting to get confused, rename s to sum, c to countPos — and your brain will thank you (usually silently).

Mistake #3: forgetting break in a search, and “search” turns into “observation”.
You set the found flag, but the loop keeps reading input and doing extra work. In problems where after finding the element everything is already clear, break is not an optimization, but part of the correct meaning: “the answer is found, we don’t search further”.

Mistake #4: wrong loop bounds when reading n numbers.
A classic: instead of i < n, they write i <= n and accidentally read n + 1 numbers. The bug looks especially tricky because sometimes the input “accidentally” has enough data and you don’t see the problem immediately. A good check is to say out loud: “I want exactly n iterations, so i should take values 0..n-1”.

Mistake #5: not handling “empty input”, but the code still tries to read the first element.
This often happens in the maximum pattern, where we read the first element separately. If you forget if n <= 0, the program will try to read a number that isn’t there, and after that the behavior becomes unpleasantly unpredictable. Better to handle this case honestly once at the beginning than later to figure out why “sometimes it works, sometimes it doesn’t”.

1
Task
Go SELF, level 4, lesson 5
Locked
Shift total
Shift total
1
Task
Go SELF, level 4, lesson 5
Locked
Discount Counter
Discount Counter
1
Task
Go SELF, level 4, lesson 5
Locked
Champion and Position
Champion and Position
1
Task
Go SELF, level 4, lesson 5
Locked
Marker search
Marker search
1
Survey/quiz
For loops, 4 level, 5 lesson
Unavailable
For loops
For loops
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION