C++ Interview Questions: Memory, Concurrency, and Modern C++ (2025)

C++ interviews go deeper than most language interviews — interviewers expect you to understand memory management, the object lifecycle, undefined behavior, template mechanics, and concurrency primitives. This guide covers the questions that separate C++ practitioners from true experts.

Memory Management and RAII

What is RAII and why does C++ depend on it?

RAII (Resource Acquisition Is Initialization) ties a resource lifetime to an object lifetime. The constructor acquires the resource; the destructor releases it. Since destructors run deterministically when an object goes out of scope (including during stack unwinding from exceptions), RAII makes resource leaks structurally impossible.

#include <fstream>
#include <stdexcept>

void process(const std::string& path) {
    std::ifstream file(path);  // RAII: file opens in constructor
    if (!file) throw std::runtime_error("cannot open");

    // ... use file ...

}  // RAII: file.close() called automatically — even if exception thrown

Without RAII you need explicit cleanup on every exit path (normal return, every throw, every early return). That is error-prone and does not compose.

When do you use unique_ptr vs shared_ptr vs raw pointers?

Type Ownership Overhead Use When
unique_ptr Exclusive Zero (same as raw) Single owner, transfer via move
shared_ptr Shared (ref-counted) Atomic ref-count, heap control block Multiple owners, unclear lifetime
weak_ptr Non-owning observer Same as shared_ptr minus ownership Break shared_ptr cycles, cache
Raw pointer/ref Non-owning borrow Zero Function parameters that do not own
// Prefer unique_ptr by default
auto widget = std::make_unique<Widget>(args);

// shared_ptr when lifetime is genuinely shared
auto shared = std::make_shared<Widget>(args);
std::weak_ptr<Widget> weak = shared;  // observe without extending lifetime

// Pass by raw pointer/ref — does not convey ownership
void render(const Widget* w);  // borrows, does not own

Move Semantics

What is the difference between copy and move semantics?

A copy duplicates the resource. A move transfers ownership of the resource — the source is left in a valid but unspecified state (typically null/empty). Move semantics eliminate unnecessary copies for temporaries and return values.

#include <vector>
#include <string>
#include <utility>

std::vector<int> make_data() {
    std::vector<int> v(1000000);
    // ... fill v ...
    return v;  // NRVO or implicit move — no copy
}

std::string a = "hello";
std::string b = a;             // copy — a still valid
std::string c = std::move(a);  // move — a is now empty

// Move constructor: O(1), just pointer swap
// Copy constructor: O(n), allocates and copies all elements

What are the Rule of Three and Rule of Five?

Rule of Three (pre-C++11): if you define any of destructor, copy constructor, copy assignment — define all three. They exist when you manage a resource manually.

Rule of Five (C++11+): also define move constructor and move assignment. Or use the Rule of Zero: use RAII types (smart pointers, std containers) so the compiler-generated defaults work correctly and you define none.

class Buffer {
    char* data_;
    size_t size_;
public:
    Buffer(size_t n) : data_(new char[n]), size_(n) {}
    ~Buffer() { delete[] data_; }

    // Copy
    Buffer(const Buffer& o) : data_(new char[o.size_]), size_(o.size_) {
        std::copy(o.data_, o.data_ + o.size_, data_);
    }
    Buffer& operator=(const Buffer& o) {
        if (this != &o) {
            delete[] data_;
            data_ = new char[o.size_];
            size_ = o.size_;
            std::copy(o.data_, o.data_ + o.size_, data_);
        }
        return *this;
    }

    // Move
    Buffer(Buffer&& o) noexcept : data_(o.data_), size_(o.size_) {
        o.data_ = nullptr; o.size_ = 0;
    }
    Buffer& operator=(Buffer&& o) noexcept {
        if (this != &o) { delete[] data_; data_ = o.data_; size_ = o.size_;
                          o.data_ = nullptr; o.size_ = 0; }
        return *this;
    }
};

Templates and Type System

What is template specialization and when do you use it?

// Primary template
template<typename T>
struct Serializer {
    static std::string serialize(const T& v) {
        return std::to_string(v);
    }
};

// Full specialization for std::string
template<>
struct Serializer<std::string> {
    static std::string serialize(const std::string& v) {
        return """ + v + """;
    }
};

// Partial specialization for std::vector<T>
template<typename T>
struct Serializer<std::vector<T>> {
    static std::string serialize(const std::vector<T>& v) {
        std::string r = "[";
        for (auto& el : v) r += Serializer<T>::serialize(el) + ",";
        return r + "]";
    }
};

What is SFINAE and when is it used vs concepts (C++20)?

SFINAE (Substitution Failure Is Not An Error): when template argument substitution fails, the compiler silently discards that overload instead of raising an error. Used for compile-time conditional overloads.

// SFINAE (C++11/14/17 style)
template<typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
void process(T val) { /* integers only */ }

// Concepts (C++20) — cleaner, better error messages
template<std::integral T>
void process(T val) { /* integers only */ }

// Or with requires clause
template<typename T>
requires std::integral<T>
void process(T val) { }

Concurrency

What is a data race and how do you prevent it?

A data race occurs when two threads access the same memory location concurrently, at least one access is a write, and there is no synchronization. Data races are undefined behavior in C++ — the compiler may reorder or optimize away operations in ways that seem impossible.

#include <atomic>
#include <mutex>
#include <thread>

// WRONG: data race
int counter = 0;
// thread 1: counter++;
// thread 2: counter++;  // UB

// FIX 1: atomic (for simple operations)
std::atomic<int> counter{0};
counter.fetch_add(1, std::memory_order_relaxed);  // thread-safe

// FIX 2: mutex (for compound operations)
std::mutex mu;
int value = 0;
{
    std::lock_guard<std::mutex> lock(mu);
    value = value * 2 + 1;  // read-modify-write is atomic under lock
}

What is the difference between std::mutex and std::shared_mutex?

std::mutex: exclusive lock — only one thread can hold it at a time (readers block each other). std::shared_mutex (C++17): supports shared (read) and exclusive (write) locks. Multiple readers can hold it simultaneously; a writer gets exclusive access.

#include <shared_mutex>
#include <unordered_map>

class Cache {
    std::unordered_map<std::string, std::string> data_;
    mutable std::shared_mutex mu_;
public:
    std::string get(const std::string& key) const {
        std::shared_lock lock(mu_);  // shared — multiple readers OK
        auto it = data_.find(key);
        return it != data_.end() ? it->second : "";
    }
    void set(const std::string& key, const std::string& val) {
        std::unique_lock lock(mu_);  // exclusive — blocks all readers
        data_[key] = val;
    }
};

Undefined Behavior

What are common sources of undefined behavior in C++?

  • Null/dangling pointer dereference: accessing memory through an invalid pointer
  • Signed integer overflow: INT_MAX + 1 is UB (not wraparound — use unsigned or check first)
  • Out-of-bounds array access: arr[n] where n >= size
  • Use after free: accessing memory after delete
  • Data races: concurrent unsynchronized access
  • Uninitialized reads: reading a local variable before writing
  • Strict aliasing violations: accessing an object through a pointer of incompatible type
// Signed overflow — UB, compiler may optimize assuming it does not happen
int x = INT_MAX;
if (x + 1 > x) { ... }  // compiler may eliminate this branch entirely

// Fix: use unsigned, or check before
if (x < INT_MAX) { ... x + 1 ... }

Modern C++ Features (C++17 / C++20)

Feature What It Does Example
Structured bindings (C++17) Unpack tuples/structs auto [k,v] = *map.begin();
std::optional (C++17) Nullable value without pointer optional<int> find();
std::variant (C++17) Type-safe union variant<int,string> v = 42;
if constexpr (C++17) Compile-time branch Replaces SFINAE in many cases
Concepts (C++20) Constrain templates template<std::integral T>
Coroutines (C++20) Stackless async co_await async_read();
Ranges (C++20) Composable algorithms views::filter | views::transform
Modules (C++20) Replace header files import std.core;

Common Interview Questions

  • What is the vtable and how does virtual dispatch work? Each class with virtual functions has a vtable (array of function pointers). Each object has a vptr (pointer to its class vtable). Virtual call = dereference vptr, index into vtable, indirect call. Cost: 1 extra pointer dereference + prevents inlining.
  • What is placement new? Constructs an object at a specified memory address without allocating. Used in memory pools, arena allocators, and low-latency systems.
  • What does const mean on a member function? The implicit this pointer becomes const T* — you cannot modify non-mutable members. Enables calling on const objects and const references.
  • What is the difference between std::thread and std::async? std::thread always creates a new OS thread; you join manually. std::async with launch::async runs on a thread pool (implementation-defined) and returns a future. For short tasks, async may be more efficient; for long-running tasks, explicit threads give more control.

Frequently Asked Questions

What is RAII and why is it important in C++?

RAII (Resource Acquisition Is Initialization) ties resource lifetime to object lifetime: the constructor acquires the resource (opens a file, allocates memory, locks a mutex) and the destructor releases it. Because C++ calls destructors deterministically when objects go out of scope — including during stack unwinding from exceptions — RAII makes resource leaks structurally impossible without extra cleanup code. std::unique_ptr, std::shared_ptr, std::lock_guard, and std::fstream are all RAII wrappers. Without RAII, you need explicit cleanup on every exit path, which is error-prone and does not compose across nested function calls.

What is the difference between unique_ptr and shared_ptr?

unique_ptr expresses exclusive ownership — only one unique_ptr can own an object at a time. Transfer of ownership is via std::move; copying is deleted. Zero overhead compared to raw pointers. shared_ptr expresses shared ownership — multiple shared_ptrs can point to the same object; a reference count tracks the owners. When the last shared_ptr is destroyed, the object is deleted. shared_ptr has overhead: heap-allocated control block, atomic reference count operations. Use unique_ptr by default; use shared_ptr only when ownership is genuinely shared. Prefer passing by raw pointer or reference for non-owning function parameters.

What is undefined behavior in C++ and why is it dangerous?

Undefined behavior (UB) means the C++ standard places no requirements on the program. The compiler is free to assume UB never occurs and optimize accordingly — eliminating null checks, assuming signed integers do not overflow, or reordering memory accesses. Common UB: null/dangling pointer dereference, signed integer overflow, out-of-bounds array access, use-after-free, data races, and uninitialized reads. UB is dangerous because it produces no guaranteed error — the program may appear to work in debug builds and fail silently or catastrophically in optimized builds. Use AddressSanitizer (-fsanitize=address), UBSanitizer (-fsanitize=undefined), and ThreadSanitizer (-fsanitize=thread) to detect UB during testing.

{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{
“@type”: “Question”,
“name”: “What is RAII and why is it important in C++?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “RAII (Resource Acquisition Is Initialization) ties resource lifetime to object lifetime: the constructor acquires the resource (opens a file, allocates memory, locks a mutex) and the destructor releases it. Because C++ calls destructors deterministically when objects go out of scope — including during stack unwinding from exceptions — RAII makes resource leaks structurally impossible without extra cleanup code. std::unique_ptr, std::shared_ptr, std::lock_guard, and std::fstream are all RAII wrappers. Without RAII, you need explicit cleanup on every exit path, which is error-prone and does not compose across nested function calls.”
}
},
{
“@type”: “Question”,
“name”: “What is the difference between unique_ptr and shared_ptr?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “unique_ptr expresses exclusive ownership — only one unique_ptr can own an object at a time. Transfer of ownership is via std::move; copying is deleted. Zero overhead compared to raw pointers. shared_ptr expresses shared ownership — multiple shared_ptrs can point to the same object; a reference count tracks the owners. When the last shared_ptr is destroyed, the object is deleted. shared_ptr has overhead: heap-allocated control block, atomic reference count operations. Use unique_ptr by default; use shared_ptr only when ownership is genuinely shared. Prefer passing by raw pointer or reference for non-owning function parameters.”
}
},
{
“@type”: “Question”,
“name”: “What is undefined behavior in C++ and why is it dangerous?”,
“acceptedAnswer”: {
“@type”: “Answer”,
“text”: “Undefined behavior (UB) means the C++ standard places no requirements on the program. The compiler is free to assume UB never occurs and optimize accordingly — eliminating null checks, assuming signed integers do not overflow, or reordering memory accesses. Common UB: null/dangling pointer dereference, signed integer overflow, out-of-bounds array access, use-after-free, data races, and uninitialized reads. UB is dangerous because it produces no guaranteed error — the program may appear to work in debug builds and fail silently or catastrophically in optimized builds. Use AddressSanitizer (-fsanitize=address), UBSanitizer (-fsanitize=undefined), and ThreadSanitizer (-fsanitize=thread) to detect UB during testing.”
}
}
]
}

Companies That Ask This Question

Scroll to Top