C++17
execution policies
parallel programming
C++ programming
software development

How do I use the new C17 execution policies?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

C++17 added execution policies so standard algorithms can express whether work should run sequentially or may run in parallel. The feature is simple to call, but using it correctly requires understanding which algorithms support policies, what guarantees change, and why not every loop becomes safe just because par exists.

The Basic Idea

Execution policies live in std::execution and are passed as the first argument to supported algorithms.

cpp
1#include <algorithm>
2#include <execution>
3#include <vector>
4
5int main() {
6    std::vector<int> values = {5, 4, 3, 2, 1};
7    std::sort(std::execution::seq, values.begin(), values.end());
8}

The three main C++17 policies are:

  • 'std::execution::seq for normal sequential execution'
  • 'std::execution::par for parallel execution'
  • 'std::execution::par_unseq for parallel execution with possible vectorization'

The policy is a request to the implementation, not a guarantee of a specific threading strategy.

Using seq, par, and par_unseq

The easiest way to experiment is with an algorithm such as std::for_each or std::sort.

cpp
1#include <algorithm>
2#include <execution>
3#include <iostream>
4#include <vector>
5
6int main() {
7    std::vector<int> values(10, 1);
8
9    std::for_each(std::execution::par, values.begin(), values.end(), [](int& x) {
10        x *= 2;
11    });
12
13    for (int x : values) {
14        std::cout << x << ' ';
15    }
16}

This may run iterations on multiple threads, but the lambda must still be safe when several invocations happen at once.

par_unseq is more aggressive. It permits both parallelism and unsequenced execution, which means the callable must be even more conservative about side effects.

Which Algorithms Support Policies

Only algorithms with execution-policy overloads can use this feature. Common examples include for_each, sort, transform, reduce, transform_reduce, and copy.

A typical numeric example is:

cpp
1#include <execution>
2#include <iostream>
3#include <numeric>
4#include <vector>
5
6int main() {
7    std::vector<int> values(1'000'000, 1);
8
9    int total = std::reduce(
10        std::execution::par,
11        values.begin(),
12        values.end(),
13        0
14    );
15
16    std::cout << total << '\n';
17}

This is often a better example than for_each because reductions fit parallel execution naturally.

Rules for Safe Parallel Use

The most important rule is that your callable must not create data races.

This is bad code:

cpp
1#include <execution>
2#include <vector>
3
4int main() {
5    std::vector<int> input = {1, 2, 3, 4, 5};
6    int total = 0;
7
8    std::for_each(std::execution::par, input.begin(), input.end(), [&](int x) {
9        total += x;
10    });
11}

Multiple threads may update total at the same time, which is undefined behavior.

Use a reduction algorithm instead:

cpp
1#include <execution>
2#include <iostream>
3#include <numeric>
4#include <vector>
5
6int main() {
7    std::vector<int> input = {1, 2, 3, 4, 5};
8
9    int total = std::reduce(std::execution::par, input.begin(), input.end(), 0);
10    std::cout << total << '\n';
11}

That expresses the operation in a way the library can parallelize safely.

Performance Expectations

Using par does not automatically make code faster. Parallel execution helps when the per-element work is large enough and independent enough to offset scheduling overhead.

For tiny inputs, sequential execution is often faster.

Real speedup also depends on the standard library implementation and toolchain. Some implementations support these overloads but provide limited or backend-dependent parallelism.

Build and Toolchain Notes

You need a compiler and standard library with execution policy support, plus a C++17 build mode.

A typical compile command looks like:

bash
g++ -std=c++17 main.cpp -O2 -pthread

If #include <execution> compiles but performance does not change, the implementation may be falling back to sequential behavior for that environment.

Common Pitfalls

A common mistake is using std::execution::par with a lambda that writes to shared state such as a counter, log buffer, or shared container.

Another pitfall is assuming algorithms preserve element order the same way once parallel execution is allowed. Some algorithms still guarantee specific results, but the timing and scheduling of individual calls are no longer something you should depend on.

Developers also sometimes use par_unseq with code that performs locking, I/O, or other side effects. That policy is intended for very restricted, vectorization-friendly work.

Finally, benchmark before and after. Execution policies are a tool, not a blanket optimization switch.

Summary

  • Pass an execution policy such as std::execution::par as the first argument to supported algorithms.
  • Use seq for normal behavior, par for parallel work, and par_unseq for the most permissive execution model.
  • Make sure the callable is free of data races and unsafe shared-state updates.
  • Prefer algorithms such as reduce when the operation is naturally parallel.
  • Expect results to depend on input size, algorithm choice, and library implementation support.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.