C++
vectors
programming
data structures
merging

Merge vector of vectors into a single vector

Master System Design with Codemia

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

Introduction

Flattening a std::vector<std::vector<T>> into one std::vector<T> is a common C++ task when nested data needs to be processed linearly. The simplest correct solution is usually to reserve the total size up front and append each inner vector with insert.

Basic Flattening with insert

Suppose you have a vector of integer vectors.

cpp
1#include <iostream>
2#include <vector>
3
4int main() {
5    std::vector<std::vector<int>> chunks = {
6        {1, 2, 3},
7        {4, 5},
8        {6, 7, 8}
9    };
10
11    std::vector<int> result;
12
13    for (const auto& chunk : chunks) {
14        result.insert(result.end(), chunk.begin(), chunk.end());
15    }
16
17    for (int value : result) {
18        std::cout << value << ' ';
19    }
20}

That appends each inner vector in order and preserves element order within each chunk.

Reserve Capacity First

If the nested vectors are large, repeated growth of the result vector can cause extra allocations and copies. Compute the total size first and reserve that amount.

cpp
1#include <iostream>
2#include <numeric>
3#include <vector>
4
5int main() {
6    std::vector<std::vector<int>> chunks = {
7        {1, 2, 3},
8        {4, 5},
9        {6, 7, 8}
10    };
11
12    std::size_t total = 0;
13    for (const auto& chunk : chunks) {
14        total += chunk.size();
15    }
16
17    std::vector<int> result;
18    result.reserve(total);
19
20    for (const auto& chunk : chunks) {
21        result.insert(result.end(), chunk.begin(), chunk.end());
22    }
23
24    std::cout << result.size() << '\n';
25}

That small optimization is often the difference between a tidy linear pass and unnecessary reallocation churn.

Move Elements When You No Longer Need the Inner Vectors

If the inner vectors will not be used again and T is expensive to copy, move the elements instead of copying them.

cpp
1#include <iostream>
2#include <iterator>
3#include <vector>
4
5int main() {
6    std::vector<std::vector<std::string>> chunks = {
7        {"alpha", "beta"},
8        {"gamma"}
9    };
10
11    std::vector<std::string> result;
12    result.reserve(3);
13
14    for (auto& chunk : chunks) {
15        result.insert(
16            result.end(),
17            std::make_move_iterator(chunk.begin()),
18            std::make_move_iterator(chunk.end())
19        );
20    }
21
22    for (const auto& value : result) {
23        std::cout << value << '\n';
24    }
25}

After moving, the original inner strings are no longer guaranteed to keep their old values, so use this only when ownership is really being transferred.

Generic Helper Function

A helper function keeps the flattening logic reusable.

cpp
1#include <vector>
2
3template <typename T>
4std::vector<T> flatten(const std::vector<std::vector<T>>& nested) {
5    std::size_t total = 0;
6    for (const auto& inner : nested) {
7        total += inner.size();
8    }
9
10    std::vector<T> out;
11    out.reserve(total);
12
13    for (const auto& inner : nested) {
14        out.insert(out.end(), inner.begin(), inner.end());
15    }
16
17    return out;
18}

That is usually clearer than writing ad hoc loops in multiple places.

Alternatives and Tradeoffs

You could flatten with repeated push_back calls in a nested loop.

cpp
1for (const auto& chunk : chunks) {
2    for (const auto& value : chunk) {
3        result.push_back(value);
4    }
5}

That is perfectly valid and sometimes easier to adapt when filtering or transforming values during the flattening step. If you simply want concatenation, insert communicates intent more directly.

Complexity

The total work is proportional to the number of elements copied or moved. Reserving capacity does not change the big-O time complexity, but it reduces allocation overhead and usually improves real performance.

Common Pitfalls

A common mistake is forgetting to reserve space when flattening many large vectors, which can trigger repeated reallocations. Another is using move iterators and then accidentally reading the old inner values as if they were unchanged. Developers also sometimes flatten by copying vectors themselves instead of their contents, which produces the wrong type entirely. Finally, if ordering matters, be careful not to parallelize or sort chunks unless that behavior is intended.

Summary

  • Use insert to append each inner vector into the result.
  • Reserve the total output size up front when possible.
  • Use move iterators only when the source data will not be reused.
  • A helper function keeps flattening logic consistent across the codebase.
  • The main performance gain usually comes from avoiding unnecessary reallocations.

Course illustration
Course illustration

All Rights Reserved.