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 | first element | |
|
maximum | first element | |
|
counting | 0 or 1 (based on the first x) | |
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”.
GO TO FULL VERSION