C++ STL
nested loops
algorithm optimization
C++ programming
code performance

How to rewrite a nested loop using the C STL algorithms?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Rewriting nested loops using C++ STL algorithms replaces manual iteration with declarative, composable functions like std::for_each, std::transform, std::find_if, std::any_of, and std::inner_product. The main benefits are improved readability (intent is expressed by the algorithm name), fewer off-by-one errors, and potential for compiler optimizations. C++20 ranges make this even cleaner by enabling pipeline-style composition without temporary containers.

Example: Nested Loop to Find Matching Pairs

Before (Nested Loop)

cpp
1#include <vector>
2#include <iostream>
3
4std::vector<int> a = {1, 2, 3, 4, 5};
5std::vector<int> b = {3, 6, 4, 8, 2};
6
7// Find elements that appear in both vectors
8std::vector<int> common;
9for (size_t i = 0; i < a.size(); i++) {
10    for (size_t j = 0; j < b.size(); j++) {
11        if (a[i] == b[j]) {
12            common.push_back(a[i]);
13            break;
14        }
15    }
16}
17// common = {2, 3, 4}

After (STL Algorithm)

cpp
1#include <algorithm>
2#include <vector>
3
4std::vector<int> common;
5std::copy_if(a.begin(), a.end(), std::back_inserter(common),
6    [&b](int val) {
7        return std::find(b.begin(), b.end(), val) != b.end();
8    });
9// common = {2, 3, 4}

std::copy_if replaces the outer loop, and std::find replaces the inner loop. The lambda captures b and checks membership.

Example: Matrix Operations

Before (Nested Loop for Row Sums)

cpp
1std::vector<std::vector<int>> matrix = {
2    {1, 2, 3},
3    {4, 5, 6},
4    {7, 8, 9}
5};
6
7std::vector<int> row_sums;
8for (size_t i = 0; i < matrix.size(); i++) {
9    int sum = 0;
10    for (size_t j = 0; j < matrix[i].size(); j++) {
11        sum += matrix[i][j];
12    }
13    row_sums.push_back(sum);
14}

After (STL Transform + Accumulate)

cpp
1#include <algorithm>
2#include <numeric>
3#include <vector>
4
5std::vector<int> row_sums;
6std::transform(matrix.begin(), matrix.end(), std::back_inserter(row_sums),
7    [](const std::vector<int>& row) {
8        return std::accumulate(row.begin(), row.end(), 0);
9    });
10// row_sums = {6, 15, 24}

std::transform applies a function to each row, and std::accumulate sums each row's elements.

Example: Check if Any Row Contains a Value

Before

cpp
1bool found = false;
2for (const auto& row : matrix) {
3    for (int val : row) {
4        if (val == 5) {
5            found = true;
6            break;
7        }
8    }
9    if (found) break;
10}

After (any_of)

cpp
1bool found = std::any_of(matrix.begin(), matrix.end(),
2    [](const std::vector<int>& row) {
3        return std::find(row.begin(), row.end(), 5) != row.end();
4    });
5// found = true

std::any_of short-circuits as soon as a match is found, just like the manual break approach.

Example: Flatten a 2D Vector

Before

cpp
1std::vector<int> flat;
2for (const auto& row : matrix) {
3    for (int val : row) {
4        flat.push_back(val);
5    }
6}

After

cpp
1std::vector<int> flat;
2std::for_each(matrix.begin(), matrix.end(),
3    [&flat](const std::vector<int>& row) {
4        flat.insert(flat.end(), row.begin(), row.end());
5    });
6// flat = {1, 2, 3, 4, 5, 6, 7, 8, 9}

C++20 Ranges

C++20 ranges provide a more readable pipeline syntax:

cpp
1#include <ranges>
2#include <algorithm>
3#include <vector>
4
5std::vector<int> a = {1, 2, 3, 4, 5};
6std::vector<int> b = {3, 6, 4, 8, 2};
7
8// Filter elements in a that are also in b
9auto common = a | std::views::filter([&b](int val) {
10    return std::ranges::find(b, val) != b.end();
11});
12
13for (int v : common)
14    std::cout << v << " ";  // 2 3 4

Flatten with views::join

cpp
1std::vector<std::vector<int>> matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
2
3auto flat = matrix | std::views::join;
4for (int v : flat)
5    std::cout << v << " ";  // 1 2 3 4 5 6 7 8 9

Transform and Filter Pipeline

cpp
1auto result = matrix
2| std::views::transform([](const auto& row) { return std::accumulate(row.begin(), row.end(), 0); }) |
3| --- |
4| Find element in inner collection | `std::find`, `std::find_if` |
5| Check if any inner element matches | `std::any_of`, `std::none_of`, `std::all_of` |
6| Transform each inner collection | `std::transform` + `std::accumulate` |
7| Copy matching elements | `std::copy_if` with inner `std::find` |
8| Flatten nested containers | `std::for_each` + `insert`, or `views::join` |
9| Inner product / dot product | `std::inner_product` |
10| Sort each inner collection | `std::for_each` + `std::sort` |
11
12## Common Pitfalls
13
14* **Worse performance with naive STL replacement**: Replacing `O(n*m)` nested loops with `std::find` inside `std::copy_if` is still `O(n*m)`. For better asymptotic performance, sort one vector and use `std::binary_search` or use `std::unordered_set` for O(1) lookups.
15* **Capturing by reference in lambdas that outlive the scope**: If the lambda is stored or passed to an async function, captured references may dangle. Use `[=]` (capture by value) for lambdas that outlive their creation scope.
16* **Mixing iterators from different containers**: Passing `a.begin()` and `b.end()` to an algorithm is undefined behavior. Always use iterator pairs from the same container.
17* **Forgetting `std::back_inserter` for output**: `std::transform` and `std::copy_if` write to an output iterator. Writing to `result.begin()` without pre-allocating space is undefined behavior. Use `std::back_inserter(result)` to append elements.
18* **Overusing STL algorithms for simple loops**: A simple range-based for loop is often more readable than a `std::for_each` with a lambda. Use STL algorithms when they express intent more clearly (filtering, transforming, searching), not as a blanket replacement for all loops.
19
20## Summary
21
22* Replace inner search loops with `std::find`, `std::find_if`, `std::any_of`, or `std::none_of`
23* Replace transform-and-accumulate patterns with `std::transform` + `std::accumulate`
24* Use `std::copy_if` with a lambda containing the inner check to replace filter-style nested loops
25* C++20 ranges enable pipeline-style composition with `views::filter`, `views::transform`, and `views::join`
26* Use STL algorithms when they improve readability, but prefer simple range-based for loops when the algorithm version is harder to read

Course illustration
Course illustration

All Rights Reserved.