concurrency
multithreading
thread safety
programming
C++

I've heard i isn't thread safe, is i thread-safe?

Interview Questions practice on Codemia

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

Browse interview questions

Introduction

If i is a shared ordinary integer, neither i++ nor ++i is thread-safe. The difference between postfix and prefix increment is only about the value returned by the expression. It does not make either operation atomic, and it does not prevent a data race when multiple threads update the same variable without synchronization.

Why Both Forms Are Unsafe on a Shared Non-Atomic Variable

For a normal integer, an increment conceptually involves:

  • reading the current value
  • computing the incremented value
  • writing the new value back

That read-modify-write sequence is exactly what races when two threads do it at the same time.

A simple example is:

cpp
1int counter = 0;
2
3void worker() {
4    for (int k = 0; k < 100000; ++k) {
5        ++counter;
6    }
7}

If multiple threads run worker concurrently, the final result is undefined behavior in C++ because the code contains a data race.

The same problem exists with counter++.

Prefix Versus Postfix Only Changes the Return Value

The meaning difference is local to the expression:

  • '++i increments and yields the incremented value'
  • 'i++ increments and yields the old value'

For example:

cpp
int i = 5;
int a = ++i; // i becomes 6, a is 6
int b = i++; // b is 6, then i becomes 7

That distinction matters for expression semantics and sometimes performance in iterator-heavy code, but it does not magically add thread safety.

So the answer to the original question is direct: no, ++i is not a thread-safe replacement for i++ on shared state.

Use std::atomic for Atomic Increments

If the variable is meant to be updated concurrently without a mutex, use std::atomic.

cpp
1#include <atomic>
2#include <thread>
3#include <vector>
4#include <iostream>
5
6std::atomic<int> counter{0};
7
8void worker() {
9    for (int k = 0; k < 100000; ++k) {
10        ++counter;
11    }
12}
13
14int main() {
15    std::vector<std::thread> threads;
16
17    for (int i = 0; i < 4; ++i) {
18        threads.emplace_back(worker);
19    }
20
21    for (auto& t : threads) {
22        t.join();
23    }
24
25    std::cout << counter.load() << '\n';
26}

Here, both ++counter and counter++ are atomic operations because counter is an atomic type.

The prefix/postfix distinction still affects the returned value, but thread safety now comes from the atomic type, not from the operator spelling.

Mutexes Are Still a Good Option

If the increment is part of a larger critical section, a mutex is often the better design.

cpp
1#include <mutex>
2
3int counter = 0;
4std::mutex m;
5
6void worker() {
7    for (int k = 0; k < 100000; ++k) {
8        std::lock_guard<std::mutex> lock(m);
9        ++counter;
10    }
11}

Atomic integers are great for simple counters. Once the update needs to coordinate multiple shared values, a mutex is usually clearer.

volatile Does Not Fix This

A common misconception is that volatile makes shared increments safe. It does not. volatile affects certain optimization behavior, but it does not make a read-modify-write sequence atomic and does not establish the synchronization rules required for safe multithreaded access in normal C++ application code.

Common Pitfalls

  • Assuming prefix increment is thread-safe while postfix increment is not is incorrect. Both are unsafe on shared non-atomic variables.
  • Focusing on expression syntax instead of data-race rules misses the real issue, which is unsynchronized shared access.
  • Using volatile as a concurrency tool does not solve atomicity or synchronization.
  • Switching to std::atomic for a variable that participates in a larger multi-variable invariant may still be the wrong design; a mutex might be needed.
  • Testing on one machine and seeing the "right" final count does not prove the code is correct because data-race behavior is still undefined.

Summary

  • Neither i++ nor ++i is thread-safe for a shared ordinary integer.
  • The only difference between them is the value returned by the expression.
  • Thread safety comes from synchronization, such as std::atomic or a mutex, not from prefix versus postfix syntax.
  • Use std::atomic for simple concurrent counters.
  • Use a mutex when the increment is part of a larger critical section.

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.