Skip to content

Medium C++ Interview Questions

96 medium questions with answers. Applied questions that check whether you can use the concepts on real work.

Topics in the medium set

Pointers and Memory Management: 13 Templates and Generic Programming: 13 Standard Template Library (STL): 13 Modern C++ (C++11 to C++23): 13 Performance, Debugging and Build Tools: 12 Object-Oriented Programming in C++: 11 Multithreading and Concurrency: 11 C++ Basics and Syntax: 10
Solved — mediumStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
3 questions free Unlock the rest in seconds
01

Explain the stages of compiling and running a C++ program.

Save to ▾
Show answer
There are four stages. The preprocessor expands #include directives and macros into a translation unit. The compiler turns each translation unit into assembly and then an object file. The linker combines object files and libraries and resolves symbols into an executable. At run time the loader maps it into memory. A missing function body therefore shows up as a linker error, not a compiler error.
02

What is the difference between const and constexpr?

Save to ▾
Show answer
const means a value cannot be modified after initialisation, but it may still be computed at run time. constexpr means the variable or function can be evaluated at compile time when its inputs are constant. With constexpr int sq(int x) { return x * x; } you can write int arr[sq(4)];. Use constexpr for array sizes, template arguments and lookup tables, and consteval when evaluation must happen at compile time.
03

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

Save to ▾
Show answer
Overloading means several functions share a name but differ in the number or types of parameters; return type alone is not enough. The compiler runs overload resolution: exact matches win, then promotions such as char to int, then standard conversions, then user-defined conversions. If two candidates are equally good the call is ambiguous and compilation fails. Name mangling gives every overload a unique linker symbol.
04

What does the static keyword mean in different contexts?

Save to ▾
Show answer
Inside a function, a static local variable is initialised once and keeps its value between calls. At namespace scope, static gives internal linkage, so the name is visible only in that translation unit. Inside a class, a static data member belongs to the class rather than each object, and a static member function has no this pointer. Since C++17 inline static members can be defined directly in the header.
05

What is the difference between an inline function and a macro?

Save to ▾
Show answer
A macro is text substitution done by the preprocessor with no type checking, so #define SQ(x) x*x gives a wrong result for SQ(a+1) and may evaluate arguments twice. An inline function is a real function with type checking, scope and debugger support, and the compiler may expand it at the call site. In modern C++ inline mainly allows a function to be defined in a header without breaking the One Definition Rule.
06

The linker reports undefined reference to a function that is declared in a header. What could be wrong?

Save to ▾
Show answer
The declaration exists but the linker finds no definition. Common causes: the .cpp file with the body is not compiled or not linked, the signature differs slightly such as const, namespace or parameter type, a template is defined in a .cpp file instead of the header, a C function is called without extern C linkage, or a library is missing from the link command or listed in the wrong order.
07

Two third-party libraries both define a global function called init and your build fails with duplicate symbols. How do you fix it?

Save to ▾
Show answer
The clean fix is to place each library's code in its own namespace so calls become liba::init and libb::init. If the source cannot change, I would check whether one function is only used internally and give it internal linkage, or wrap one library behind a shared library that hides its symbols using visibility settings. I avoid macro renaming hacks because they break silently on upgrades.
08

What are lvalues and rvalues?

Save to ▾
Show answer
An lvalue has identity and a persistent address, such as a named variable x. An rvalue is a temporary without lasting identity, such as 5 or the result of a + b. C++11 splits these further into lvalue, xvalue and prvalue. The distinction matters because rvalue references, written T&&, bind to temporaries and enable move semantics, letting the program steal resources instead of making expensive copies.
09

Write a C++ program that counts how often each word appears in the input.

Save to ▾
Show answer
#include <iostream> #include <map> #include <string> int main() { std::map<std::string, int> freq; std::string word; while (std::cin >> word) { ++freq[word]; } for (const auto& [w, c] : freq) { std::cout << w << ' ' << c << '\n'; } } // Use std::unordered_map for O(1) average lookups if sorted output is not needed.
10

Tell me about a time you found a difficult bug in your C++ code.

Save to ▾
Show answer
Situation: a service crashed randomly about once a day in production. Task: find the root cause without blocking releases. Action: I enabled core dumps, analysed them in gdb and saw a corrupted vector inside a callback. A staging run with AddressSanitizer showed a use-after-free because a lambda captured a raw pointer to a destroyed object. I changed it to capture a weak_ptr. Result: no crashes for months, and sanitizer runs were added to CI.
11

What is a memory leak and how do you detect one?

Save to ▾
Show answer
A leak happens when heap memory is allocated but never freed and the last pointer to it is lost, so memory use keeps growing. In long-running servers this eventually exhausts RAM. Detect leaks with Valgrind memcheck, LeakSanitizer through -fsanitize=address, or heap profilers. Prevent them with RAII types such as std::unique_ptr and std::vector, which release memory automatically.
12

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

Save to ▾
Show answer
new allocates memory and calls the constructor, returns a typed pointer and throws std::bad_alloc on failure. malloc only allocates raw bytes, returns void* and gives a null pointer on failure without calling any constructor. delete runs the destructor and then frees memory, while free does not run destructors. Never mix them, and always pair new[] with delete[].
13

What is RAII and why is it central to C++?

Save to ▾
Show answer
Resource Acquisition Is Initialization ties a resource's lifetime to an object's lifetime. The constructor acquires the resource, such as memory, a file, a socket or a mutex, and the destructor releases it. Because destructors run automatically when a scope ends, even when an exception is thrown, resources are never leaked. std::unique_ptr, std::lock_guard and std::fstream are standard RAII types.
14

Explain unique_ptr, shared_ptr and weak_ptr.

Save to ▾
Show answer
unique_ptr has sole ownership, cannot be copied, can only be moved, and costs almost nothing over a raw pointer. shared_ptr keeps a reference count in a control block and destroys the object when the last owner disappears. weak_ptr observes an object owned by shared_ptr without increasing the count; call lock() to get a shared_ptr if it still exists. weak_ptr breaks cycles and suits caches.
15

Why should you prefer make_unique and make_shared over calling new directly?

Save to ▾
Show answer
They avoid writing raw new, so no allocated object can leak if another expression in the same call throws. make_shared also allocates the object and the control block together in one allocation, which is faster and more cache friendly. One trade-off: with make_shared the memory block stays allocated until every weak_ptr is gone, which matters for very large objects.
16

Two objects hold shared_ptr references to each other and their memory is never released. Why, and how do you fix it?

Save to ▾
Show answer
This is a reference cycle. Each object keeps the other's reference count above zero, so neither destructor runs even after all outside pointers are gone. Fix it by making one direction non-owning, usually the back pointer, with weak_ptr. For example, a parent holds shared_ptr to its children, while each child holds a weak_ptr to the parent and calls lock() when it needs access.
17

What is the difference between const int*, int* const and const int* const?

Save to ▾
Show answer
Read the declaration from right to left. const int* p is a pointer to a const int: you cannot modify the value through p, but p can point elsewhere. int* const p is a const pointer to int: the address is fixed but the value can change. const int* const p fixes both the address and the value. These forms are frequent interview traps and appear in function signatures.
18

What is the difference between a shallow copy and a deep copy?

Save to ▾
Show answer
A shallow copy copies pointer members, so two objects share one heap buffer; destroying one leaves the other dangling and causes a double free. A deep copy allocates new memory and copies the contents. The compiler-generated copy constructor copies member by member, so a class owning a raw pointer needs a custom copy constructor and copy assignment, or better, should hold a std::vector or unique_ptr instead.
19

Reverse a singly linked list using pointers.

Save to ▾
Show answer
struct Node { int val; Node* next; }; Node* reverse(Node* head) { Node* prev = nullptr; while (head) { Node* nxt = head->next; head->next = prev; prev = head; head = nxt; } return prev; } // O(n) time, O(1) extra space.
20

Your program crashes with the message double free or corruption. What could cause it?

Save to ▾
Show answer
The same block was freed twice or heap metadata was overwritten. Typical causes are a class with a raw owning pointer and the default copy constructor, deleting the same pointer in two places, mixing delete and delete[], or a buffer overflow writing past an allocation. AddressSanitizer shows the first invalid free and where the memory was allocated. The lasting fix is clear ownership with smart pointers or containers.
21

A function returns a reference to a local variable and the code sometimes works. Explain what is happening.

Save to ▾
Show answer
The local is destroyed when the function returns, so the reference dangles and using it is undefined behaviour. It may seem to work because the old bytes remain on the stack until another call overwrites them. Compilers warn with -Wreturn-local-addr or -Wreturn-stack-address. The fix is to return by value; copy elision and move semantics make returning objects cheap.
22

Describe a time you improved code quality by changing how memory was managed.

Save to ▾
Show answer
Situation: our module used raw new and delete everywhere and QA reported frequent leaks. Task: make it safe without a full rewrite. Action: I proposed a step-by-step migration to unique_ptr and std::vector, wrote a one-page ownership guideline and enabled LeakSanitizer in CI so new leaks failed the build. Result: leak reports dropped to zero within two sprints and reviews became faster because ownership was obvious.
23

Write a function that allocates a 2D matrix safely without raw new and delete.

Save to ▾
Show answer
#include <vector> std::vector<std::vector<int>> makeMatrix(int rows, int cols) { return std::vector<std::vector<int>>(rows, std::vector<int>(cols, 0)); } // Faster single-block version: index with r * cols + c struct Matrix { int rows, cols; std::vector<int> data; Matrix(int r, int c) : rows(r), cols(c), data(r * c) {} int& at(int r, int c) { return data[r * cols + c]; } };
24

Explain the rule of three, rule of five and rule of zero.

Save to ▾
Show answer
Rule of three: if a class needs a custom destructor, copy constructor or copy assignment, it usually needs all three because it manages a resource. Rule of five adds the move constructor and move assignment from C++11. Rule of zero says the best design avoids writing any of them by holding resources in members such as std::vector, std::string and unique_ptr, which already manage themselves.
25

How do virtual functions work internally?

Save to ▾
Show answer
Most compilers give each polymorphic class a vtable, a static table of function pointers for its virtual functions. Every object stores a hidden vptr pointing to its class's vtable. A call through a base pointer loads the vptr, looks up the slot and jumps to the right override. The cost is one pointer per object, an indirect call and usually no inlining, which matters only in very hot loops.
26

Why should a base class have a virtual destructor?

Save to ▾
Show answer
If you delete a derived object through a base pointer and the base destructor is not virtual, only the base destructor runs, which is undefined behaviour and typically leaks the derived part's resources. Making the destructor virtual ensures the derived destructor runs first, then the base one. Rule: any class meant to be used polymorphically needs a public virtual destructor, or a protected non-virtual one.
27

What is object slicing?

Save to ▾
Show answer
Slicing happens when a derived object is copied into a base object by value. Only the base part is copied, the derived data is lost and virtual calls use the base version. Example: void draw(Shape s) called with a Circle loses Circle's radius and behaviour. Avoid it by passing polymorphic objects by reference or pointer, and consider deleting copy operations in polymorphic base classes.
28

Memory leaks appear when derived objects stored as Base* are deleted. What is wrong and how do you fix it?

Save to ▾
Show answer
The base class almost certainly has a non-virtual destructor, so delete through a Base* runs only the base destructor and the derived members, such as buffers or file handles, are never released. Add virtual ~Base() = default; to the base. Better still, store the objects as std::unique_ptr<Base> so deletion is automatic, and enable -Wdelete-non-virtual-dtor to catch this at compile time.
29

Design a class structure for UPI, card and net banking payments in a checkout system.

Save to ▾
Show answer
I would define an abstract PaymentMethod interface with virtual functions such as authorize, capture, refund and a virtual destructor. UpiPayment, CardPayment and NetBankingPayment implement it with their own validation. Checkout depends only on PaymentMethod, and a factory creates the right object from user choice. New methods such as wallets can be added without touching checkout code, which follows the open-closed principle.
30

Should a Car class inherit from Engine or contain an Engine? Explain your choice.

Save to ▾
Show answer
A car is not an engine, it has an engine, so composition is correct: class Car holds an Engine member or a unique_ptr<Engine>. Inheritance models an is-a relationship and exposes the base interface, which would let callers treat a Car as an Engine. Composition also allows swapping engine types at run time through an interface such as PetrolEngine or ElectricMotor and keeps coupling low.
31

A function taking a Money object is silently called with an int and gives wrong results. What is happening?

Save to ▾
Show answer
Money probably has a single-argument constructor such as Money(int paise) that is not marked explicit, so the compiler performs an implicit conversion from int. A call like pay(500) compiles even though the unit is unclear. Mark single-argument constructors explicit, so callers must write pay(Money(500)). Also consider strong types or named factory functions like Money::fromRupees to make units obvious.
32

Overload operator+ and operator<< for a Complex number class.

Save to ▾
Show answer
#include <ostream> class Complex { double re_, im_; public: Complex(double re = 0, double im = 0) : re_(re), im_(im) {} Complex operator+(const Complex& o) const { return Complex(re_ + o.re_, im_ + o.im_); } friend std::ostream& operator<<(std::ostream& os, const Complex& c) { return os << c.re_ << (c.im_ < 0 ? '-' : '+') << (c.im_ < 0 ? -c.im_ : c.im_) << 'i'; } }; // operator<< is a friend non-member so the stream stays on the left side.
33

Implement a thread-safe singleton in modern C++.

Save to ▾
Show answer
class Config { public: static Config& instance() { static Config inst; // initialised once, thread-safe since C++11 return inst; } Config(const Config&) = delete; Config& operator=(const Config&) = delete; private: Config() = default; }; // The Meyers singleton relies on magic statics; no mutex or double-checked locking needed.
34

Tell me about a time you designed the class structure for a module and how you handled review feedback.

Save to ▾
Show answer
Situation: I had to build a report export module supporting PDF, CSV and Excel. Task: design it so new formats were easy to add. Action: I proposed an abstract Exporter interface and a factory, shared a short design note and asked seniors to review before coding. They pointed out duplicated formatting logic, so I extracted a Formatter helper. Result: a fourth format was later added in one day with no changes to existing classes.
35

Why are templates usually defined in header files?

Save to ▾
Show answer
A template is only a pattern; code is generated when it is instantiated with concrete types, and the compiler must see the full definition at that point. If the body lives in a .cpp file, other translation units cannot instantiate it and you get linker errors. The usual fix is defining templates in headers, or using explicit instantiation in the .cpp for a known, fixed set of types.
36

What is the difference between full and partial template specialization?

Save to ▾
Show answer
Full specialization provides a separate implementation for one exact set of template arguments, such as template <> class Hash<std::string>. Partial specialization, allowed only for class and variable templates, covers a family of arguments, such as template <typename T> class Hash<T*> for all pointer types. Function templates cannot be partially specialised; overloading is used instead.
37

What are C++20 concepts?

Save to ▾
Show answer
Concepts are named compile-time predicates on template parameters. You can write template <std::integral T> T gcd(T a, T b) or define your own with a requires expression. If a type does not satisfy the concept, the compiler reports a short, readable error at the call site instead of pages of instantiation errors. Concepts also allow overloading by constraint and document the template's expectations.
38

When do you need the typename keyword inside a template?

Save to ▾
Show answer
Inside a template, a name that depends on a template parameter, such as T::value_type, could be a type or a static member, so the compiler assumes it is a value. You must write typename T::value_type x; to say it is a type. Similarly, a dependent member template needs the template keyword, as in obj.template get<0>(). C++20 relaxes this in some contexts, but the rule still matters.
39

What are type traits and how does if constexpr use them?

Save to ▾
Show answer
Type traits in <type_traits> answer questions about types at compile time, such as std::is_integral_v<T>, std::is_pointer_v<T> or std::is_same_v<T, U>, and transform types, such as std::remove_reference_t. if constexpr picks a branch at compile time and discards the other, so code like if constexpr (std::is_integral_v<T>) can call integer-only operations safely inside a generic function.
40

What is template argument deduction and class template argument deduction?

Save to ▾
Show answer
For function templates the compiler deduces T from the call, so maxOf(3, 7) deduces int; mixing int and double fails because T would be ambiguous. Class template argument deduction, added in C++17, lets you write std::vector v{1, 2, 3}; or std::pair p(1, 2.5); without spelling the template arguments. Deduction guides can be written to tell the compiler how to deduce types from constructors.
41

A teammate complains that a template function produces hundreds of lines of compiler errors when misused. How do you improve this?

Save to ▾
Show answer
Constrain the template so misuse fails early with a clear message. With C++20 I would add a concept or requires clause, for example template <std::totally_ordered T>, so the error names the unmet requirement at the call site. On older standards, a static_assert with a readable message at the top of the function body helps. Good naming and a comment on expected type properties also help.
42

A generic average function must accept only numeric types. How would you enforce that?

Save to ▾
Show answer
In C++20 I would constrain it: template <typename T> requires std::is_arithmetic_v<T> double average(const std::vector<T>& v). Passing a vector of strings then fails with a clear constraint error. On C++17 the same check can go in a static_assert or std::enable_if. I would also decide whether bool and char should be allowed, since they are technically arithmetic types.
43

You need a container that holds ints, strings and doubles together. Which approach would you choose?

Save to ▾
Show answer
If the set of types is fixed and known, std::vector<std::variant<int, std::string, double>> is type-safe and needs no heap per element; std::visit handles each type. If types are open-ended, std::any works but needs any_cast and loses compile-time checks. If the items share behaviour, a base class interface with unique_ptr is cleanest. I usually prefer variant for closed sets.
44

A generic maxOf template gives wrong results when called with two C-style string literals. Why, and how do you fix it?

Save to ▾
Show answer
T is deduced as const char*, so the template compares pointer addresses instead of the characters. The result depends on where the literals are stored. Fix it by adding an overload for const char* that uses std::strcmp, or better, call it with std::string or std::string_view, where operator< compares contents. This shows why generic code must consider what operations mean for each type.
45

Write a variadic function that sums any number of arguments and one that prints them.

Save to ▾
Show answer
#include <iostream> template <typename... Args> auto sum(Args... args) { return (args + ...); // unary right fold } template <typename... Args> void printAll(const Args&... args) { ((std::cout << args << ' '), ...); // comma fold std::cout << '\n'; } // sum(1, 2, 3.5) returns 6.5; printAll(1, 'x', 2.5) prints 1 x 2.5
46

Write a C++20 concept and use it to constrain a function.

Save to ▾
Show answer
#include <concepts> #include <string> template <typename T> concept Printable = requires(T t) { { t.toString() } -> std::convertible_to<std::string>; }; template <Printable T> std::string describe(const T& obj) { return "Item: " + obj.toString(); } struct User { std::string name; std::string toString() const { return name; } }; // describe(User{"Asha"}) compiles; describe(42) fails with a clear message.
47

Tell me about a time you wrote generic, reusable code that saved effort for your team.

Save to ▾
Show answer
Situation: three services each had their own copy of retry logic for network calls. Task: remove duplication without slowing them down. Action: I wrote a small header-only template, retry with a callable and a policy for attempts and backoff, added unit tests and documented examples. Result: all three services adopted it, around 600 lines of duplicate code were deleted and a bug fix now happens in one place.
48

When are std::vector iterators invalidated?

Save to ▾
Show answer
Any insertion that causes reallocation, when size exceeds capacity, invalidates all iterators, pointers and references. Insertion without reallocation invalidates those at or after the insertion point. erase invalidates iterators at and after the erased element. clear and resize to a larger size beyond capacity invalidate everything. Holding a pointer to an element and then calling push_back is a classic bug.
49

How does std::vector grow, and what is the difference between size, capacity and reserve?

Save to ▾
Show answer
size is the number of stored elements; capacity is how many fit before reallocation. When push_back exceeds capacity, vector allocates a larger buffer, typically 1.5 or 2 times larger, moves or copies elements and frees the old one, giving amortised O(1) appends. reserve(n) pre-allocates capacity to avoid repeated reallocations when the final size is known; shrink_to_fit requests releasing unused capacity.
50

What is the difference between push_back and emplace_back?

Save to ▾
Show answer
push_back takes an existing object, copying or moving it into the container. emplace_back forwards constructor arguments and builds the element directly in place, which can avoid a temporary. For example v.emplace_back(name, age) constructs a Person inside the vector. The gain is small when you already have an object, and emplace_back can call explicit constructors unexpectedly, so use it deliberately.
51

What are the iterator categories in C++?

Save to ▾
Show answer
Input and output iterators support a single pass, such as stream iterators. Forward iterators allow multiple passes, as in forward_list. Bidirectional iterators also move backwards, as in list, map and set. Random access iterators support jumps and arithmetic, as in vector and deque, and C++20 adds contiguous iterators. Algorithms require a minimum category, so std::sort needs random access and cannot sort a std::list.
52

When would you use std::set versus std::priority_queue?

Save to ▾
Show answer
priority_queue is a binary heap that gives O(1) access to the largest element and O(log n) push and pop, but you cannot search or erase arbitrary elements. std::set keeps all elements sorted with O(log n) insert, erase and find, and gives both minimum and maximum. Use priority_queue for schedulers and Dijkstra, and set or multiset when you must update or remove arbitrary items.
53

What is the erase-remove idiom and what replaced it in C++20?

Save to ▾
Show answer
std::remove does not delete elements; it shifts the kept elements forward and returns an iterator to the new logical end. You then call erase to shrink the container: v.erase(std::remove(v.begin(), v.end(), 0), v.end());. remove_if works the same with a predicate. C++20 adds std::erase(v, value) and std::erase_if(v, pred), which do both steps in one call.
54

A program does millions of lookups in a std::map with string keys and is slow. How would you speed it up?

Save to ▾
Show answer
If ordering is not needed, switch to std::unordered_map and call reserve with the expected size to avoid rehashing. For string keys, use heterogeneous lookup with string_view to avoid building temporary strings. If keys are fixed after loading, a sorted vector with binary search is very cache friendly. I would profile before and after, because hashing long strings has its own cost.
55

Using a custom struct as the key of an unordered_map gives a compile error. Why, and how do you fix it?

Save to ▾
Show answer
unordered_map needs a hash function and equality comparison for the key type, and std::hash is not defined for user structs. Fix it by defining operator== (or defaulting it in C++20) and providing a hash functor that combines member hashes, passed as the third template argument, or by specialising std::hash for the type. std::map instead needs operator< or a comparator.
56

Code that erases elements from a vector inside a range-based for loop crashes. Why, and what is the correct way?

Save to ▾
Show answer
erase invalidates the iterator being used by the loop, so the next increment is undefined behaviour. Range-based for loops cannot handle this safely. Use the iterator returned by erase: for (auto it = v.begin(); it != v.end();) { if (bad(*it)) it = v.erase(it); else ++it; }. Better still, use std::erase_if, which is O(n) overall instead of O(n squared) for repeated erases.
57

Sorting a vector of large objects is slow. What can you do?

Save to ▾
Show answer
Sorting moves elements many times, so large objects that copy instead of move are expensive. First ensure the type has a noexcept move constructor. If moves are still heavy, sort a vector of indices or pointers with a comparator on the key, then reorder once if needed. Precomputing the sort key avoids repeated expensive comparisons. Use std::stable_sort only when equal-element order matters.
58

Write a custom hash so a struct can be used as a key in std::unordered_map.

Save to ▾
Show answer
#include <functional> #include <string> #include <unordered_map> struct Point { int x, y; bool operator==(const Point&) const = default; }; struct PointHash { std::size_t operator()(const Point& p) const noexcept { std::size_t h1 = std::hash<int>{}(p.x); std::size_t h2 = std::hash<int>{}(p.y); return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)); } }; std::unordered_map<Point, std::string, PointHash> grid;
59

Group a list of words into anagram groups.

Save to ▾
Show answer
#include <algorithm> #include <string> #include <unordered_map> #include <vector> std::vector<std::vector<std::string>> groupAnagrams(const std::vector<std::string>& words) { std::unordered_map<std::string, std::vector<std::string>> groups; for (const auto& w : words) { std::string key = w; std::sort(key.begin(), key.end()); groups[key].push_back(w); } std::vector<std::vector<std::string>> out; for (auto& [k, g] : groups) out.push_back(std::move(g)); return out; } // O(n * k log k) where k is the word length.
60

Tell me about a time when choosing a better data structure improved performance.

Save to ▾
Show answer
Situation: a pricing job took 40 minutes because it searched a vector of 2 lakh products linearly for every order line. Task: bring it under 5 minutes without changing output. Action: I profiled with perf, confirmed the hot loop, replaced the search with an unordered_map keyed by product ID built once with reserve, and added a regression test. Result: the job finished in about 3 minutes and the team adopted profiling before optimising.
61

Explain move semantics and what std::move actually does.

Save to ▾
Show answer
Move semantics let an object transfer its resources, such as a heap buffer, to another object instead of copying them. A move constructor takes T&& and usually steals the pointer and leaves the source empty but valid. std::move does not move anything; it is just a cast to an rvalue reference that allows a move overload to be chosen. After moving, only assign to or destroy the source.
62

What are std::optional, std::variant and std::any?

Save to ▾
Show answer
std::optional<T> holds either a T or nothing, ideal for functions that may not return a value, without using magic values or pointers. std::variant<A, B> is a type-safe union that holds exactly one of the listed types, inspected with std::visit or std::get_if. std::any holds a value of any copyable type and needs any_cast to read it. Prefer optional and variant because they are checked at compile time.
63

What does the C++20 ranges library add?

Save to ▾
Show answer
Ranges let algorithms take whole containers, as in std::ranges::sort(v), instead of begin and end pairs. Views such as filter, transform, take and reverse are lazy and compose with the pipe operator, for example v | std::views::filter(isEven) | std::views::transform(square). Nothing is computed until you iterate. Ranges also use concepts, so errors are clearer, and projections let you sort by a member easily.
64

What is std::string_view and what are its pitfalls?

Save to ▾
Show answer
string_view is a non-owning pointer and length pair that refers to existing characters, so passing it avoids copies and accepts both std::string and literals. The main pitfall is lifetime: it must not outlive the data it views, so returning a string_view to a local string or a temporary dangles. It is also not guaranteed to be null-terminated, so do not pass data() to C functions expecting C strings.
65

Is it expensive to return a large std::vector from a function by value?

Save to ▾
Show answer
Usually no. Return value optimisation constructs the result directly in the caller's storage, and since C++17 this copy elision is guaranteed when returning a prvalue. For a named local, compilers apply named RVO, and if they cannot, the vector is moved, which only copies three pointers. So return by value is clean and fast. Do not write return std::move(local); because it can block elision.
66

A function returns a string_view and callers sometimes print garbage. What is the likely bug?

Save to ▾
Show answer
The function probably builds or receives a temporary std::string and returns a string_view into it. When the function returns, the string is destroyed and the view dangles. Examples are returning std::string_view(a + b) or a view of a local string. Fix it by returning std::string, or make sure the view refers to data with a longer lifetime, such as a member or static storage.
67

How would you choose between exceptions, error codes and std::expected for error handling?

Save to ▾
Show answer
Exceptions suit rare, truly exceptional failures, keep the happy path clean and work well with RAII, but have unpredictable cost when thrown and are often disabled in embedded or game code. Error codes are explicit and cheap but easy to ignore. C++23 std::expected<T, E> returns either a value or an error, combining explicit handling with clean types. I follow the project convention and use expected for expected failures like parsing.
68

After calling std::move on a string, a teammate prints it and sees an empty value. Is this a bug?

Save to ▾
Show answer
It is a misunderstanding. A moved-from standard library object is in a valid but unspecified state; for std::string it is often empty, but you cannot rely on that. The safe operations are destroying it or assigning a new value. Reading it is not undefined behaviour, but the content is meaningless. Clang-tidy's bugprone-use-after-move check catches this pattern in code review.
69

Write a move constructor and move assignment operator for a class that owns a buffer.

Save to ▾
Show answer
#include <utility> class Buffer { std::size_t size_ = 0; int* data_ = nullptr; public: explicit Buffer(std::size_t n) : size_(n), data_(new int[n]{}) {} ~Buffer() { delete[] data_; } Buffer(Buffer&& other) noexcept : size_(std::exchange(other.size_, 0)), data_(std::exchange(other.data_, nullptr)) {} Buffer& operator=(Buffer&& other) noexcept { if (this != &other) { delete[] data_; size_ = std::exchange(other.size_, 0); data_ = std::exchange(other.data_, nullptr); } return *this; } };
70

Use std::variant and std::visit to handle different message types.

Save to ▾
Show answer
#include <iostream> #include <string> #include <variant> struct Login { std::string user; }; struct Logout { int sessionId; }; using Message = std::variant<Login, Logout>; template <class... Ts> struct Overloaded : Ts... { using Ts::operator()...; }; void handle(const Message& m) { std::visit(Overloaded{ [](const Login& l) { std::cout << "login " << l.user << '\n'; }, [](const Logout& o) { std::cout << "logout " << o.sessionId << '\n'; }}, m); } // Adding a new type without a handler becomes a compile error.
71

Use a C++20 ranges pipeline to get squares of the first three even numbers.

Save to ▾
Show answer
#include <iostream> #include <ranges> #include <vector> int main() { std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8}; auto result = v | std::views::filter([](int x) { return x % 2 == 0; }) | std::views::transform([](int x) { return x * x; }) | std::views::take(3); for (int x : result) std::cout << x << ' '; // 4 16 36 } // Views are lazy: elements are processed only while iterating.
72

Tell me about a time you convinced your team to adopt a new C++ feature or practice.

Save to ▾
Show answer
Situation: our team avoided smart pointers, and leaks kept reaching QA. Task: get agreement to adopt unique_ptr in new code. Action: I prepared a short benchmark showing no measurable overhead, converted one leaky module as a pilot, and presented before and after defect counts at our tech sync. I also wrote clang-tidy rules to help. Result: the team agreed to make it a coding standard and leak bugs dropped sharply in the next quarter.
73

How do you handle a tight deadline when the C++ code you need to change is complex?

Save to ▾
Show answer
Situation: a client needed a protocol change in two days in code I had not touched. Task: deliver safely. Action: I read the tests first, wrote two new tests for the required behaviour, made the smallest change that passed them, and flagged a risky area to my lead early instead of at the end. I logged a refactoring ticket for later. Result: we shipped on time with no regressions and cleaned up the module the next sprint.
74

Compare std::mutex, lock_guard, unique_lock and scoped_lock.

Save to ▾
Show answer
std::mutex provides lock and unlock, but calling them manually is error-prone. lock_guard is a simple RAII wrapper that locks in the constructor and unlocks in the destructor. unique_lock is more flexible: it can defer locking, unlock early, be moved and is required by condition_variable. scoped_lock, from C++17, locks one or more mutexes at once using a deadlock-avoidance algorithm.
75

What is a deadlock and how do you prevent it?

Save to ▾
Show answer
A deadlock happens when threads wait forever for each other's locks, for example thread A holds m1 and waits for m2 while thread B holds m2 and waits for m1. It needs mutual exclusion, hold and wait, no preemption and circular wait. Prevent it by always locking in a fixed global order, locking several mutexes together with std::scoped_lock, keeping critical sections small and never calling unknown code while holding a lock.
76

How does std::condition_variable work and what are spurious wakeups?

Save to ▾
Show answer
A condition variable lets a thread sleep until another thread signals that some state changed. The waiter holds a unique_lock and calls cv.wait(lock, predicate), which releases the mutex while sleeping and reacquires it on wake. The notifier changes the state under the mutex and calls notify_one or notify_all. Threads can wake without notification, called spurious wakeups, so always wait with a predicate that rechecks the condition.
77

Explain std::async, std::future and std::promise.

Save to ▾
Show answer
std::async runs a function, possibly on another thread, and returns a std::future that will hold its result or exception; calling get() waits for it. Use std::launch::async to force a new thread. std::promise is the writing end: one thread calls set_value or set_exception, and the paired future receives it. They make simple fork-join tasks easier than manual threads, but are not a full thread pool.
78

What does C++20 std::jthread add over std::thread?

Save to ▾
Show answer
std::jthread joins automatically in its destructor, so forgetting join no longer calls std::terminate. It also supports cooperative cancellation: the thread function can take a std::stop_token, check stop_requested() in its loop, and the owner calls request_stop(), which the destructor also does before joining. std::stop_callback lets you register cleanup actions. This makes long-running worker threads safer to write.
79

A service occasionally freezes and thread dumps show two threads each waiting on a mutex held by the other. How do you fix it?

Save to ▾
Show answer
This is a lock-order deadlock. I would find the two code paths, for example transfer locking account A then B while another locks B then A. The fix is to lock both together with std::scoped_lock(a.m, b.m), or to define a global ordering such as by account ID and always lock lower first. I would also shrink critical sections and add a stress test, and consider TSan or lock hierarchy checks.
80

Design a producer-consumer pipeline where one thread reads log lines and several workers parse them.

Save to ▾
Show answer
I would use a bounded thread-safe queue built with a mutex and two condition variables, one for not empty and one for not full, so a fast reader cannot exhaust memory. The reader pushes lines and workers pop and parse them. For shutdown, the reader pushes a sentinel per worker or sets a closed flag and notifies all. Batching lines per push reduces locking overhead, and results go to a second queue.
81

A program crashes with terminate called without an active exception when a function returns. What is the cause?

Save to ▾
Show answer
A std::thread object that is still joinable was destroyed, often because the function returned early or threw before reaching join. The std::thread destructor calls std::terminate in that case. Fix it by making sure every thread is joined on all paths, using an RAII guard, or switching to std::jthread, which joins automatically. Be careful with detach, since detached threads may access destroyed data.
82

Write a program where two threads print odd and even numbers alternately up to 10.

Save to ▾
Show answer
#include <condition_variable> #include <iostream> #include <mutex> #include <thread> std::mutex m; std::condition_variable cv; int n = 1; const int kMax = 10; void printer(int parity) { std::unique_lock<std::mutex> lk(m); while (true) { cv.wait(lk, [&] { return n > kMax || n % 2 == parity; }); if (n > kMax) break; std::cout << n++ << '\n'; cv.notify_all(); } cv.notify_all(); } int main() { std::thread odd(printer, 1), even(printer, 0); odd.join(); even.join(); }
83

Compute the sum of a large vector in parallel using std::async.

Save to ▾
Show answer
#include <future> #include <numeric> #include <vector> long long parallelSum(const std::vector<int>& v, int parts = 4) { std::vector<std::future<long long>> futs; std::size_t chunk = v.size() / parts; for (int i = 0; i < parts; ++i) { auto b = v.begin() + i * chunk; auto e = (i == parts - 1) ? v.end() : b + chunk; futs.push_back(std::async(std::launch::async, [b, e] { return std::accumulate(b, e, 0LL); })); } long long total = 0; for (auto& f : futs) total += f.get(); return total; } // C++17 alternative: std::reduce(std::execution::par, v.begin(), v.end(), 0LL)
84

Write a deadlock-free money transfer between two bank accounts.

Save to ▾
Show answer
#include <mutex> #include <stdexcept> struct Account { std::mutex m; long long balance = 0; }; void transfer(Account& from, Account& to, long long amount) { if (&from == &to) return; std::scoped_lock lock(from.m, to.m); // locks both without deadlock if (from.balance < amount) throw std::runtime_error("insufficient funds"); from.balance -= amount; to.balance += amount; }
85

What do compiler optimisation levels like -O0, -O2, -O3 and -Os mean?

Save to ▾
Show answer
-O0 disables optimisation and is best for debugging. -O1 and -O2 enable progressively more optimisations such as inlining, constant propagation and loop optimisations; -O2 is the common release default. -O3 adds aggressive vectorisation and inlining, which may increase code size. -Os optimises for size. Link-time optimisation with -flto optimises across translation units, and -march=native tunes for the current CPU.
86

What is cache locality and why does it matter for performance?

Save to ▾
Show answer
CPUs load memory in cache lines, usually 64 bytes, and access to L1 cache takes a few cycles while main memory takes hundreds. Code that walks memory sequentially, like iterating a std::vector, uses every byte loaded and lets the prefetcher help. Pointer-heavy structures like linked lists jump around memory and cause cache misses. That is why a vector often beats a list even for insert-heavy workloads.
87

Which profiling tools do you use for C++ and what does each show?

Save to ▾
Show answer
On Linux, perf samples the CPU and shows hot functions and call graphs, often visualised as flame graphs. Valgrind callgrind counts instructions precisely but runs slowly, and massif or heaptrack profile memory. gprof is older and needs instrumented builds. Intel VTune and AMD uProf show cache misses and pipeline stalls. On Windows, the Visual Studio profiler covers CPU and memory.
88

What are AddressSanitizer, UndefinedBehaviorSanitizer and ThreadSanitizer?

Save to ▾
Show answer
They are compiler instrumentation tools in GCC and Clang. ASan, -fsanitize=address, detects out-of-bounds access, use-after-free, double free and leaks with about 2x slowdown. UBSan, -fsanitize=undefined, catches signed overflow, invalid shifts and null dereferences. TSan, -fsanitize=thread, detects data races but cannot be combined with ASan. Running tests under sanitizers in CI catches bugs long before production.
89

A C++ server crashes with a segmentation fault in production. How do you debug it?

Save to ▾
Show answer
Make sure core dumps are enabled with ulimit -c unlimited or systemd-coredump, and keep debug symbols for release builds. Load the core with gdb binary core, run bt to see the stack trace, then inspect frames and variables. If the stack looks corrupted, reproduce in staging with AddressSanitizer. I would also check logs around the crash time and recent deployments, then add a regression test with the fix.
90

A full build of your C++ project takes 40 minutes. How would you reduce it?

Save to ▾
Show answer
I would measure with Clang -ftime-trace or build tooling to find the slowest files and headers. Then reduce header dependencies using forward declarations and the pimpl idiom, use Ninja for parallel builds, enable ccache or sccache, add precompiled headers for stable heavy headers, and split huge translation units. Unity builds and distributed compilation also help, and CI can cache build outputs.
91

Your application is slow and you are asked to optimise it. Where do you start?

Save to ▾
Show answer
I start by measuring, not guessing. I define a target, such as p99 under 50 ms, create a repeatable benchmark, and profile with perf to find where time actually goes. Then I fix the biggest hotspot first, usually algorithmic complexity, unnecessary copies, allocations or I/O, and re-measure after each change. I keep correctness tests running and stop when the target is met.
92

A binary runs on your laptop but fails on the server with error while loading shared libraries. What is wrong?

Save to ▾
Show answer
The program depends on a shared library that is missing or in a different version on the server, or the loader cannot find it. I would run ldd on the binary to see unresolved libraries, then install the right package, set the rpath at link time, or ship the library alongside. A newer glibc or libstdc++ on the build machine is a common cause; build inside a container matching production, or link statically where allowed.
93

List the gdb commands you would use to debug a crash.

Save to ▾
Show answer
g++ -g -O0 main.cpp -o app # build with debug symbols gdb ./app # or: gdb ./app core (gdb) run arg1 arg2 # start the program (gdb) bt # backtrace after the crash (gdb) frame 2 # select a stack frame (gdb) info locals # show local variables (gdb) print ptr->size # inspect an expression (gdb) break order.cpp:42 # set a breakpoint (gdb) watch total # stop when a variable changes (gdb) next / step / continue # step over, step into, resume
94

Write a GoogleTest unit test for a function, including an edge case.

Save to ▾
Show answer
#include <gtest/gtest.h> #include <stdexcept> #include <vector> double average(const std::vector<int>& v) { if (v.empty()) throw std::invalid_argument("empty"); long long s = 0; for (int x : v) s += x; return static_cast<double>(s) / v.size(); } TEST(AverageTest, ComputesMean) { EXPECT_DOUBLE_EQ(average({2, 4, 6}), 4.0); } TEST(AverageTest, ThrowsOnEmpty) { EXPECT_THROW(average({}), std::invalid_argument); }
95

How do you prioritise when a critical bug and a feature deadline arrive at the same time?

Save to ▾
Show answer
Situation: a customer-facing crash was reported two days before a feature demo. Task: handle both without dropping quality. Action: I informed my lead immediately, estimated both tasks, and we agreed the crash came first because it affected users. I fixed and released it in half a day, then cut one optional part of the feature after agreeing with the product owner. Result: the crash was resolved and the demo went ahead on time.
96

Why should we hire you for this C++ developer role?

Save to ▾
Show answer
Situation: I have prepared specifically for systems-level roles. Task: show I can contribute quickly. Action: I have built projects such as a multithreaded log analyser and a small key-value store in modern C++, used CMake, GoogleTest and sanitizers, and practised data structures and debugging with gdb. Result: I can write safe, measurable, well-tested code from the first sprint, and I learn fast from code reviews and feedback.

Frequently asked questions

Who should practise medium C++ questions?

Anyone with some hands-on practice: most real interview time is spent on medium questions like these.

How should I practise these?

Answer each question aloud before opening the answer, mark it as solved only when your answer matches the key idea, and save misses to Last-Minute Revision.

Other difficulty levels