1. Introduction
When you are just starting to program, an array looks like a simple box of numbers: put in — take out. But in reality an array quickly becomes "data" that you need to do the same things to all the time: find the minimum, find the maximum, check whether a value exists, reverse the order. These repeating techniques — kind of like cooking recipes — are called algorithmic patterns.
The main idea is simple: a fixed-size array is most often processed by a single linear pass over the indices 0..N-1. During this pass you either "improve the answer" (as in min/max), or "search for a match" (as in search), or "move elements around" (as in reverse).
Let's fix this in a small table (not for an exam, but to keep your brain calm):
| Operation | What we do inside the loop | How many passes | Idea of the result |
|---|---|---|---|
|
compare the current element with the best | 1 | "best value" |
| linear search | check a[i] == target | 1 | "position" or "not found" |
| reverse | swap pairs of elements | |
array modified in-place |
2. Minimum and maximum in one pass
Minimum and maximum are perfect examples of how a loop becomes a "machine for improving the result". At first we don't have an answer, so we need to take some initial value. Beginners often do the move "let's start with 0" — and get surprised if all numbers are negative or if zero never appears. It's more correct and honest to start with the first element of the array.
So the logic is: "for now the minimum is a[0]. Now let's see if something smaller appears." Therefore the loop usually starts from i = 1, because we've already used a[0] as the start.
Example 1: min/max for a C-array — a small "Temperatures" app
Imagine we write a small "Temperatures for the week" program: we input 7 integer temperatures and want to find the minimum and maximum.
#include <cstddef>
#include <iostream>
int main() {
constexpr std::size_t N = 7;
int t[N] = {}; // temperatures
for (std::size_t i = 0; i < N; ++i) std::cin >> t[i];
int mn = t[0], mx = t[0];
for (std::size_t i = 1; i < N; ++i) { if (t[i] < mn) mn = t[i]; if (t[i] > mx) mx = t[i]; }
std::cout << "min=" << mn << " max=" << mx << '\n'; // e.g.: min=-3 max=8
}
Note two points that make the code "robust". We took the start from t[0], so we do not depend on what numbers are in the array. We started the loop with i = 1, so we don't compare t[0] with itself (this is not an error, but extra work and unnecessary mental noise).
A bit about the precondition
This pattern assumes that the array is not empty. In our case this is guaranteed by the fact that N is fixed and greater than zero. If the size could be 0, you'd have to decide separately what to do (but that's another story).
3. Linear search and the "not found" marker
Linear search is when we honestly go from left to right and ask each element: "are you the one?". The computer doesn't mind such questions. This is one of the most useful patterns for a beginner, because it trains discipline: correct bounds, correct index type, and a clear result.
The most important question here is not "how to compare", but "how to return the result". Often we want to return the position (index), but the position is a number from 0 to N-1. So how to denote "not found"?
One convenient way (especially if the index is stored as std::size_t) is to use the value N. This is safe because N cannot be a valid index, since the last index is N-1.
Example 2: find the first occurrence
Add to our app: the user enters a number target, and we search on which day of the week it occurred.
#include <cstddef>
#include <iostream>
int main() {
constexpr std::size_t N = 7;
int t[N] = {};
for (std::size_t i = 0; i < N; ++i) std::cin >> t[i];
int target = 0;
std::cin >> target;
std::size_t pos = N; // "not found" marker
for (std::size_t i = 0; i < N; ++i)
if (t[i] == target)
{ pos = i; break; }
if (pos != N)
std::cout << "found at day " << pos << '\n'; // e.g.: found at day 3
else
std::cout << "not found\n";
}
Here break means "the first match is enough". This is a normal strategy and often the expected one. But it's important to understand: if you remove break, you will get the last match, because pos will be overwritten each time.
One loop skeleton for different tasks
If you look closely, min/max and search are more similar than they seem. In both cases we have a for loop with an if inside. The only difference is what we do inside the if.
For min/max we don't "jump out", we update the answer variable and continue the pass. For search we either update the "answer" (for example, the position) or exit if we chose the "first match" strategy.
To make this stick as a habit rather than magic, you can mentally keep this mini-scheme:
Go i from 0 to N-1
if the element satisfies the condition:
either improve the answer and continue
or fix the answer and stop
This is one of the most useful "skeletons" in programming.
4. In-place array reversal
Reversing an array is an operation where it's easy to make the classic index error: confusing N - i and N - 1 - i. Remember the iron rule: the last index is N - 1.
The idea of reversal is: the first element swaps with the last, the second with the penultimate, and so on. If you do swaps until the end of the array, you'll just reverse it twice. Therefore you must swap only up to the middle: i < N / 2.
Small scheme of pairs when reversing
For an array of 7 elements the indices look like this:
0 1 2 3 4 5 6
| | | | | | |
6 5 4 3 2 1 0
So the paired index for i is N - 1 - i.
Example 3: reverse a C-array using a temporary variable
Add a reversal of temperatures to our app so we can print them "from the end of the week to the start".
#include <cstddef>
#include <iostream>
int main() {
constexpr std::size_t N = 7;
int t[N] = {};
for (std::size_t i = 0; i < N; ++i) std::cin >> t[i];
for (std::size_t i = 0; i < N / 2; ++i) {
int tmp = t[i];
t[i] = t[N - 1 - i];
t[N - 1 - i] = tmp;
}
for (std::size_t i = 0; i < N; ++i) std::cout << t[i] << ' ';
std::cout << '\n'; // e.g.: 8 6 4 1 0 -2 -3
}
Why not use std::swap here? Because it's more important now to see the mechanics by hand: "temporarily put one element into tmp, then do two assignments". It's transparent: each assignment is visible, which makes debugging easier.
5. The same patterns on std::array
With std::array it's nice that it can report its own size: a.size(). This greatly reduces the amount of "magic numbers" and the risk of desynchronization when you change the array size but forget to update a loop.
Psychologically it helps too: you worry less about "did I use 7 everywhere?" and more about the actual task.
Example 4: min/max for std::array
#include <array>
#include <cstddef>
#include <iostream>
int main() {
std::array<int, 7> t{}; // 7 temperatures
for (std::size_t i = 0; i < t.size(); ++i) std::cin >> t[i];
int mn = t[0], mx = t[0];
for (std::size_t i = 1; i < t.size(); ++i)
{ if (t[i] < mn) mn = t[i]; if (t[i] > mx) mx = t[i]; }
std::cout << "min=" << mn << " max=" << mx << '\n';
}
Note: the logic is the same, the "recipe" is the same, only the loop bound changed. That's a good sign: you're not tied to a specific array implementation, you think in terms of "data and a pass".
Example 5: search in std::array with a "not found" marker
#include <array>
#include <cstddef>
#include <iostream>
int main() {
std::array<int, 7> t{};
for (std::size_t i = 0; i < t.size(); ++i) std::cin >> t[i];
int target = 0;
std::cin >> target;
std::size_t pos = t.size();
for (std::size_t i = 0; i < t.size(); ++i)
if (t[i] == target)
{ pos = i; break; }
std::cout << (pos == t.size() ? "not found\n" : "found\n"); // found / not found
}
Here the marker is t.size(). It's exactly the same idea as pos = N, just more self-documenting.
6. Practical scenario: "temperatures for the week"
To make sure this is not just a set of separate tricks, it's useful to imagine writing a small app that performs several operations in sequence. In real life data is rarely processed by a single command; more often it's a chain of steps.
We have almost assembled such a program: input 7 temperatures, find min/max, search for a specific temperature, reverse the array and print. Yes, it sounds like a "mini-Excel on the cheap", but exercises like this build basic array manipulation muscle memory.
Here is a compact version where everything is in one main (and this matters for today: no user-defined functions).
#include <array>
#include <cstddef>
#include <iostream>
int main() {
std::array<int, 7> t{};
for (std::size_t i = 0; i < t.size(); ++i)
std::cin >> t[i];
int mn = t[0], mx = t[0];
for (std::size_t i = 1; i < t.size(); ++i)
{ if (t[i] < mn) mn = t[i]; if (t[i] > mx) mx = t[i]; }
int target = 0; std::cin >> target;
std::size_t pos = t.size();
for (std::size_t i = 0; i < t.size(); ++i)
if (t[i] == target) { pos = i; break; }
for (std::size_t i = 0; i < t.size() / 2; ++i)
{ int tmp = t[i]; t[i] = t[t.size() - 1 - i]; t[t.size() - 1 - i] = tmp; }
std::cout << "min=" << mn << " max=" << mx << '\n';
std::cout << (pos == t.size() ? "not found\n" : "found\n");
}
Yes, the lines are dense. But note the structure: three passes (min/max, search, reverse) and each pass is the same familiar loop over indices. That's the goal of the lecture: to make you see repetition in code instead of chaos.
7. Typical mistakes in min/max, search and reverse
Mistake #1: initializing min/max with “0” or "a very large number".
A beginner often writes int mn = 0; and then wonders why the minimum of an array like {-5, -2} suddenly equals 0. The problem is that you took the initial value from your head, not from the array. It's safer to start with a[0] and begin the loop with i = 1: then the answer is always based on real data.
Mistake #2: the loop for min/max starts at i = 0, but mn is also equal to a[0], causing confusion.
Formally this does not break the result: comparing a[0] with mn when mn == a[0] is harmless. But for a beginner it adds unnecessary mental load: "why are we comparing the element with itself?". Better stick to the clean template mn = a[0]; for (i = 1; ...).
Mistake #3: no clear "not found" in search.
If you look for a position and forget to agree on what to do on failure, you get odd hacks: return 0, return -1 in size_t (which becomes a huge number), or print garbage. The safest technique at this level is pos = N (or pos = a.size()), because that value cannot be a valid index.
Mistake #4: missing break or, conversely, an extra break.
Sometimes you want the first occurrence but forget break and get the last. Sometimes you want the last (or all) occurrences but place a break and get only the first. This is not a compilation error, it's a mistake in problem specification. Before writing the loop, honestly decide: "do I need the first, the last, or all matches?".
Mistake #5: wrong index formula when reversing (N - i instead of N - 1 - i).
Classic mistake: you remember "the end of the array" but forget that indexing starts from zero. As a result you access a[N], which is out of bounds. Fix it with a mantra: "the last index is N - 1".
GO TO FULL VERSION