Free Handbook · Every example compiled & verified

Interview Questions

60 C++ interview questions with model answers, from pointers and RAII to move semantics, templates, concurrency and low-latency design, plus a coding round.

0 / 145 lessons🔥 0 day streak
ShareXLinkedIn

Module 15 · what you'll be able to do

  • Answer the 25 junior C++ questions that decide first-round screens, precisely and with examples
  • Explain move semantics, the Rule of Zero, vtables, templates, concepts and the memory model at the depth a mid-level round expects
  • Reason through senior questions on low latency, build times, ABI, migration, API design and production crashes
  • Run a live coding round with a repeatable script, and hand in a take-home that reviewers approve
01

How to use this module

C++ interviews follow a stable pattern: a screen on the language and memory (pointers, references, const, RAII, virtual functions), a technical round on modern C++ (move semantics, templates, the STL, concurrency) plus a coding problem, and, for experienced roles, a design conversation about performance, large codebases and production. The 60 questions below are grouped the same way: 25 junior, 25 mid-level, 10 senior.

  • Say the answer out loud before opening it. Recognising an answer when you read it is not the same as producing it under pressure.
  • Read the "what they are really testing" line. It tells you what the interviewer will follow up on, which is where most candidates lose points.
  • Back every claim with code you have compiled. The modules these draw on, such as Memory & Smart Pointers, Classes & Objects and Templates & the STL, have examples you can paste into your editor.
Know which C++ they write
Some teams are on C++17, many new projects use C++20, and some embedded and legacy codebases are still C++11 or older, with exceptions or RTTI disabled. Ask early, and when you answer, say which standard a feature needs ("concepts are C++20; before that we used enable_if").
02

Junior: the language and the build (10 questions)

Asked in nearly every first-round C++ screen. Short questions with precise answers; a vague answer here ends the interview early. Background: Module 01, Module 03 and Module 05.

JuniorWhat happens when you build a C++ program?

Three stages. The preprocessor handles #include, #define and #if, producing one translation unit per .cpp file. The compiler turns each translation unit into an object file of machine code, checking syntax and types. The linker combines the object files and libraries into an executable, resolving every call to exactly one definition. That is why a missing definition is a link error ("undefined reference"), not a compile error, and why headers carry declarations while .cpp files carry definitions.

What they are really testing: The three stages, and which errors come from which.

JuniorWhat is the difference between a pointer and a reference?

A reference is an alias for an existing object: it must be initialised, cannot be null in valid code, and cannot be re-seated to refer to something else. A pointer is an object holding an address: it can be null, can be changed to point elsewhere, supports arithmetic, and needs * or -> to reach the object. Use references for parameters that must refer to something (const T& for read-only), pointers when "nothing" is a valid value or when re-pointing is needed, and smart pointers when ownership is involved.

What they are really testing: Null-ability and re-seating, plus when to choose each.

JuniorWhat is the difference between the stack and the heap?

Local variables live on the stack: allocation is a pointer bump, and they are destroyed automatically, in reverse order, when the scope ends. The stack is small (typically 1 to 8 MB), so large or long-lived data goes on the heap (free store), allocated with new or by containers and smart pointers, and living until explicitly freed. Heap allocation is slower and needs an owner to free it. Modern C++ keeps raw new/delete out of application code: a std::vector on the stack manages its heap buffer for you.

What they are really testing: Lifetimes, and not managing heap memory by hand.

JuniorWhat does const mean in its different positions?

const int x: the value cannot change. const int* p: pointer to const, the pointed-to value cannot change through p, but p can point elsewhere. int* const p: const pointer, p always points at the same place, but the value can change. void f(const std::string& s): the function will not modify the argument, and no copy is made. int size() const on a member function: it does not modify the object, so it can be called on const objects. Read declarations right to left: "p is a const pointer to int".

What they are really testing: Pointer-to-const versus const pointer, and const member functions.

JuniorWhat is the difference between struct and class?

Only the defaults: members and base classes of a struct are public by default, those of a class are private. Otherwise they are identical: both can have constructors, member functions, inheritance and virtual functions. By convention, struct is used for simple aggregates of public data and class for types that maintain an invariant behind a private interface.

What they are really testing: That the difference is only default access.

JuniorWhat is function overloading, and how does the compiler choose an overload?

Several functions can share a name if their parameter lists differ (return type alone is not enough). The compiler picks the best match for the argument types: an exact match beats a promotion (char to int), which beats a conversion (int to double), which beats a user-defined conversion. If two candidates are equally good the call is ambiguous and does not compile. Default arguments and overloads can overlap and cause ambiguity, so use them carefully.

What they are really testing: The ranking of conversions and the ambiguity error.

JuniorWhat are the ways to pass arguments to a function, and when do you use each?

By value (T x): the function gets its own copy; right for small types (int, double, iterators) and for parameters the function will keep anyway (move them in). By const reference (const T& x): no copy, read-only; the default for strings, vectors and other large types. By reference (T& x): the function modifies the caller's object; an output parameter. By pointer (T* x): like a reference but can be null. By rvalue reference (T&& x): accept a temporary to move from it.

What they are really testing: Practical rules, not just the syntax.

JuniorWhat is a header file, and what are include guards?

A header (.h/.hpp) holds declarations (function signatures, class definitions, templates) that several .cpp files need; #include pastes it in. Including the same header twice in one translation unit would define its classes twice, so every header has an include guard: #pragma once (supported everywhere in practice) or the classic #ifndef FOO_H / #define FOO_H / #endif. Function definitions in headers must be inline (templates and in-class member definitions are implicitly), or the linker sees duplicates.

What they are really testing: Declarations versus definitions, and the one-definition rule.

JuniorWhat is auto, and when should you avoid it?

auto deduces a variable's type from its initialiser. It removes noise for long types (auto it = map.find(k)), is required for lambdas, and keeps code correct when a return type changes. Pitfalls: auto drops references and top-level const (auto x = vec[0] copies; write auto& or const auto&), auto x = {1} is an initializer_list, and proxy types like std::vector<bool>::reference can surprise. Avoid it where the type is important to the reader and not obvious.

What they are really testing: The copy-versus-reference pitfall.

JuniorWhat is nullptr, and why not use NULL or 0?

nullptr (C++11) is a null pointer literal of its own type, std::nullptr_t, which converts to any pointer type but not to integers. NULL is usually just 0, an integer, so f(NULL) with overloads f(int) and f(char*) calls the int version. nullptr always picks the pointer overload and makes intent clear.

What they are really testing: The overload-resolution reason.

03

Junior: objects, memory and ownership (8 questions)

The questions that separate C++ from every garbage-collected language: who owns an object, and when it dies. Background: Module 06, Module 07 and Module 08.

JuniorWhat are constructors and destructors, and when do they run?

A constructor initialises an object when it is created; a destructor cleans up when its lifetime ends: at the end of the scope for a local, when delete is called for a heap object, or when its owner (a container, a smart pointer) is destroyed. Members are constructed in declaration order (initialise them in the member initialiser list, in that order) and destroyed in reverse. Destructors run even when an exception unwinds the stack, which is what makes RAII work.

What they are really testing: Deterministic destruction and member initialisation order.

JuniorWhat is RAII?

Resource Acquisition Is Initialisation: tie every resource (memory, a file, a lock, a socket, a database transaction) to an object whose constructor acquires it and whose destructor releases it. Because destructors run automatically at scope exit, including during exception unwinding, the resource can never leak. std::vector, std::string, std::unique_ptr, std::lock_guard and std::fstream are all RAII types. It is the central idea of C++ resource management and the reason C++ has no finally.

What they are really testing: Whether they know RAII is the core of C++, with examples.

JuniorWhat are unique_ptr and shared_ptr?

std::unique_ptr<T> is the sole owner of a heap object: it cannot be copied, only moved, and deletes the object when it is destroyed. It has zero overhead over a raw pointer and is the default choice. std::shared_ptr<T> allows shared ownership through a reference count: the object is deleted when the last shared_ptr goes away. It costs an extra control block and atomic count updates. Create them with std::make_unique and std::make_shared. std::weak_ptr observes a shared object without keeping it alive, which breaks reference cycles.

What they are really testing: unique_ptr as the default, and weak_ptr for cycles.

JuniorWhat is a virtual function?

A member function declared virtual in a base class is dispatched at run time on the dynamic type of the object: calling it through a Base& or Base* runs the derived class's override. Non-virtual functions are chosen at compile time from the static type. Mark overrides with override so the compiler checks the signature matches. A pure virtual function (= 0) has no implementation in the base, making the class abstract.

What they are really testing: Static versus dynamic dispatch, and override.

JuniorWhy must a polymorphic base class have a virtual destructor?

If a derived object is deleted through a base pointer (std::unique_ptr<Base> p = std::make_unique<Derived>()) and the base destructor is not virtual, only the base destructor runs: the behaviour is undefined, and in practice the derived part's resources leak. A virtual ~Base() = default; makes deletion dispatch to the derived destructor first. Rule: a class meant to be used through base pointers gets a public virtual destructor; a class not meant for that should be final or have a protected non-virtual destructor.

What they are really testing: The undefined behaviour and the rule.

JuniorWhat is object slicing?

Copying a derived object into a base-class value keeps only the base part: Base b = derived; or a std::vector<Base> of derived objects. The derived members are cut off and virtual calls on the copy run the base version. Avoid it by handling polymorphic objects through references or pointers (std::vector<std::unique_ptr<Base>>), and consider deleting the base copy operations.

What they are really testing: Recognising slicing in containers.

JuniorWhat is the difference between new/delete and malloc/free?

new allocates memory and runs the constructor, returns a typed pointer, and throws std::bad_alloc on failure; delete runs the destructor and frees. malloc only allocates raw bytes (returning void* and null on failure) and free only releases them: no constructors or destructors. Never mix them (free on newed memory is undefined), use delete[] for new[], and in modern code prefer containers and smart pointers to either.

What they are really testing: Construction and destruction, and never mixing the pairs.

JuniorWhat are the most common STL containers, and how do you choose?

std::vector: contiguous, fast iteration and indexing, append at the end; the default. std::array: fixed size known at compile time. std::deque: fast insert at both ends. std::list: stable iterators and O(1) splice, but slow to traverse. std::unordered_map/unordered_set: hash tables, O(1) average lookup. std::map/set: sorted, O(log n), ordered iteration and range queries. Adapters: std::stack, std::queue, std::priority_queue. Choose by the operations you need most; when unsure, vector.

What they are really testing: Cost-based choice, and vector as the default.

04

Junior: the STL, errors and undefined behaviour (7 questions)

Daily-work questions: using the standard library correctly and knowing what is unsafe. Background: Module 09, Module 10 and Module 11.

JuniorWhat is iterator invalidation?

Some operations make existing iterators, pointers and references into a container unusable. For std::vector: push_back that exceeds capacity reallocates and invalidates everything; insert and erase invalidate from the change point onward. For std::list and std::map, only iterators to erased elements are invalidated. Using an invalidated iterator is undefined behaviour, often a silent wrong value. The fix: re-obtain iterators after modifying, use the iterator returned by erase, or reserve() in advance.

What they are really testing: Knowing the vector rules and the erase-return idiom.

JuniorHow do you handle errors in C++?

For errors the caller cannot reasonably prevent (file not found, network failure, invalid input deep in a call chain), throw an exception derived from std::exception and catch it by const& where it can be handled. For expected, local outcomes, return a value that encodes them: std::optional, or std::expected (C++23). Programming errors are checked with assert. Some codebases (games, embedded) disable exceptions and use error codes throughout. Whatever the style, RAII guarantees cleanup.

What they are really testing: A reasoned policy, not "wrap everything in try".

JuniorWhat is undefined behaviour? Give examples.

Code for which the C++ standard imposes no requirements: the program may crash, produce garbage, appear to work, or behave differently with another compiler or optimisation level, because the optimiser is allowed to assume it never happens. Examples: dereferencing a null or dangling pointer, reading out of bounds, signed integer overflow, reading an uninitialised variable, data races, using an object after its lifetime ends. Detect it with warnings, sanitizers (-fsanitize=address,undefined) and code review.

What they are really testing: Examples, and that "it works on my machine" proves nothing.

JuniorWhat is the difference between vector::operator[] and vector::at?

v[i] does no bounds checking: an out-of-range index is undefined behaviour. v.at(i) checks and throws std::out_of_range. [] is used in hot loops where the index is known to be valid; at where the index comes from outside. Debug builds of some standard libraries (and hardened modes) add checks to [] too.

What they are really testing: Checked versus unchecked access.

JuniorWhat is a lambda expression?

An anonymous function object written inline: [captures](params) { body }. The capture list decides which outer variables it can use: by value [x], by reference [&x], all used variables [=] or [&], or new members [n = 0]. Lambdas are how you pass custom logic to algorithms (std::sort comparators, std::find_if predicates) and callbacks. A lambda that captures by reference must not outlive the variables it refers to.

What they are really testing: Capture semantics and the dangling-capture risk.

JuniorWhat is std::string_view, and when is it dangerous?

A non-owning view of a sequence of characters: a pointer and a length. It lets a function accept any string-like argument (std::string, a literal, a substring) without copying. It is dangerous because it does not own the data: returning a string_view into a local std::string, or storing one that refers to a temporary, leaves it dangling. Use it for read-only parameters, not for members or return values unless the lifetime is guaranteed.

What they are really testing: Ownership and lifetime.

JuniorWhat does static mean in its different contexts?

Inside a function: the local variable is initialised once and keeps its value between calls (thread-safe initialisation since C++11). On a class member: it belongs to the class, not to each object; a static member function has no this. At namespace scope: internal linkage, meaning the name is private to the translation unit (an anonymous namespace does the same, and is preferred in modern code).

What they are really testing: All three meanings.

05

Mid-level: the object model and move semantics (8 questions)

For roles with two to five years of experience: how objects are copied, moved and dispatched, and how to design classes that are correct by default.

Mid-levelExplain the Rule of Zero, Three and Five.

If a class manages a resource directly, it usually needs a custom destructor, and then also a copy constructor and copy assignment (Rule of Three), and since C++11 a move constructor and move assignment too (Rule of Five), because the compiler-generated versions would copy the raw handle and cause double frees. The Rule of Zero is the goal: do not manage resources in ordinary classes at all; hold them in members that already manage themselves (std::vector, std::string, std::unique_ptr) and write none of the five.

What they are really testing: The Rule of Zero as the preferred design.

Mid-levelWhat is move semantics, and what does std::move actually do?

Moving transfers resources from an object that is about to be discarded instead of copying them: moving a std::vector steals its buffer pointer in O(1). Rvalue references (T&&) bind to temporaries and to objects cast with std::move, selecting the move constructor or move assignment. std::move itself moves nothing: it is a cast to an rvalue reference that permits a move. The moved-from object is left in a valid but unspecified state (typically empty) and may only be assigned to or destroyed.

What they are really testing: That std::move is only a cast, and moved-from state.

Mid-levelWhat are copy elision and RVO?

When a function returns a local object by value, the compiler constructs it directly in the caller's storage instead of copying or moving it. Since C++17 this is guaranteed for returning a prvalue (return Widget{...};), and named return value optimisation (NRVO, return result;) is applied in practice by all major compilers. So returning a large std::vector by value is cheap. Writing return std::move(local); is a pessimisation: it prevents NRVO.

What they are really testing: Returning by value is cheap, and not to std::move a return.

Mid-levelHow does a virtual function call work under the hood?

Each class with virtual functions has a vtable, a static table of function pointers, one per virtual function. Each object of such a class holds a hidden vptr pointing at its class's vtable, set by the constructor. A virtual call loads the vptr, indexes the table and calls through the pointer. The costs: one pointer per object, an indirect call that is hard to inline, and possible cache misses. Inside constructors and destructors, virtual calls dispatch to the class currently being constructed, not the most derived one.

What they are really testing: vtable/vptr, the costs, and the constructor caveat.

Mid-levelWhat is the difference between static_cast, dynamic_cast, const_cast and reinterpret_cast?

static_cast: compile-time conversions between related types (numeric conversions, up and down a class hierarchy without a check). dynamic_cast: a checked downcast in a polymorphic hierarchy, returning null (pointers) or throwing std::bad_cast (references) if the object is not of that type; it needs RTTI and costs a runtime lookup. const_cast: adds or removes const; modifying an object that was really declared const is still undefined. reinterpret_cast: reinterprets bits, for low-level code only. C-style casts try all of these silently, which is why they are avoided.

What they are really testing: Which one is checked, and why C-style casts are discouraged.

Mid-levelWhat is explicit for?

A single-argument constructor is also an implicit conversion: with Meters(double), the call walk(5.0) silently builds a Meters. Marking it explicit requires walk(Meters{5.0}), preventing accidental conversions and overload surprises. The same applies to conversion operators (explicit operator bool()). Good default: make single-argument constructors explicit unless implicit conversion is the point (like std::string from a literal).

What they are really testing: Implicit conversions as a source of bugs.

Mid-levelWhat is the pimpl idiom?

"Pointer to implementation": a class holds only a std::unique_ptr<Impl> to a privately defined implementation struct, declared in the header and defined in the .cpp. Changing the private members no longer changes the header, so dependent files do not recompile, and ABI stays stable for shared libraries. Costs: a heap allocation and an extra indirection per call. The destructor must be defined in the .cpp, where Impl is complete.

What they are really testing: Compile-time and ABI firewalls, and the destructor detail.

Mid-levelWhat does noexcept do, and why does it matter for move constructors?

noexcept promises a function will not throw; if it does, std::terminate is called. It enables optimisations, and crucially, std::vector only moves elements during reallocation if their move constructor is noexcept; otherwise it copies them to keep the strong exception guarantee. So a type with a throwing (or not marked) move constructor makes vector growth slow. Mark move operations, swap and destructors noexcept (destructors are implicitly).

What they are really testing: The vector reallocation consequence.

06

Mid-level: templates, the STL and compile-time C++ (9 questions)

Generic programming is where modern C++ spends much of its power, and interviewers probe whether you understand what the compiler does with your templates. Background: Module 09 and Module 10.

Mid-levelHow do templates work, and what are the trade-offs?

A template is a pattern the compiler instantiates for each set of template arguments at compile time, generating fully typed code with no runtime dispatch: that is why std::sort with a lambda can outperform C's qsort. Trade-offs: definitions must be visible (usually in headers), each instantiation adds compile time and binary size, and errors used to be notoriously long (concepts help). Techniques: specialisation, variadic templates, if constexpr, and SFINAE or concepts to constrain.

What they are really testing: Compile-time instantiation and its costs.

Mid-levelWhat are concepts and how do they improve on SFINAE?

Concepts (C++20) are named compile-time predicates on types: template <std::integral T> or a custom concept HasArea = requires(T t) { t.area(); };. They constrain templates declaratively, select overloads by constraint, and give short errors ("constraints not satisfied") at the call site. SFINAE achieved similar selection with std::enable_if tricks that were hard to read and gave cryptic errors.

What they are really testing: Readability and diagnostics.

Mid-levelWhat is the difference between std::map and std::unordered_map internally?

std::map is a balanced binary search tree (a red-black tree in practice): O(log n) operations, keys kept sorted, requires operator<, iterators stay valid on insertion. std::unordered_map is a hash table with buckets of nodes: O(1) average, O(n) worst case, requires std::hash and ==, no ordering, and rehashing invalidates iterators. Both allocate a node per element; for performance-critical code, flat hash maps (Abseil, Boost) store elements contiguously and are much faster.

What they are really testing: Data structures and complexity, plus knowledge of flat maps.

Mid-levelWhat does emplace_back do differently from push_back?

push_back(x) takes an existing object and copies or moves it into the vector. emplace_back(args...) constructs the element in place from constructor arguments, avoiding a temporary: v.emplace_back("Asha", 30) for a Person. For an object you already have, they are equivalent. Caveat: emplace_back can call explicit constructors, so std::vector<std::unique_ptr<T>> v; v.emplace_back(new T) compiles, and leaks if the vector's reallocation throws.

What they are really testing: In-place construction, and the explicit-constructor caveat.

Mid-levelWhat are ranges and views in C++20?

The ranges library lets algorithms take whole ranges (std::ranges::sort(v)), adds projections (std::ranges::sort(people, {}, &Person::age)), and introduces views: lazy, non-owning adaptors composed with | (v | views::filter(pred) | views::transform(f)). Views compute elements on demand with no intermediate containers, and can be infinite (views::iota(1)). Pitfalls: a view must not outlive its underlying container, and some views cache state, so iterating a const view may not compile.

What they are really testing: Laziness, projections and lifetime pitfalls.

Mid-levelWhat is perfect forwarding?

Passing arguments through a wrapper function while preserving whether each was an lvalue or an rvalue, so the inner call copies or moves exactly as a direct call would. It uses a forwarding reference (template <typename T> void wrap(T&& arg), where T is deduced) and std::forward<T>(arg). std::make_unique, emplace_back and std::thread's constructor all rely on it. T&& is only a forwarding reference when T is deduced in that very call.

What they are really testing: Forwarding references versus rvalue references.

Mid-levelWhat is constexpr, and how is it different from const?

const means "not modified after initialisation"; the value may be computed at run time. constexpr on a variable means it is computed at compile time and usable in constant expressions (array sizes, template arguments). A constexpr function can run at compile time when given constants, and at run time otherwise; consteval functions must run at compile time. Since C++20, constexpr allows loops, std::vector and std::string inside the function, so whole lookup tables can be built by the compiler.

What they are really testing: Compile-time versus immutability.

Mid-levelHow would you choose between std::variant and virtual functions?

Virtual functions suit an open set of types: new derived classes can be added (even in plugins) without touching existing code, at the cost of heap allocation per object and indirect calls. std::variant suits a closed set known up front: values are stored inline with no allocation, std::visit dispatch can be inlined, and the compiler checks that every alternative is handled, but adding a type means editing the variant and every visitor. Adding operations is easy with variant and hard with inheritance, and vice versa.

What they are really testing: The open versus closed trade-off.

Mid-levelHow do you make a type usable as a key in std::unordered_map?

Provide equality (operator==, which can be defaulted in C++20) and a hash: specialise std::hash<Key> or pass a hasher type as the map's third template argument. Combine the members' hashes carefully (for example with a hash_combine function), and keep the hash consistent with equality: equal keys must hash equally. For std::map, provide operator< or a defaulted operator<=> instead.

What they are really testing: Hash and equality consistency.

07

Mid-level: concurrency and performance (8 questions)

C++ is chosen for performance, so interviewers expect a working model of threads, the memory model and the hardware. Background: Module 13 and Module 12.

Mid-levelWhat is a data race, and how do you prevent one?

Two threads access the same memory at the same time, at least one writes, and nothing orders the accesses. In C++ that is undefined behaviour, not just a wrong value. Prevent it by protecting shared data with a std::mutex (locked through std::lock_guard or std::scoped_lock), by using std::atomic for single variables, or by not sharing: give each thread its own data and combine results afterwards. ThreadSanitizer (-fsanitize=thread) detects races in tests.

What they are really testing: That a race is UB, RAII locking, and TSan.

Mid-levelHow do you avoid deadlocks?

A deadlock occurs when threads each hold a lock the other needs. Avoid it by always acquiring multiple mutexes in one global order, or by locking them together with std::scoped_lock(m1, m2), which uses a deadlock-avoidance algorithm. Hold locks for as short a time as possible, never call unknown code (callbacks, virtual functions) while holding a lock, and prefer designs such as message queues where only one thread owns each piece of data.

What they are really testing: Lock ordering, scoped_lock and design-level avoidance.

Mid-levelWhat does std::atomic give you, and what are memory orders?

std::atomic<T> makes single operations on a value indivisible (fetch_add, compare_exchange) and visible across threads, without a mutex. By default operations are sequentially consistent, the easiest to reason about. Weaker orders (memory_order_acquire/release for publishing data from one thread to another, relaxed for plain counters) allow more optimisation but are easy to get wrong. Atomics protect one variable; invariants across several variables still need a mutex.

What they are really testing: Atomicity versus ordering, and not overusing relaxed orders.

Mid-levelWhy can a std::vector be much faster than a std::list even for insertions?

Modern CPUs are dominated by memory access costs. A vector's elements are contiguous, so iteration streams through cache lines and the hardware prefetcher predicts the next access. A list scatters nodes across the heap: every step is a pointer chase and a likely cache miss, plus an allocation per element. So for typical sizes, a vector with O(n) insertion in the middle often beats a list's O(1) insertion, because finding the insertion point in the list is O(n) cache misses anyway. Measure before choosing a node-based container.

What they are really testing: Cache awareness over textbook Big-O.

Mid-levelHow do you profile and optimise a slow C++ program?

Measure first, on an optimised build with debug symbols (-O2 -g) and realistic input. Use a sampling profiler (perf, Instruments, VTune, the Visual Studio profiler) to find the hot functions, then fix the biggest cost: a better algorithm or data structure first, then fewer allocations (reserve, reuse buffers, avoid copies), better memory layout, and only then micro-optimisations. Use Google Benchmark for micro-benchmarks, and verify each change with numbers.

What they are really testing: A measure-first process with tools.

Mid-levelWhat are the costs of exceptions in C++?

Modern implementations use "zero-cost" table-based exception handling: no runtime cost on the path where nothing is thrown, but throwing is expensive (unwinding the stack, looking up handlers), and the tables increase binary size. So exceptions suit genuinely exceptional failures, not control flow in hot loops. Some domains (games, embedded, low-latency trading) disable exceptions with -fno-exceptions and use error codes or std::expected.

What they are really testing: Happy-path versus throw cost, and why some codebases ban them.

Mid-levelWhat is the one-definition rule, and how does it get violated?

The ODR says each non-inline function and variable must have exactly one definition in the whole program, and inline functions, classes and templates defined in several translation units must be token-for-token identical. Violations: defining a non-inline function in a header included by two .cpp files (a "duplicate symbol" link error), or two different classes with the same name in different files (no error, silent undefined behaviour). Mark header function definitions inline, and put file-local helpers in an anonymous namespace.

What they are really testing: Both the loud and the silent violations.

Mid-levelHow do std::thread, std::async and std::jthread differ?

std::thread starts a thread immediately; you must join() or detach() it before its destructor runs, or the program terminates. std::jthread (C++20) joins automatically in its destructor and supports cooperative cancellation through a std::stop_token, so it is the better default. std::async runs a function (possibly on a new thread) and returns a std::future for its result and exceptions; the future it returns blocks in its destructor until the task finishes.

What they are really testing: jthread as the RAII default, and the async future pitfall.

08

Senior: performance, large codebases and production (10 questions)

Senior C++ interviews assume experience with large, long-lived, performance-sensitive systems. There is no single right answer; the model answers show the shape of a strong one: what you would ask first, what you would measure, and which trade-off you would accept.

SeniorHow would you design a low-latency component in C++?

Start with the latency budget and measure tail latency (p99, p99.9), not averages. Keep the hot path free of heap allocation (preallocate, object pools, arena allocators), locks (single-writer designs, lock-free SPSC queues) and system calls. Lay data out for the cache: contiguous arrays, hot and cold fields separated, no false sharing between cores (alignas(std::hardware_destructive_interference_size)). Pin threads to cores, avoid virtual dispatch in tight loops, and keep logging off the hot path. Verify every change with benchmarks and production percentiles.

What they are really testing: Allocation, locking, cache layout and measurement discipline.

SeniorHow do you keep a large C++ codebase building fast?

Measure the build (Clang's -ftime-trace, ClangBuildAnalyzer) to find expensive headers and templates. Reduce include fan-out: forward declarations, the pimpl idiom, include-what-you-use, and splitting "god headers". Use precompiled headers for stable third-party headers, extern template for common instantiations, and C++20 modules where the toolchain supports them. Structure the project into libraries with clear dependencies so incremental builds touch less, and use a compiler cache (ccache, sccache) plus remote builds in CI.

What they are really testing: Measurement and the standard toolbox for build times.

SeniorWhat is ABI compatibility, and why does it matter for C++ libraries?

The ABI is the binary contract: name mangling, calling conventions, class layout, vtable layout, exception handling. Two binaries interoperate only if they agree. Adding a data member, a virtual function, or changing an inline function or template breaks ABI for a shared library, forcing every consumer to recompile. Library authors preserve it with the pimpl idiom, stable C interfaces (extern "C") at boundaries, versioned inline namespaces, and not exposing standard library types across DLL boundaries when compilers may differ.

What they are really testing: What breaks ABI and the techniques that preserve it.

SeniorHow would you migrate a legacy C++98 codebase to modern C++?

First a safety net: build it with a current compiler and warnings on, get it under CI with tests (characterisation tests if there are none), and run sanitizers. Then modernise incrementally with tools: clang-tidy modernize-* checks with --fix (nullptr, override, auto, range-for, make_unique), one check at a time, in reviewable commits. Replace owning raw pointers with unique_ptr, manual arrays with vector/array, and hand-written resource classes with RAII types, starting in the code that changes most. Raise the language standard step by step, and leave stable old code alone.

What they are really testing: Incremental, tool-driven migration with a safety net.

SeniorHow do you design an API in C++ that is hard to misuse?

Make invalid states unrepresentable: strong types instead of raw int/double (Meters, UserId), enum class instead of booleans and magic numbers, std::optional/std::expected in return types, and [[nodiscard]] on results that must be checked. Express ownership in signatures: unique_ptr parameters take ownership, references borrow, std::span and string_view view. Make classes RAII, let constructors establish invariants (factory functions when construction can fail), and keep the header minimal.

What they are really testing: Types, ownership in signatures and invariants.

SeniorExplain the exception safety guarantees.

The no-throw guarantee: the operation never throws (destructors, swap, moves should provide it). The strong guarantee: if it throws, state is unchanged (commit-or-rollback), typically via copy-and-swap or building the new state aside and swapping it in. The basic guarantee: if it throws, no resources leak and invariants hold, but the state may have changed. Every function should give at least the basic guarantee, which RAII makes almost automatic. std::vector::push_back gives the strong guarantee, which is why it needs noexcept moves to move rather than copy.

What they are really testing: The three levels and how to achieve them.

SeniorHow do you approach memory management in a performance-critical system?

Measure allocation first (heaptrack, allocation profilers). Reduce allocations: reserve, reuse buffers across iterations, small-buffer-optimised types, std::string_view and std::span instead of copies. Use arena or monotonic allocators (std::pmr::monotonic_buffer_resource) for request-scoped data freed all at once, and object pools for frequently recycled fixed-size objects. Consider a faster general allocator (jemalloc, mimalloc, tcmalloc). Keep ownership explicit with smart pointers at the edges while hot paths use indices or references into owned storage.

What they are really testing: Allocation-aware design and pmr knowledge.

SeniorHow would you debug a crash that happens only in production?

Collect evidence: core dumps or minidumps with symbols stored for every release build (build IDs, a symbol server), logs with enough context around the crash, and the exact build and configuration. Load the dump in a debugger to get the backtrace and variables. Reproduce with the production input under sanitizers (ASan, UBSan, TSan) and in a release build, since optimisations can expose undefined behaviour that debug builds hide. If it is timing-dependent, suspect data races and check with TSan or record-replay tools (rr). Add a regression test once found.

What they are really testing: Symbols and dumps, sanitizers, and release-only UB.

SeniorWhen would you choose C++ for a new project, and when not?

Choose C++ when you need predictable performance and control over memory and hardware: game engines, trading systems, databases and storage engines, browsers, embedded and automotive software, high-performance computing, and when integrating with an existing C++ ecosystem (Unreal, Qt, CUDA). Consider Rust when memory safety is paramount and the ecosystem fits, Go, Java or C# for typical network services where developer speed matters more than the last microsecond, and Python for glue and data work. Team skills, existing code and hiring matter as much as benchmarks.

What they are really testing: Pragmatic trade-offs, including when C++ is the wrong tool.

SeniorWalk through how you review a C++ pull request.

Correctness and lifetime first: who owns each object, can any reference, pointer, iterator or view dangle, is anything used after a move. Undefined behaviour: bounds, signed overflow, uninitialised values, data races. Resource safety: RAII everywhere, exception safety of state changes. API: types that prevent misuse, const-correctness, explicit, [[nodiscard]], ownership visible in signatures. Performance: needless copies, allocations in loops, container choice. Build health: new warnings, header dependencies, compile time. Tests cover edge cases and pass under sanitizers. Comments separate blocking issues from suggestions.

What they are really testing: A lifetime- and UB-focused review.

The senior-answer shape
Clarify the goal and constraints, name two options with their costs, pick one and say what would make you change your mind, then say how you would verify it in production. That structure matters more than any single fact.
09

The coding round, walked through

A live coding round is 30 to 45 minutes on one or two problems in a shared editor, often without autocompletion. The interviewer grades how you think, communicate and test, not only whether it compiles. Follow the same script every time, the one from Module 14.

  1. 1
    Clarify (2 min)

    Restate the problem. Ask about input size, empty input, duplicates, value ranges (does a sum fit in int?), ordering of the output, and what to return when there is no answer. Write the signature first.

  2. 2
    Example (1 min)

    Work one small case by hand. It becomes your first test.

  3. 3
    Brute force out loud (2 min)

    "For every request, count the same client's requests in the next window: O(n²)." Say it and its cost before improving it.

  4. 4
    Pick the pattern (1 min)

    Group with an unordered_map? Sort then slide a window? A heap? Name it, and why.

  5. 5
    Code (15 min)

    Talk while you type. const& parameters, size_t versus int handled deliberately, STL algorithms where they help, and the edge cases you listed.

  6. 6
    Test (5 min)

    Run your example, then the edges. Finding your own bug scores higher than never having one.

  7. 7
    Complexity and improvements (2 min)

    State time and space, then the better version if there is one.

A typical 30-minute problem solved that way: given a log of (client, second) requests in no particular order, return every client that made more than limit requests within any window-second span, sorted by name. The version below groups by client, sorts each client's times, and slides a window over them (Module 14, pattern 2).

C++main.cpp
#include <algorithm>
#include <iostream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

// Contract: log is unsorted and may be empty; limit >= 1; window >= 1 second.
// A client offends if more than 'limit' requests fall within 'window' seconds
// (times t and t + window - 1 are in the same span). Result sorted by name.
std::vector<std::string> offenders(const std::vector<std::pair<std::string, int>>& log,
                                   int limit, int window) {
    std::unordered_map<std::string, std::vector<int>> byClient;
    for (const auto& [client, second] : log) byClient[client].push_back(second);

    std::vector<std::string> result;
    for (auto& [client, times] : byClient) {
        std::sort(times.begin(), times.end());                // O(k log k) per client
        std::size_t left = 0;
        for (std::size_t right = 0; right < times.size(); ++right) {
            while (times[right] - times[left] >= window) ++left;
            if (right - left + 1 > static_cast<std::size_t>(limit)) {
                result.push_back(client);
                break;
            }
        }
    }
    std::sort(result.begin(), result.end());                   // unordered_map has no order
    return result;
}

int main() {
    std::vector<std::pair<std::string, int>> log{
        {"ana", 1}, {"bo", 2}, {"ana", 3}, {"ana", 5}, {"bo", 30}, {"cy", 7},
        {"ana", 70}, {"bo", 31}, {"bo", 33}, {"cy", 50}, {"bo", 32},
    };
    for (int limit : {2, 3}) {
        for (const auto& c : offenders(log, limit, 10)) std::cout << c << ' ';
        std::cout << '\n';
    }
    std::cout << offenders({}, 1, 1).size() << '\n';           // empty log
}
Outputcompiled & run with real C++
ana bo 
bo 
0

ana makes 3 requests in seconds 1 to 5, so she offends at a limit of 2 but not 3; bo makes 4 in seconds 30 to 33. Overall O(n log n) for the sorts, O(n) for the windows, O(n) extra memory. The explicit static_cast avoids a signed/unsigned comparison warning.

Your turn

The interviewer follows up: "the log is now an endless stream, already in time order". Rewrite it with an unordered_map<std::string, std::deque<int>> of each client's recent times, dropping times that fall out of the window as each request arrives. What is the memory cost now?

What loses the round

  • Silence for ten minutes, then a wall of code
  • Iterating an unordered_map and assuming its order
  • Comparing int with size() and ignoring the warning
  • Raw new and delete in a coding problem
  • "It should work" without running an example

What wins it

  • Narrating your reasoning, including dead ends
  • A signature, contract and example before code
  • Testing edge cases: empty log, a limit no one exceeds, requests exactly window seconds apart
  • Naming the complexity without being asked
  • Idiomatic C++: const&, structured bindings, STL algorithms
10

Take-home assignment checklist

C++ take-homes are usually "implement this data structure or component with tests" or "build a small command-line tool that processes this file". Reviewers open the README, build it, run the tests, then read the code. Most rejected submissions fail at step two: it does not build on the reviewer's machine.

  • It builds on a clean machine with CMake: cmake -S . -B build && cmake --build build && ctest --test-dir build. Fetch GoogleTest with FetchContent or state the one dependency to install. Test on at least two compilers if you can.
  • README: what it does, how to build, run and test it, the C++ standard used, and the decisions you made, including what you deliberately left out.
  • Tests with GoogleTest or Catch2: the happy path, empty and invalid input, and the one tricky rule in the spec. Mention that they pass under AddressSanitizer and UBSan.
  • Zero warnings with -Wall -Wextra -Wpedantic, and code formatted with clang-format.
  • Modern, safe C++: RAII everywhere, no raw owning pointers, const-correctness, std::optional or exceptions for errors, containers instead of manual arrays.
  • Structure: a library with the logic and a thin main, so the logic is tested without the command-line wrapper.
  • Performance where asked: if the task mentions speed, include a small benchmark and state the complexity; do not micro-optimise where it was not asked.
  • No noise in the repo: no build directories, no IDE folders, no commented-out code.
  • Time-box to what they asked and say so in the README. A handful of meaningful commits beats one "final" dump.
The sentence reviewers want to write
"Built first time with no warnings, tests pass under sanitizers, ownership is obvious, and the code reads like the team already wrote it." Aim every decision at that sentence. The next module, Job Ready, turns the same standards into a portfolio.

Frequently asked questions

What C++ topics are asked most in interviews?
At junior level: pointers versus references, const, the stack and the heap, RAII, constructors and destructors, virtual functions and virtual destructors, smart pointers, STL containers and undefined behaviour. At mid level: move semantics, the Rule of Zero, Three and Five, vtables, casts, templates and concepts, iterator invalidation, threads, mutexes and atomics. Senior rounds add low-latency design, build times, ABI, legacy migration and production debugging.
Which C++ standard should I prepare for interviews?
Prepare on C++17 and C++20: smart pointers, move semantics, lambdas, auto, structured bindings, std::optional and std::variant, constexpr, and in C++20 concepts, ranges and std::span. Be ready to explain what code looked like before these features, since many interviewers work in older codebases.
Do C++ interviews include coding problems?
Almost always. Expect one or two data-structure and algorithm problems solved in C++ with the STL, where interviewers also watch for C++-specific issues such as unnecessary copies, signed and unsigned comparisons, and memory safety. Games, trading and embedded interviews may add domain problems such as bit manipulation or implementing a small container.

Finish the C++ handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.