- 60 questions in six groups: basics, pointers and memory, OOP and virtual functions, templates and STL, modern C++ (smart pointers, move semantics, RAII), concurrency and C++17/20.
- Highest-yield topics: virtual functions and vtables, virtual destructors, rule of three/five/zero, smart pointers,
std::move, RAII, iterator invalidation. - Code examples are short and compile as standard C++; type them out and run them, because interviewers often ask "what does this print?"
- Modern C++ answers score higher: prefer
unique_ptr,make_unique, range-for,auto, and the rule of zero.
C++ questions in interviews come in two flavours. In general SDE loops, C++ is just your coding language, and the interviewer checks that you know the STL, complexity of containers, and how not to leak memory. In roles at companies that build systems software, databases, trading platforms, browsers, games or embedded products, C++ itself is the subject: object layout, virtual dispatch, move semantics, undefined behaviour, concurrency. This list covers both, with code wherever "show me" beats "tell me".
How C++ Is Tested in Interviews
| Context | What gets asked |
|---|---|
| Campus and service companies | OOP pillars, pointers vs references, virtual functions, constructors and destructors, "output of this code" questions |
| Product companies, C++ as coding language | STL choices and complexities, references vs copies in function parameters, custom comparators, memory safety |
| Systems, HFT and embedded roles | Object model, vtables, move semantics, smart pointer internals, memory ordering, cache-friendly design, undefined behaviour |
C++ Language Basics (Q1 to Q10)
Q1. What are the main differences between C and C++?
C++ adds classes with access control, inheritance and virtual functions, references, function and operator overloading, templates, exceptions, namespaces, RAII-based resource management and the standard library (containers, algorithms, smart pointers). C++ is close to, but not a strict superset of, C: valid C like int* p = malloc(4); without a cast does not compile as C++. More differences are in C programming interview questions.
Q2. What is the difference between declaration and definition?
A declaration introduces a name and its type (extern int x;, int f(int);). A definition also creates the entity: allocates storage or provides the body (int x = 5;, int f(int a) { return a; }). The One Definition Rule says a non-inline function or variable may be defined only once across the program, while it can be declared many times.
Q3. What does const mean in different positions?
const int* p1; // pointer to const int: *p1 cannot change, p1 can
int* const p2 = &x; // const pointer to int: p2 cannot change, *p2 can
const int* const p3 = &x; // neither can change
class Account {
int balance = 0;
public:
int get() const { return balance; } // const member function: cannot modify members
};
Read declarations right to left: "p2 is a const pointer to int". const member functions can be called on const objects, which is why getters should be const. mutable members can still be modified inside const functions, useful for caches and mutexes.
Q4. What is the difference between const, constexpr and #define?
#define is textual replacement by the preprocessor: no type, no scope, hard to debug. const is a typed, scoped variable that cannot be modified, but may be initialised at run time. constexpr requires the value to be computable at compile time (for variables) or allows compile-time evaluation (for functions), so it can be used for array sizes and template arguments. Prefer constexpr for constants and inline functions or templates instead of function-like macros.
Q5. What does static mean in C++?
- Static local variable: initialised once, lives for the whole program, keeps its value between calls. Initialisation is thread-safe since C++11.
- Static global or function at namespace scope: internal linkage, visible only in that translation unit (unnamed namespaces are the modern alternative).
- Static data member: one copy shared by all objects of the class.
- Static member function: no
thispointer; can access only static members.
Q6. What is the difference between struct and class?
Only default access: struct members and base classes are public by default, class members and bases are private by default. Everything else is identical. By convention, struct is used for plain data aggregates and class for types with invariants.
Q7. What is an inline function?
inline was originally a hint to substitute the function body at the call site. Modern compilers decide inlining themselves; today the keyword's main meaning is "this function may be defined in multiple translation units" (ODR exemption), which is why functions defined in headers are inline. Member functions defined inside the class body are implicitly inline. C++17 extends this to inline variables.
Q8. What is function overloading, and how is it resolved?
Several functions with the same name and different parameter lists (number, types, or const/ref qualifiers). The compiler picks the best match at compile time: exact match, then promotions, then conversions, then user-defined conversions. Return type alone cannot distinguish overloads. Ambiguous calls are compile errors. Under the hood, the compiler encodes parameter types into the symbol name (name mangling), which is why C++ functions called from C need extern "C".
Q9. What are lvalues and rvalues?
Roughly: an lvalue has an identity and persists beyond the expression (a named variable, *p, arr[i]); you can take its address. An rvalue is a temporary (x + 1, std::string("hi"), a function returning by value). C++11 refines these into lvalue, xvalue and prvalue categories. The distinction matters because rvalue references (T&&) bind to rvalues and enable move semantics (Q47).
Q10. What is undefined behaviour? Give examples.
Code for which the standard imposes no requirements: the program may crash, appear to work, or be "optimised" in surprising ways. Examples: dereferencing null or dangling pointers, out-of-bounds array access, signed integer overflow, reading uninitialised variables, data races, using an object after its lifetime ends, modifying a string literal, double delete. Compilers assume UB never happens, so a check like if (x + 1 < x) for a signed int can be removed entirely. Tools to catch it: -fsanitize=address,undefined, Valgrind and compiler warnings (-Wall -Wextra).
Pointers, References and Memory (Q11 to Q20)
Q11. Pointers vs references: what are the differences?
| Aspect | Pointer | Reference |
|---|---|---|
| Null | Can be nullptr | Cannot be null in valid code |
| Initialisation | Optional (dangerous if left uninitialised) | Must be initialised |
| Reseating | Can point elsewhere | Always refers to the same object |
| Arithmetic | Supported | Not applicable |
| Syntax | *p, p->x | Same as the object |
| Levels | Pointer to pointer allowed | No reference to reference |
Guideline: use references for parameters that must refer to an object (const T& for read-only inputs to avoid copies), pointers when "no object" is a valid state or when you need to reseat. Prefer smart pointers for ownership.
Q12. Stack vs heap memory?
Stack memory holds local variables and call frames; allocation is just moving the stack pointer, so it is extremely fast, and objects are destroyed automatically at scope exit. It is small (often 1 to 8 MB per thread by default), so deep recursion or large local arrays cause stack overflow. Heap (free store) memory is allocated with new or via containers and smart pointers; it is large and flexible in lifetime but slower to allocate and must be released. Prefer stack and containers; reach for the heap when size or lifetime demands it.
Q13. new/delete vs malloc/free?
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 returns NULL on failure. delete calls the destructor then frees; free only frees. Never mix them (malloc then delete is UB). Use delete[] for arrays allocated with new[]. In modern code you rarely write either: use containers and std::make_unique.
Q14. What is a memory leak, a dangling pointer and a double free?
void leak() {
int* p = new int[100];
if (somethingFails()) return; // leak: delete[] never runs
delete[] p;
}
int* dangling() {
int x = 42;
return &x; // dangling: x dies when the function returns
}
void doubleFree() {
int* p = new int(5);
delete p;
delete p; // UB: double free (set p = nullptr after delete, or better, use unique_ptr)
}
All three disappear with RAII types: std::vector<int> or std::unique_ptr<int[]> in the first case, returning by value in the second, unique_ptr in the third.
Q15. What is a wild pointer and a null pointer? Why nullptr instead of NULL?
A wild pointer is uninitialised and points anywhere. A null pointer explicitly points to nothing. NULL is typically the integer 0 (or 0L), so f(NULL) can pick an f(int) overload over f(char*). nullptr (C++11) has its own type, std::nullptr_t, converts only to pointer types, and avoids that ambiguity.
Q16. What are the ways to pass arguments to a function?
By value (copy; the callee cannot affect the caller's object), by reference (T&, callee can modify), by const reference (const T&, no copy, read-only; the default for large inputs), by pointer (can be null), and by rvalue reference or by value-then-move for sink parameters that the function will store. Passing a vector by value inside a recursive DFS is a classic interview performance bug: O(n) copy per call.
Q17. What is the size of an empty class, and why?
At least 1 byte, so that two distinct objects have distinct addresses. When an empty class is used as a base class, the empty base optimisation lets it take zero bytes inside the derived object. A class with a virtual function is bigger than its data alone because of the hidden vptr, typically 8 bytes on 64-bit systems.
Q18. What is structure padding and alignment?
struct A { char c; int i; char d; }; // typically sizeof(A) == 12
struct B { int i; char c; char d; }; // typically sizeof(B) == 8
The compiler inserts padding so each member sits at an address that is a multiple of its alignment (often 4 for int), and pads the struct size to a multiple of its largest alignment so arrays stay aligned. Ordering members from largest to smallest alignment reduces padding. Exact sizes are implementation-defined, so say "typically".
Q19. What is the difference between shallow copy and deep copy?
The compiler-generated copy constructor copies each member. For a raw pointer member, that copies the address, so two objects share one buffer: a shallow copy, leading to double deletes when both destructors run. A deep copy allocates a new buffer and copies the contents. This is why classes that own raw resources need a custom copy constructor and copy assignment (the rule of three, Q45), or better, should hold a std::vector or std::string and need no custom copy at all.
Q20. What is volatile? Is it useful for multithreading?
volatile tells the compiler that every read and write must actually happen, because the value can change outside the program's control: memory-mapped hardware registers, signal handlers. It does not provide atomicity or memory ordering between threads in C++. For threads, use std::atomic or mutexes. Saying "volatile is not for concurrency in C++" is a strong signal.
OOP, Virtual Functions and the Object Model (Q21 to Q32)
Q21. What are the four pillars of OOP, in C++ terms?
Encapsulation: private data with public member functions maintaining invariants. Abstraction: exposing interfaces (abstract base classes) while hiding implementation. Inheritance: deriving classes to reuse and extend (class Car : public Vehicle). Polymorphism: compile-time via overloading and templates, run-time via virtual functions. For language-neutral answers see OOPs concepts interview questions.
Q22. Compile-time vs run-time polymorphism?
Compile-time (static) polymorphism is resolved by the compiler: function overloading, operator overloading, templates, CRTP. No runtime cost, but types must be known at compile time. Run-time (dynamic) polymorphism uses virtual functions called through base pointers or references; the actual function is chosen at run time from the object's dynamic type.
Q23. How do virtual functions work? Explain the vtable and vptr.
#include <iostream>
struct Shape {
virtual double area() const { return 0; }
virtual ~Shape() = default;
};
struct Circle : Shape {
double r;
explicit Circle(double r) : r(r) {}
double area() const override { return 3.14159 * r * r; }
};
int main() {
Shape* s = new Circle(2);
std::cout << s->area() << "\n"; // calls Circle::area via the vtable
delete s; // safe: virtual destructor
}
In every mainstream implementation: each class with virtual functions gets a vtable, a static array of function pointers, one slot per virtual function. Each object of such a class holds a hidden vptr, set by the constructor, pointing to its class's vtable. A call s->area() becomes: load the vptr from the object, load the function pointer from the slot for area, call it with this = s. Cost: one pointer per object, two dependent loads and an indirect call per virtual call, and usually no inlining. The standard does not mandate vtables, so say "in typical implementations".
Q24. Why do we need a virtual destructor?
If you delete a derived object through a base-class pointer and the base destructor is not virtual, the behaviour is undefined, and in practice only the base destructor runs, leaking whatever the derived part owns. Rule: if a class is meant to be used polymorphically (has any virtual function), give it a public virtual destructor, or make the destructor protected and non-virtual to forbid deletion through the base.
Q25. What is a pure virtual function and an abstract class?
A pure virtual function is declared with = 0: virtual void draw() const = 0;. A class with at least one is abstract and cannot be instantiated. Derived classes must override it to become concrete. A pure virtual function can still have a definition, which derived classes may call explicitly; a pure virtual destructor must have one. C++ interfaces are abstract classes with only pure virtual functions and a virtual destructor.
Q26. Can a constructor be virtual? Can you call virtual functions in constructors?
Constructors cannot be virtual: when constructing, you must know the exact type, and the vptr is set up during construction. The virtual constructor idiom uses a virtual clone() function instead. Calling a virtual function inside a constructor or destructor calls the version of the class currently being constructed or destroyed, not the most-derived override, because the derived part does not exist yet (or no longer exists). Calling a pure virtual function this way is UB.
Q27. What is object slicing?
void print(Shape s) { std::cout << s.area(); } // takes Shape BY VALUE
Circle c(2);
print(c); // slicing: only the Shape part is copied; prints 0, not 12.566
Copying a derived object into a base object by value copies only the base subobject; derived data and dynamic behaviour are lost. Pass polymorphic objects by reference or pointer (const Shape&), and consider deleting copy operations in polymorphic base classes.
Q28. What are override and final?
override asks the compiler to verify that the function actually overrides a base virtual function; it catches signature mismatches like a missing const that would otherwise silently create a new function. final on a function prevents further overriding; on a class it prevents inheritance, and lets the compiler devirtualise calls. Always write override.
Q29. What is the diamond problem, and how does virtual inheritance solve it?
If B and C both derive from A, and D derives from both, then D contains two A subobjects, and d.a_member is ambiguous. Declaring class B : virtual public A and class C : virtual public A makes them share one A subobject, which the most-derived class (D) constructs directly. Virtual inheritance adds indirection and complexity, so most designs avoid deep multiple inheritance of state and inherit only interfaces.
Q30. What are public, protected and private inheritance?
Public inheritance models "is-a": public base members stay public. Protected inheritance makes public and protected base members protected. Private inheritance makes them private and models "implemented-in-terms-of", which is usually better expressed by composition. Private base members are never accessible to the derived class in any mode.
Q31. What is a friend function or class?
A function or class granted access to another class's private and protected members. Common legitimate use: overloading operator<< for output, which must be a non-member but needs private data. Friendship is not inherited, not transitive and not mutual. Overuse weakens encapsulation.
Q32. What is the order of construction and destruction?
Construction: virtual base classes first, then direct base classes in declaration order, then data members in declaration order (not initializer-list order), then the constructor body. Destruction is exactly the reverse. Compilers warn (-Wreorder) when the initializer list order differs from declaration order, because initializing one member from another that is declared later reads an uninitialised value.
Templates and the STL (Q33 to Q42)
Q33. What are templates? How do they differ from Java generics?
Templates let you write type-generic code that the compiler instantiates separately for each type used: max<int> and max<double> become two different functions. This gives zero runtime overhead and works with any type supporting the operations used, but increases compile time and binary size, and errors used to be verbose (C++20 concepts improve this). Java generics use type erasure: one compiled version working on references, with no instantiation for primitives.
Q34. What is template specialization?
template <typename T>
struct TypeName { static const char* get() { return "unknown"; } };
template <>
struct TypeName<int> { static const char* get() { return "int"; } }; // full specialization
template <typename T>
struct TypeName<T*> { static const char* get() { return "pointer"; } }; // partial specialization
Full specialization provides a custom implementation for one exact type. Partial specialization (class templates only) handles a family of types such as all pointers. std::vector<bool> is a famous (and much criticised) specialization that packs bits, so its elements are not real bool& references.
Q35. What is SFINAE, and what replaced it?
"Substitution Failure Is Not An Error": when substituting template arguments produces an invalid type in a function template's signature, that overload is silently removed from consideration rather than causing an error. It was used with std::enable_if to constrain templates. C++17's if constexpr and C++20's concepts (template <std::integral T>) express the same constraints far more readably.
Q36. What are the STL components?
Containers (vector, deque, list, map, unordered_map, set...), iterators (the glue that lets algorithms work over any container), algorithms (sort, find, lower_bound, accumulate...) and function objects (comparators, lambdas). Plus adaptors like stack, queue and priority_queue built on other containers. For coding-round usage patterns, see C++ STL for coding interviews.
Q37. How does std::vector grow? What is the complexity of push_back?
A vector stores elements contiguously with a size and a capacity. When full, push_back allocates a larger buffer (growth factor 2 in libstdc++, 1.5 in MSVC), moves or copies elements over, and frees the old buffer. A single push can be O(n), but geometric growth makes it amortised O(1). Use reserve(n) when you know the size to avoid reallocations. Reallocation uses the move constructor only if it is noexcept; otherwise it copies to keep the strong exception guarantee, which is why move constructors should be marked noexcept.
Q38. map vs unordered_map?
| Aspect | std::map | std::unordered_map |
|---|---|---|
| Structure | Balanced BST (red-black tree in common implementations) | Hash table with buckets |
| Lookup / insert | O(log n) guaranteed | O(1) average, O(n) worst case |
| Ordering | Sorted by key; supports lower_bound, ranges | No order |
| Key requirement | operator< or a comparator | A hash function and == |
| Memory | Node per element | Nodes plus bucket array |
Competitive programmers know that crafted inputs can force many collisions in unordered_map with the default integer hash, turning it into O(n) per operation; a custom randomised hash fixes this. Pairs need a custom hash to be used as unordered_map keys.
Q39. What is iterator invalidation? Give examples.
Operations that change a container can make existing iterators, pointers and references invalid. For vector: push_back invalidates everything if it reallocates; insert/erase invalidate from the modified position onward. For list, map, set: only iterators to erased elements are invalidated. For unordered_map: rehashing invalidates iterators (but not references to elements).
// Wrong: erase invalidates `it`
// for (auto it = v.begin(); it != v.end(); ++it) if (*it % 2 == 0) v.erase(it);
// Right: use the iterator returned by erase
for (auto it = v.begin(); it != v.end(); ) {
if (*it % 2 == 0) it = v.erase(it);
else ++it;
}
// Idiomatic: erase-remove (O(n) instead of O(n^2))
v.erase(std::remove_if(v.begin(), v.end(), [](int x) { return x % 2 == 0; }), v.end());
Q40. What is the difference between push_back and emplace_back?
push_back takes an object (copying or moving it into the container). emplace_back takes constructor arguments and constructs the element in place, avoiding a temporary. For v.emplace_back(3, 'a') on a vector<string>, the string "aaa" is built directly in the vector. With an already-built object, both are equivalent. Caveat: emplace_back can call explicit constructors silently, so it is slightly less type-safe.
Q41. How do you write a custom comparator for sort, set or priority_queue?
#include <queue>
#include <vector>
#include <algorithm>
#include <utility>
struct Task { int priority; int id; };
void examples(std::vector<Task>& tasks) {
// sort descending by priority, then ascending by id
std::sort(tasks.begin(), tasks.end(), [](const Task& a, const Task& b) {
if (a.priority != b.priority) return a.priority > b.priority;
return a.id < b.id;
});
// min-heap of pairs (priority_queue is a max-heap by default)
std::priority_queue<std::pair<int,int>, std::vector<std::pair<int,int>>,
std::greater<std::pair<int,int>>> minHeap;
minHeap.push({3, 1});
}
Comparators must implement a strict weak ordering: return false for equal elements. Using <= instead of < violates this and can make std::sort read out of bounds or crash. That is a real-world bug interviewers like to ask about.
Q42. What are lambdas, and what do capture modes mean?
Lambdas are anonymous function objects: [capture](params) { body }. [x] captures by value (a copy at creation time), [&x] by reference, [=] and [&] capture everything used by value or reference, [this] captures the object pointer, and [p = std::move(ptr)] (C++14 init-capture) moves a value in. Danger: capturing locals by reference in a lambda that outlives the scope (stored callbacks, threads) creates dangling references. By-value captures are const unless the lambda is mutable.
Smart Pointers, Move Semantics and RAII (Q43 to Q54)
Q43. What is RAII?
Resource Acquisition Is Initialization: wrap every resource (memory, file, socket, mutex lock, database connection) in an object whose constructor acquires it and whose destructor releases it. Because C++ guarantees destructors run when objects leave scope, including during exception stack unwinding, resources cannot leak even on early returns or exceptions. std::vector, std::string, std::unique_ptr, std::lock_guard and std::fstream are all RAII types.
#include <cstdio>
#include <stdexcept>
class File {
std::FILE* f_;
public:
explicit File(const char* path) : f_(std::fopen(path, "r")) {
if (!f_) throw std::runtime_error("cannot open file");
}
~File() { std::fclose(f_); }
File(const File&) = delete; // owning a handle: no copies
File& operator=(const File&) = delete;
std::FILE* get() const { return f_; }
};
void process(const char* path) {
File f(path); // acquired
// ... any return or exception here still closes the file
} // released by ~File()
This is the single most important idea in C++. Java and Python rely on garbage collection plus try-with-resources or with; C++ uses deterministic destruction.
Q44. Explain unique_ptr, shared_ptr and weak_ptr.
| Smart pointer | Ownership | Copyable? | Overhead | Use for |
|---|---|---|---|---|
unique_ptr<T> | Exclusive | No, move-only | None with default deleter (same size as raw pointer) | Default choice for heap objects, factory return values, pimpl |
shared_ptr<T> | Shared, reference counted | Yes | Two pointers per handle plus a control block; atomic count updates | Genuinely shared lifetime: caches, graph nodes, async callbacks |
weak_ptr<T> | Non-owning observer of a shared_ptr | Yes | Uses the control block's weak count | Breaking cycles, caches that should not keep objects alive |
#include <memory>
#include <iostream>
struct Node {
int val;
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // weak: avoids a reference cycle
explicit Node(int v) : val(v) {}
~Node() { std::cout << "free " << val << "\n"; }
};
int main() {
auto u = std::make_unique<int>(7); // sole owner
auto u2 = std::move(u); // ownership transferred; u is now null
auto a = std::make_shared<Node>(1);
auto b = std::make_shared<Node>(2);
a->next = b;
b->prev = a; // if prev were shared_ptr, neither would be freed
if (auto p = b->prev.lock()) std::cout << "prev is " << p->val << "\n";
std::cout << "a.use_count = " << a.use_count() << "\n"; // 1
} // prints "free 1" then "free 2"
Q45. What are the rule of three, rule of five and rule of zero?
Rule of three: if a class needs a custom destructor, copy constructor or copy assignment operator, it almost certainly needs all three (it owns a resource). Rule of five (C++11): add the move constructor and move assignment operator. Rule of zero: design classes so they need none of them, by holding resources in RAII members (vector, string, unique_ptr) and letting the compiler generate everything. Rule of zero is the modern default; rule of five is for the few low-level resource-owning classes.
Q46. Write a class that follows the rule of five.
#include <cstddef>
#include <algorithm>
#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(const Buffer& other) // copy constructor: deep copy
: size_(other.size_), data_(new int[other.size_]) {
std::copy(other.data_, other.data_ + size_, data_);
}
Buffer& operator=(Buffer other) noexcept { // copy-and-swap: handles copy AND move assignment
swap(other);
return *this;
}
Buffer(Buffer&& other) noexcept // move constructor: steal, leave source empty
: size_(std::exchange(other.size_, 0)),
data_(std::exchange(other.data_, nullptr)) {}
void swap(Buffer& o) noexcept {
std::swap(size_, o.size_);
std::swap(data_, o.data_);
}
std::size_t size() const { return size_; }
};
The by-value operator= (copy-and-swap idiom) covers both copy assignment and move assignment: the parameter is copy-constructed from an lvalue or move-constructed from an rvalue, then swapped in. It is exception-safe and handles self-assignment. A dedicated move assignment operator can be slightly faster; mention the trade-off.
Q47. What are move semantics, and why were they added?
Before C++11, returning or passing a large object (a vector of a million elements) by value meant a deep copy, even when the source was a temporary about to be destroyed. Move semantics let a class transfer ownership of its internal resources from an rvalue: the move constructor copies the pointer and nulls the source, O(1) instead of O(n). Rvalue references (T&&) let overload resolution distinguish "you may steal from this" from "you must copy this".
Q48. What does std::move actually do?
Nothing at run time. It is a cast: static_cast<T&&>(x), telling the compiler "treat x as an rvalue", so the move constructor or move assignment is selected. The move happens inside those functions. After moving from an object, it is in a "valid but unspecified" state: you can destroy it or assign a new value, but should not rely on its contents. Two common mistakes: std::move on a const object silently copies (a const T&& cannot bind to the move constructor), and return std::move(local); can prevent copy elision, making code slower.
Q49. What is perfect forwarding? What is a forwarding (universal) reference?
In a template, T&& where T is deduced is a forwarding reference: it binds to both lvalues and rvalues, with T deduced as U& or U respectively (reference collapsing). std::forward<T>(arg) passes the argument on with its original value category, so a wrapper like make_unique or emplace_back forwards rvalues as rvalues and lvalues as lvalues.
#include <memory>
#include <utility>
template <typename T, typename... Args>
std::unique_ptr<T> myMakeUnique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
Q50. What are copy elision and RVO?
Return value optimisation constructs a function's return value directly in the caller's storage, skipping the copy or move. Since C++17, elision is guaranteed when returning a prvalue (return Widget(args);). Named return value optimisation (Widget w; ...; return w;) is permitted and done by all major compilers, and if it does not happen, the return is treated as a move. So return local objects by value without std::move.
Q51. Why prefer make_shared and make_unique over new?
They avoid naked new, so nothing can leak between allocation and taking ownership (this was a real issue in function calls with multiple arguments before C++17 tightened evaluation order). make_shared also allocates the object and control block in one allocation, which is faster and more cache-friendly. Trade-off: with make_shared, the object's memory cannot be released until all weak_ptrs are gone, because it shares the block with the weak count. Use new with shared_ptr only for custom deleters or that weak-pointer memory concern.
Q52. Is shared_ptr thread-safe?
The reference count is updated atomically, so copying and destroying different shared_ptr instances that point to the same object from different threads is safe. Accessing the same shared_ptr instance from multiple threads where one writes is a data race (use std::atomic<std::shared_ptr<T>> in C++20). And the pointed-to object is not protected at all; you still need a mutex for it.
Q53. What are the exception safety guarantees?
No-throw: the operation never throws (destructors, swaps, move operations marked noexcept). Strong: if it throws, state is unchanged, commit-or-rollback (copy-and-swap provides this). Basic: if it throws, no leaks and invariants hold, but state may change. Destructors should never throw: if a destructor throws during stack unwinding, std::terminate is called. Since C++11, destructors are implicitly noexcept.
Q54. What is the PIMPL idiom?
"Pointer to implementation": a class holds only a std::unique_ptr<Impl> to a privately defined struct. Private members move out of the header, so changing them does not recompile every user of the header, and the binary interface stays stable. The destructor must be defined in the .cpp file where Impl is complete, otherwise unique_ptr cannot delete an incomplete type. Costs: an extra allocation and an indirection per access.
Concurrency and C++17/20 Features (Q55 to Q60)
Q55. How do you create threads and avoid data races?
#include <thread>
#include <mutex>
#include <vector>
#include <iostream>
int main() {
long long counter = 0;
std::mutex m;
std::vector<std::thread> workers;
for (int t = 0; t < 4; ++t) {
workers.emplace_back([&] {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(m); // RAII lock
++counter;
}
});
}
for (auto& w : workers) w.join(); // must join or detach before destruction
std::cout << counter << "\n"; // 400000
}
A std::thread destroyed while still joinable calls std::terminate, so always join (C++20's std::jthread joins automatically). lock_guard is RAII for mutexes; unique_lock adds deferred locking and works with condition variables; scoped_lock (C++17) locks several mutexes without deadlock. For conceptual background on locks, deadlocks and condition variables, see multithreading and concurrency interview questions; the concepts transfer directly.
Q56. std::atomic vs mutex?
std::atomic<int> makes individual operations (load, store, fetch_add, compare-exchange) indivisible, usually with a single hardware instruction and no blocking; ideal for counters and flags. A mutex protects a critical section of arbitrary size and multiple variables. In the example above, std::atomic<long long> counter; ++counter; would be simpler and faster. Atomics default to sequentially consistent ordering; weaker orderings (memory_order_relaxed, acquire/release) are faster but much harder to reason about, and interviews for low-latency roles probe them.
Q57. What is a condition variable? Why the predicate loop?
A std::condition_variable lets threads sleep until notified, for producer-consumer queues. Always wait with a predicate, cv.wait(lock, [&]{ return !queue.empty(); });, because of spurious wakeups and because another consumer may have taken the item between notification and waking. The predicate is checked while holding the lock.
Q58. Name important C++17 features.
- Structured bindings:
for (auto& [key, value] : myMap). std::optional(maybe-a-value),std::variant(type-safe union),std::any.std::string_view: non-owning view of a string, no copies (beware dangling views of temporaries).if constexpr, if/switch with initializers, fold expressions, class template argument deduction (std::pair p(1, 2.0);).- Guaranteed copy elision, inline variables,
std::filesystem, parallel algorithms (std::execution::par).
Q59. Name important C++20 features.
- Concepts: named constraints on templates, readable errors.
- Ranges: composable views like
v | std::views::filter(pred) | std::views::transform(f). - Coroutines:
co_await,co_yieldfor async code and generators (library support matured in C++23). - Modules: a replacement for header inclusion, with improving compiler support.
- Three-way comparison
operator<=>,std::span,std::format,std::jthread,consteval, designated initializers.
Say which standard your team or your competitive programming setup uses, and avoid claiming hands-on depth in features you have only read about. Interviewers check quickly.
Q60. What is the difference between static_cast, dynamic_cast, const_cast and reinterpret_cast?
| Cast | Purpose | Checked? |
|---|---|---|
static_cast | Well-defined conversions: numeric types, up/down casts in a hierarchy when you know the type | Compile time only |
dynamic_cast | Safe downcast in polymorphic hierarchies | Run time via RTTI: returns nullptr (pointers) or throws std::bad_cast (references) |
const_cast | Add or remove const/volatile | Modifying an object that was originally const is UB |
reinterpret_cast | Reinterpret bits: pointer to integer, unrelated pointer types | None; most uses are implementation-defined or UB |
C-style casts (T)x try these in sequence and can silently do a reinterpret_cast, which is why named casts are preferred: they are explicit and searchable. Frequent dynamic_cast usage often signals a design that should use virtual functions instead.
How to Prepare for a C++ Interview
- Write and run the snippets. Compile with
g++ -std=c++17 -Wall -Wextra -fsanitize=address,undefined. Sanitizers turn invisible memory bugs into clear reports, and seeing them once makes the theory stick. - Implement three classes from scratch: a minimal
unique_ptr, avectorwith growth and move semantics, and ashared_ptrwith a control block. These cover rule of five, templates, RAII and move semantics in one exercise, and "implement unique_ptr" is itself a common interview question. - Know STL complexities cold and when iterators are invalidated.
- Prepare "output of this code" questions on virtual dispatch, constructors/destructors order, slicing, and static members.
- For systems and trading roles, add memory ordering, cache lines and false sharing, and how to avoid allocations on hot paths.
Object-oriented design questions often follow C++ fundamentals. Continue with the top 10 design patterns for interviews to see how virtual functions and smart pointers show up in strategy, factory and observer patterns.
Frequently Asked Questions
Interview-Ready in C++ and DSA
Prepflix's program pairs DSA in C++ with CS fundamentals and mock interviews, so you can answer language deep dives like these as confidently as the coding question.
Explore the Prepflix Course →