20 topics
/
← Back to Quick Reference
Topic 08

Loops

for · while · Range-for · break/continue · Algorithms · Performance

C++17 · Advanced Reference

Loop Types in C++

01

Three loop constructs

C++ has three loop statements. The right choice depends on whether you know the iteration count upfront, need at least one execution, or are iterating a range.

Choosing a loop

  1. 1.for — use when you know the count or need an index. Init, condition, and increment are all in one line.
  2. 2.while — use when the count is unknown and the loop might not execute at all.
  3. 3.do-while — use when the body must run at least once (e.g., input validation, retry logic).
  4. 4.range-for — use whenever iterating all elements of a container. Cleaner and harder to get wrong.

Key rules

  1. 1.Prefer const auto& in range-for to avoid copies and prevent accidental mutation.
  2. 2.Avoid comparing int loop counters to .size() — use std::size_t or range-for.
  3. 3.Prefer standard algorithms (std::find, std::transform) over raw loops — they express intent and optimize better.
  4. 4.Break nested loops with a lambda return or goto found: — not a boolean flag and extra condition check.

C++20 ranges let you compose lazy filters and transforms without writing a loop at all.

for Loop

02
// for (init; condition; increment)
for (int i = 0; i < 10; i++) {
  std::cout << i << " ";
}

// Multiple variables (same type)
for (int i = 0, j = 9; i < j; i++, j--) { }

// Infinite loop
for (;;) {
  if (done) break;
}

// ── Pitfalls ─────────────────────────────────────────────────
// Signed / unsigned mismatch — common warning
std::vector<int> v = {1,2,3};
for (int i = 0; i < v.size(); i++) { }  // ⚠ int vs size_t

// Fix 1: use size_t
for (std::size_t i = 0; i < v.size(); i++) { }

// Fix 2: use ptrdiff_t for reverse iteration
for (std::ptrdiff_t i = v.size()-1; i >= 0; i--) { }

// Fix 3: just use range-for (best)
for (auto& x : v) { }
for (;;)Infinite loop — all three clauses are optional. Use break to exit.
int i, j = ...Multiple variables can be declared in the init clause if they share the same type.
i < v.size()Signed/unsigned comparison warning — i is int, size() returns size_t. Use size_t or range-for.
reverse iterationUse ptrdiff_t (signed) for the counter — size_t wraps to max on i-- when i==0.

while & do-while

03
// while — test first, may not execute at all
int i = 0;
while (i < 5) {
  std::cout << i++;
}

// do-while — executes at least once
int attempts = 0;
do {
  attempts++;
} while (!tryConnect() && attempts < 3);

// ── Reading until EOF ────────────────────────────────────────
int n;
while (std::cin >> n) {      // evaluates to false on EOF or error
  process(n);
}

// ── Polling / event loop ─────────────────────────────────────
while (running) {
  processEvents();
  update();
  render();
}
while (cin >> n)The stream evaluates to false on EOF or parse error — idiomatic way to read all input.
do-whileBody executes before the first condition check. Use for retry logic and menu loops.
while (true)Infinite loop — same as for(;;). Prefer for(;;) in C++ by convention.

Range-Based for (C++11)

04
// Range-based for (C++11) — preferred for container iteration
std::vector<int> v = {1, 2, 3, 4, 5};

for (int x : v)        { }   // copies each element
for (auto x : v)       { }   // auto deduces int — also copies
for (const auto& x : v){ }   // ✅ const ref — no copy, no modify
for (auto& x : v)      { }   // ✅ ref — modifies original

// Works on anything with begin() / end()
std::string s = "hello";
for (char c : s) { std::cout << c; }

std::array<int,3> arr = {10, 20, 30};
for (auto x : arr) { }

// C-style array
int raw[4] = {1,2,3,4};
for (int x : raw) { }     // ✅ works — size known at compile time

// ── With index (C++20 not needed — use a counter)
int idx = 0;
for (auto& x : v) {
  std::cout << idx++ << ": " << x << "\n";
}
for (auto x : v)Copies each element. Fine for small types (int, char), wasteful for large objects.
for (const auto& x : v)Const reference — no copy, read-only. Prefer this for most range-for loops.
for (auto& x : v)Mutable reference — modifies elements in-place.
custom typesAny type with begin() and end() works — including your own types.
Default to const auto&. It works for every type, never copies, and the compiler will tell you if you accidentally try to modify a const element.

break · continue · Nested Loops

05
// break — exit the innermost loop (or switch) immediately
for (int i = 0; i < 100; i++) {
  if (i == 42) break;    // stops here
}

// continue — skip to next iteration
for (int i = 0; i < 10; i++) {
  if (i % 2 == 0) continue;   // skip even numbers
  std::cout << i << " ";      // prints: 1 3 5 7 9
}

// ── Breaking out of nested loops ────────────────────────────
// goto (one of its few legitimate uses)
for (int i = 0; i < n; i++) {
  for (int j = 0; j < m; j++) {
    if (grid[i][j] == target) goto found;
  }
}
found:

// Alternative: wrap in a lambda and return
auto search = [&]() -> std::pair<int,int> {
  for (int i = 0; i < n; i++)
    for (int j = 0; j < m; j++)
      if (grid[i][j] == target) return {i, j};
  return {-1, -1};
};
breakExits the innermost enclosing loop or switch. Does not break outer loops.
continueSkips the rest of the current iteration and jumps to the next condition check / increment.
goto foundOne of the few legitimate uses of goto — breaking out of deeply nested loops cleanly.
lambdaWrap nested loops in a lambda and use return — structured, no goto needed.

Loops → Standard Algorithms

06
#include <algorithm>
#include <numeric>

std::vector<int> v = {3, 1, 4, 1, 5, 9};

// Replace most hand-written loops with standard algorithms

// Count / search
auto it = std::find(v.begin(), v.end(), 4);
int  cnt = std::count(v.begin(), v.end(), 1);      // 2

// Transform (map each element)
std::vector<int> out(v.size());
std::transform(v.begin(), v.end(), out.begin(),
               [](int x){ return x * 2; });

// Accumulate (fold / reduce)
int sum = std::accumulate(v.begin(), v.end(), 0);  // 23

// All / any / none
bool anyNeg = std::any_of(v.begin(), v.end(), [](int x){ return x < 0; });

// for_each (when you need side effects)
std::for_each(v.begin(), v.end(), [](int& x){ x *= 2; });

// C++20 ranges — same without begin()/end()
#include <ranges>
for (int x : v | std::views::filter([](int x){ return x > 3; })) {
  std::cout << x << " ";   // 4 5 9
}
std::findReturns iterator to first match, or end(). O(n).
std::transformApplies a function to each element, storing results in an output range.
std::accumulateFolds a range into a single value. Default operation is addition.
std::any_ofReturns true if predicate is true for at least one element. Short-circuits.
C++20 viewsLazy range adaptors — filter, transform, take, drop. Compose without allocating.

Loop Performance

07
// ── Cache efficiency matters more than loop structure ────────
// Row-major access (correct for C++ arrays)
for (int i = 0; i < N; i++)       // outer: row
  for (int j = 0; j < M; j++)     // inner: column
    sum += matrix[i][j];           // ✅ sequential memory

// Column-major (cache-unfriendly — cache misses every access)
for (int j = 0; j < M; j++)
  for (int i = 0; i < N; i++)
    sum += matrix[i][j];           // ❌ stride-N access

// ── Loop hoisting — move invariant computation out ───────────
// ❌ Recomputes v.size() every iteration (compiler may optimize, but don't rely on it)
for (int i = 0; i < (int)v.size(); i++) { }

// ✅ Hoist the size
const auto n = v.size();
for (std::size_t i = 0; i < n; i++) { }

// ── Early termination beats full traversal ───────────────────
for (auto& x : v) {
  if (x == target) return x;   // stop as soon as found
}

// ── Prefer algorithms — the optimizer knows them ─────────────
// std::find, std::count, std::accumulate can be vectorized by
// the compiler better than equivalent hand-written loops
row-major accessIterate rows in the outer loop, columns in the inner — matches how C++ lays out 2D arrays in memory.
hoist invariantsCompute loop-invariant values before the loop. The compiler often does this, but hoisting manually makes intent clear.
early returnStop as soon as the answer is known. Avoids unnecessary iterations.
use algorithmsstd:: algorithms are known to the auto-vectorizer. Hand-rolled loops may not vectorize as reliably.
Cache locality is the biggest loop win. Access memory sequentially. A loop that touches 1 MB sequentially is orders of magnitude faster than one that jumps around randomly — even with fewer total accesses.