1. What does the std::align function calculate?
Answer: It fits an object of a given size and alignment into a raw buffer, adjusting the buffer pointer and remaining space accordingly.
std::align(alignment, size, ptr, space) is the manual memory-alignment helper used by custom allocators and pools. Given a buffer (ptr, space):
- If there’s room, it advances
ptrto the first address that satisfies the requested alignment (rounding up), decreasesspaceby the bytes consumed, and returnstrue. - If the remaining space is too small to fit
sizeat the required alignment, it returnsfalseand leaves things unchanged.
This is how you hand out aligned chunks from a raw byte pool — e.g., allocating aligned storage for a type inside a custom arena. The interview answer: it aligns a pointer into a buffer for a given size/alignment, updating the pointer and remaining space, or reports failure.
2. What is the fundamental difference between std::atomic::compare_exchange_strong and std::atomic::compare_exchange_weak?
Answer: compare_exchange_weak may fail spuriously (return false even when the values match), but is faster in retry loops; compare_exchange_strong never fails spuriously.
Both compare the current value against expected and, if equal, store desired; if not, load the actual value into expected. The difference is on success detection:
- On Load-Linked/Store-Conditional (LL/SC) architectures like ARM, the store can be aborted spuriously — interrupted by a cache contention — even though the comparison matched.
compare_exchange_weakexposes this: it can returnfalsefor no logical reason. compare_exchange_strongretries internally so it never reports a spurious failure — at a small performance cost.
Since spurious failures are always retried in a loop anyway, weak is preferred in CAS loops for performance. The interview answer: weak can fail spuriously but is faster in loops (especially ARM LL/SC); strong never fails spuriously.
3. What is the structural purpose of a Non-Virtual Interface (NVI) idiom in class design?
Answer: Public non-virtual functions enforce invariants and pre/post-conditions, delegating the actual work to private/protected virtual functions that derived classes override.
The NVI idiom flips the usual design. Instead of making the interface itself virtual, you make the public entry point non-virtual and non-overridable, and make a hook virtual:
class Base {
public:
void doWork() { // public non-virtual
lock(); // common pre-conditions
doWorkImpl(); // virtual hook
unlock(); // common post-conditions
}
protected:
virtual void doWorkImpl() {} // derived overrides this
};
Benefits: the base class controls when and around what the virtual does — locking, invariant checks, logging, validation — and derived classes can’t accidentally skip those steps by overriding the public function. The interface is stable; the extension points are contained. The interview answer: public non-virtual methods enforce common behavior (locks, checks) and delegate to private/protected virtual hooks.
4. What will happen if you attempt to copy a std::unique_ptr using standard copy assignment (p1 = p2;)?
Answer: Compilation fails — std::unique_ptr deletes its copy constructor and copy assignment operator.
std::unique_ptr enforces exclusive ownership, so copying it is meaningless (who owns the pointee? a copy would mean two owners). The copy constructor and copy assignment operator are explicitly deleted (= delete), so p1 = p2; is a compile-time error, not a runtime problem.
Ownership can only be transferred via move: p1 = std::move(p2);, which hands the pointee to p1 and leaves p2 null. The interview answer: it’s a compile-time error; copying is deleted, so you must std::move.
5. What is the output of the following integer bitwise manipulation?
int x = 5; // 0101 in binary
int y = x << 2;
std::cout << y;
Output: 20.
Shifting left by N bits multiplies the value by 2^N. 5 << 2 = 5 × 4 = 20. In binary, 5 is 0101; shifting left two positions gives 10100, which is 16 + 4 = 20. Output: 20. (Watch for signed-overflow UB at extremes, but this example is well within range.)
6. What is the evaluation of std::is_same_v<int, const int>?
Answer: false.
std::is_same<T, U> asks whether two types are exactly identical. int and const int are distinct types — const is a top-level qualifier that makes them different. const on the type isn’t stripped when comparing with is_same; you’d have to remove it explicitly (std::remove_const_t<int> == int) to get true.
So std::is_same_v<int, const int> evaluates to false, while std::is_same_v<int, int> is true. The interview answer: false — int and const int are different types.
7. What design safety guarantee does the final specifier provide when appended to a class member method declaration?
Answer: It prevents derived classes from overriding that specific virtual function.
Marking a virtual function final locks it: no class further down the hierarchy may override it. Attempting to do so is a compile-time error.
class Base {
virtual void foo() final; // no override allowed below
};
class Derived : public Base {
void foo() override; // ERROR: Base::foo is final
};
The benefit is twofold: it documents design intent (“this behavior is locked”), and it enables devirtualization — since the compiler knows the function can’t be overridden, it can sometimes resolve the call statically instead of through the vtable. final can also be applied to a whole class (no one may derive from it). The interview answer: final prevents further overriding of that virtual function, enabling devirtualization.
8. What is the main reason to prefer std::make_unique over using new directly (std::unique_ptr<T>(new T()))?
Answer: std::make_unique is exception-safe — it prevents memory leaks during function-call argument evaluation.
Pre-C++17, evaluation order of function arguments was unspecified. Consider:
foo(std::unique_ptr<T>(new T()), someOtherThrowingFunction());
The compiler could evaluate new T() first, then call the throwing function before the unique_ptr constructor takes ownership of the raw pointer. If it throws, new T()’s memory is leaked. std::make_unique<T>() wraps allocation + construction into one exception-safe operation — there’s no window where a raw pointer exists unprotected. (C++17’s evaluation-order fixes reduced this, but make_unique remains the clean idiom.) Bonus: also makes the code shorter. The interview answer: make_unique is exception-safe, removing the leak window during argument evaluation.
9. What is the effect of declaring a variable using thread_local?
Answer: A distinct instance is created per thread, initialized at thread start and destroyed at thread end.
thread_local is a storage-class specifier. Each thread gets its own copy of the variable — no sharing, no synchronization needed for the variable itself. Its lifetime spans the thread: it’s created when the thread accesses it (or at thread start), persists across all function calls on that thread, and is destroyed when the thread terminates.
thread_local int counter = 0; // every thread has its own counter
This is the standard tool for per-thread state: thread-local caches, thread IDs, per-thread accumulators. The interview answer: one independent instance per thread, initialized at thread start and destroyed at thread end.
10. What is the primary function of std::enable_shared_from_this?
Answer: It lets a class method safely produce a std::shared_ptr to this sharing the existing control block instead of creating a duplicate.
The naive approach — a member returning std::shared_ptr<T>(this) — creates a brand-new control block for the same object. Now two independent control blocks believe they own the same T; when both reach refcount 0, you get a double free.
The fix: inherit from std::enable_shared_from_this<T> and call shared_from_this():
struct Node : std::enable_shared_from_this<Node> {
std::shared_ptr<Node> getShared() {
return shared_from_this(); // shares the existing control block
}
};
auto p = std::make_shared<Node>();
auto q = p->getShared(); // same control block, safe
Requirement: the object must be owned by a shared_ptr before calling shared_from_this() (otherwise it throws std::bad_weak_ptr). The interview answer: shared_from_this() returns a shared_ptr sharing the object’s existing control block, avoiding double-free.
11. What does the compile-time expression sizeof…(args) do when expanding variadic templates?
Answer: It evaluates the number of elements in the parameter pack at compile time.
sizeof...(args) is the pack-size operator, usable only with variadic template parameter packs. It returns the count of arguments in the pack as a compile-time constant — not the total byte size of the arguments (that would be sizeof per-element, summed or sizeof on a fold).
template<typename... Args>
size_t count() { return sizeof...(Args); } // e.g., 3 for <int, char, double>
It’s a constant expression, so it can be used in static assertions, array sizes, and template logic — often to stop recursion or to select overloads during pack expansion. The interview answer: the exact count of elements in the pack, computed at compile time.
12. What is the behavior of calling std::vector::emplace_back instead of push_back?
Answer: emplace_back constructs the element in place inside the vector’s storage from its arguments, avoiding a separate temporary and copy/move.
push_back(x) takes an already-constructed element and copies or moves it into the vector. emplace_back(args...) forwards its arguments directly to the element’s constructor, building the element inside the vector’s buffer:
v.push_back(Person("Alice", 30)); // construct temp → move into vector
v.emplace_back("Alice", 30); // construct Person directly in place
With emplace_back, no temporary Person is created and no move/copy is needed — the element’s constructor runs exactly once, in the vector’s memory. This is both faster (for non-trivial types) and expresses intent (you’re constructing, not inserting an existing object). The interview answer: emplace_back forwards arguments to the element constructor, constructing directly in place and avoiding copy/move of a temporary.
13. What is the execution result of the following pointer arithmetic operation on a 64-bit platform?
int arr[5] = {10, 20, 30, 40, 50};
int* ptr = arr;
ptr = ptr + 2;
std::cout << *ptr;
Output: 30.
Pointer arithmetic is scaled by the size of the pointed-to type. ptr + 2 on an int* advances the address by 2 * sizeof(int) — i.e., two array elements, not two bytes. ptr starts at arr[0] (10); ptr + 2 points at arr[2], which is 30. Output: 30.
14. What happens when a pure virtual function (virtual void foo() = 0;) is declared in a class?
Answer: The class becomes abstract and cannot be instantiated directly.
= 0 after a virtual function declaration marks it pure. A class with at least one pure virtual function is abstract: you can’t create objects of it. It can still be used as a base class, and derived classes must provide concrete implementations of all pure virtual functions to become instantiable themselves.
Note: a pure virtual function can have a body in C++ (you can still define it and call it explicitly from derived classes), but the class remains abstract. Abstract classes serve as interfaces/contracts — they define the shape of behavior without being usable as concrete types. The interview answer: declaring a pure virtual function makes the class abstract — no direct instantiation, derived classes must override all pure virtuals to be concrete.
15. What causes a stack overflow error during deep recursive calls?
Answer: The cumulative memory of nested call-stack frames exceeds the thread’s fixed stack limit (typically 1–8 MB).
Every function call pushes a stack frame — return address, saved registers, local variables — onto the thread’s call stack, which has a fixed, OS-allocated size (commonly 1–8 MB). Deep (especially unbounded) recursion keeps pushing frames without returning, until the stack region is exhausted. At that point the program faults with a stack overflow (in practice, SIGSEGV or an unhandled exception).
Note it’s the stack, not the heap, that’s exhausted — local data lives on the stack; only heap allocations grow dynamically. The fixes: convert recursion to iteration, use explicit heap-based stacks, or (when recursion is genuinely required and depth-bounded) increase the thread stack size. The interview answer: nested stack frames exceed the thread’s fixed stack limit, causing a stack overflow fault.
Premium Content
Unlock Top 50 - Part 2 and all premium lessons with a subscription.
From ₹199.99/year — See plans