C++ Language Core
Top 10 Must-Asked Questions — Master these, and C++ interviews become your comfort zone.
Stack vs Heap Memory
CoreMost Asked4 minThink of stack like a stack of plates at a restaurant. You put one plate on top, take from top. Super fast, super organized. Every time a function is called, a "stack frame" is pushed — local variables, return address, params. When function returns, frame is popped. Memory cleaned automatically.
Heap is like a warehouse. Tons of space, but you manage it yourself. You request memory (new), you use it, and you MUST return it (delete). If you forget, that's a memory leak. Heap allocation is slower because the OS has to search for a free block big enough.
The critical thing interviewers want: stack has limited size (1-8 MB), grows downward, LIFO. Heap is much larger, grows upward, fragments over time. Stack is thread-local (each thread gets its own), heap is shared across threads.
Stack grows down, heap grows up. Stack is contiguous; heap fragments over time.
Key Interview Points
- Stack allocation = just moving stack pointer (one CPU instruction). O(1), insanely fast.
- Heap allocation = OS must search free list or buddy system. Much costlier.
- Stack overflow = too deep recursion or huge local arrays.
- Each thread has its own stack, but ALL threads share the same heap.
- Local variables → Stack.
new/malloc→ Heap.
Trap
Returning a pointer to a local stack variable is undefined behavior — the frame is already popped! Always return heap-allocated memory or use smart pointers.
Competitive Edge
Stack allocation literally just increments/decrements the ESP/RSP register. Mention this register-level detail and interviewers will know you truly understand the machine.
Follow-ups
Can we increase stack size?
ulimit -s on Linux). But it's fixed at thread creation time, not dynamically growable like heap.What happens if heap runs out?
malloc returns NULL, new throws std::bad_alloc. Always check allocation results in production code.Compile-Time vs Runtime Polymorphism
CoreMust Know4 minPolymorphism means "many forms." Same function name, different behavior. The real question is: WHEN does the compiler decide which function to call?
Compile-time (Static binding): Function overloading — compiler sees argument types and picks the right function at compile time. Also templates — compiler generates code for each type. Zero runtime cost.
Runtime (Dynamic binding): You have a base class pointer pointing to a derived object. Which function to call? Can't decide at compile time because actual type is known only at runtime. This uses virtual functions + vtable. There IS a small overhead — one pointer indirection through vtable.
// Compiler picks which add() to call by argument types class Calculator { public: int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } int add(int a, int b, int c) { return a + b + c; } };
class Animal { public: virtual void speak() { cout << "Some sound" << endl; } virtual ~Animal() {} }; class Dog : public Animal { public: void speak() override { cout << "Bark!" << endl; } }; // Decided at RUNTIME — which speak()? Animal* a = new Dog(); a->speak(); // Output: "Bark!" — Dog::speak() called delete a;
Key Interview Points
- Compile-time: function overloading, operator overloading, templates (early binding)
- Runtime: virtual functions, function overriding (late binding via vtable)
- Without
virtual, base pointer calls base function — no polymorphism - Compile-time = faster (no vtable lookup). Runtime = flexible, extensible
Trap
Overloading is NOT overriding. Overloading = same name, different params, same class. Overriding = same name, same params, parent-child classes. Mixing these up is an instant red flag.
Competitive Edge
Mention CRTP (Curiously Recurring Template Pattern) — achieves polymorphism at compile-time without virtual function overhead. Used in performance-critical systems: class Dog : public Animal<Dog> {}.
What Happens During C++ Compilation?
ConceptDeep4 minEach .cpp is compiled independently. The linker combines all object files into the final binary.
Key Interview Points
- Preprocessor errors: wrong #include path. Compiler errors: syntax/type errors. Linker errors: undefined reference.
- Each .cpp file is compiled independently — that's why headers exist (share declarations).
- Static linking: library code copied into executable. Dynamic linking: .dll/.so loaded at runtime.
- "Undefined reference to..." is a LINKER error — declaration found, definition missing.
Trap
"Undefined reference" is a LINKER error, NOT a compiler error. It means the declaration was found (header included) but the definition is missing (source file not linked). Don't confuse the two stages.
Virtual Functions & VTable
CoreDeep Internals5 minWhen you mark a function virtual, you tell the compiler: "Don't bind this call at compile time. Figure it out at runtime." But how? Enter the vtable.
For every class with at least one virtual function, the compiler creates a hidden table — the VTable. It's just an array of function pointers. Each entry points to the most derived version of that function. Every object gets a hidden pointer (vptr) to its class's vtable.
When you call basePtr->someVirtualFunc(), the CPU: follows vptr → goes to vtable → picks the function pointer at correct index → calls it. That's one extra indirection.
Each class has its own vtable. Derived class overrides speak() but inherits move().
Key Interview Points
- VTable is per-class (shared by all objects). vptr is per-object (hidden member).
- Pure virtual (
= 0) makes class abstract — can't instantiate it. - Always make base class destructor virtual — else derived destructor won't be called through base pointer → memory leak.
- vptr adds
sizeof(pointer)to each object (usually 8 bytes on 64-bit).
Trap
Virtual functions don't work with objects — only with pointers or references. If you do Base obj = derivedObj; (object slicing), the derived part is chopped off. No polymorphism.
Competitive Edge
vtable is created at compile time but resolved at runtime. Virtual function calls can't be inlined (compiler doesn't know which function will run). This is why game engines and HFT systems avoid virtuals in hot paths.
Shallow Copy vs Deep Copy
Core4 minIn C++, the default copy constructor does shallow copy — copies member values as-is. If a member is a raw pointer, it copies the address, not the data. Two objects now share the same heap memory. When one is destroyed, it frees the memory — the other has a dangling pointer. Crash.
Deep copy: You write a custom copy constructor that allocates new memory and copies actual content. Both objects are fully independent.
class MyString { char* data; int len; public: MyString(const char* s) { len = strlen(s); data = new char[len + 1]; strcpy(data, s); } // DEFAULT = SHALLOW COPY (compiler-generated) // Just copies the pointer → both point to same memory // → double-free crash when both destructors run! // DEEP COPY — allocate + copy MyString(const MyString& other) { len = other.len; data = new char[len + 1]; // new memory! strcpy(data, other.data); // copy content } ~MyString() { delete[] data; } };
Key Interview Points
- Default copy constructor = shallow (memberwise copy).
- If class has raw pointers → MUST write deep copy constructor.
- Shallow copy causes double-free and dangling pointer bugs.
- Rule of Three: If you write destructor, copy ctor, or copy assignment — write ALL three.
Trap
std::vector, std::string already do deep copy internally. The problem only arises with raw pointers. If your class manages a resource, you need a custom copy constructor.
Copy Constructor vs Move Constructor
CoreC++114 minclass Buffer { int* data; size_t size; public: Buffer(size_t n) : size(n), data(new int[n]) {} // ─── COPY CONSTRUCTOR (expensive) ─── Buffer(const Buffer& other) : size(other.size) { data = new int[size]; // allocate memcpy(data, other.data, size * sizeof(int)); // copy } // ─── MOVE CONSTRUCTOR (cheap) ─── Buffer(Buffer&& other) noexcept : data(other.data), size(other.size) { other.data = nullptr; // steal + nullify other.size = 0; } ~Buffer() { delete[] data; } }; Buffer a(1000000); Buffer b = a; // COPY — allocates + copies 1M ints Buffer c = std::move(a); // MOVE — just steals pointer. O(1)
Key Interview Points
- Move is invoked for temporaries (rvalues) or with
std::move(). - After move, source is in "valid but unspecified state" — don't use it.
- Rule of Five: destructor + copy ctor + copy assignment + move ctor + move assignment.
std::move()doesn't move — it just casts to rvalue reference. Actual move happens in the move constructor.- Mark move constructor
noexcept— STL containers won't use move otherwise.
Competitive Edge
Mention RVO/NRVO (Return Value Optimization). Modern compilers often eliminate copies entirely by constructing the return value at the caller's location. Since C++17, copy elision is mandatory in certain cases. Sometimes neither copy nor move happens.
Smart Pointers & Why Needed
CoreModern C++4 min#include <memory> // ─── unique_ptr: SOLE owner, zero overhead ─── { auto ptr = std::make_unique<int>(42); // Can't copy: auto ptr2 = ptr; ← ERROR auto ptr2 = std::move(ptr); // transfer ownership } // auto-deleted here // ─── shared_ptr: SHARED ownership, ref counted ─── { auto sp1 = std::make_shared<int>(100); // refcount = 1 { auto sp2 = sp1; // refcount = 2 } // refcount = 1 } // refcount = 0 → deleted // ─── weak_ptr: observer, breaks circular references ─── auto shared = std::make_shared<int>(99); std::weak_ptr<int> weak = shared; // doesn't increase refcount if (auto locked = weak.lock()) { // safe to use — object still alive }
Key Interview Points
- Use
make_unique/make_sharedinstead ofnew— exception-safe + potentially faster. unique_ptrhas zero overhead vs raw pointer. Use by default.shared_ptrhas overhead: atomic ref count + control block.- Circular
shared_ptrreferences = memory leak. Break withweak_ptr. - Smart pointers use RAII: Resource Acquisition Is Initialization.
Trap
Two shared_ptrs created from the same raw pointer = double delete crash! Always create from make_shared or from another shared_ptr, never from a raw pointer already managed.
Process vs Thread
Concept3 minProcess is like a separate house — own kitchen, bathroom, everything. Thread is like roommates in the same house — share kitchen and bathroom but have their own beds. Sharing makes threads faster to create and communicate, but creates problems (race conditions).
Each process has its own virtual address space, file descriptors, stack, heap. Threads within a process share heap, code, data segments — but each thread has its own stack and registers.
Threads share memory within a process. Processes are fully isolated from each other.
Key Interview Points
- Process: Own address space, heavy, isolated, crash-safe.
- Thread: Shared address space, lightweight, fast communication, shared fate.
- Thread creation: ~microseconds. Process creation: ~milliseconds.
- Each thread has own stack + registers. Shares: code, data, heap, files.
- One thread crash can kill entire process (shared memory).
Trap
"Threads share everything" — Wrong! Each thread has its own stack and registers. They share heap, code, and data. Getting this wrong shows you haven't thought deeply about it.
Memory Leak & Segmentation Fault
Core4 minMemory leak: You do new but never delete. The memory stays allocated but unreachable. Program slowly eats more and more RAM. In short-lived programs, almost harmless (OS reclaims on exit). In long-running servers, catastrophic.
Segmentation fault: Accessing memory you shouldn't — dereferencing NULL, dangling pointer, buffer overflow, stack overflow. The OS sends SIGSEGV signal and kills your program.
// ─── MEMORY LEAK: forgot to delete ─── void leaky() { int* p = new int[1000]; // ... use p ... return; // LEAK! p is lost, memory never freed } // ─── FIX: use smart pointer ─── void safe() { auto p = std::make_unique<int[]>(1000); // auto-freed when scope exits, even on exception }
// ─── NULL dereference ─── int* p = nullptr; *p = 42; // SEGFAULT! // ─── Dangling pointer ─── int* q = new int(10); delete q; *q = 20; // SEGFAULT! memory already freed // ─── Stack overflow ─── void infinite() { infinite(); // never-ending recursion → stack overflow }
Key Interview Points
- Leak causes: Forgotten delete, exception before delete, circular shared_ptr.
- Segfault causes: NULL dereference, dangling pointer, out-of-bounds, stack overflow.
- Tools: Valgrind (leak detection), AddressSanitizer (segfault debugging).
- Prevention: Smart pointers, RAII, bounds checking, avoid raw new/delete.
Competitive Edge
Memory leak is NOT undefined behavior — your program still runs, just slowly eats RAM. Segfault usually IS undefined behavior. Also mention that Valgrind's --leak-check=full catches every single leak with exact line numbers.
malloc/free vs new/delete
CoreClassic3 min| Aspect | malloc / free | new / delete |
|---|---|---|
| Language | C function | C++ operator |
| Constructor | Does NOT call | Calls constructor |
| Destructor | Does NOT call | Calls destructor |
| Return type | void* (needs cast) |
Typed pointer (no cast) |
| On failure | Returns NULL | Throws std::bad_alloc |
| Size | Manual: sizeof(T)*n |
Automatic |
| Overloadable | No | Yes (operator overloading) |
// ─── malloc: raw bytes, no constructor ─── int* arr = (int*)malloc(5 * sizeof(int)); // just raw memory if (arr == NULL) { /* handle error */ } free(arr); // ─── new: allocate + construct ─── int* arr2 = new int[5]; // allocated + initialized delete[] arr2; // delete[] for arrays! // ─── For objects — new CALLS constructor ─── MyClass* obj1 = (MyClass*)malloc(sizeof(MyClass)); // obj1 is GARBAGE — constructor never ran! free(obj1); MyClass* obj2 = new MyClass(); // constructor runs ✓ delete obj2; // destructor runs ✓ // ─── BEST: use smart pointers ─── auto obj3 = std::make_unique<MyClass>(); // auto-managed ✓✓
Key Interview Points
new= allocate + construct.delete= destruct + free.malloc= allocate raw bytes.free= release bytes. No ctor/dtor.newthrows on failure.mallocreturns NULL.new/deleteare operators (can be overloaded).malloc/freeare functions.- Never mix: Don't
free()what younew'd, don'tdeletewhat youmalloc'd.
Trap
new[] must be paired with delete[] (NOT delete). Using delete on an array from new[] is undefined behavior — only the first destructor runs, rest are skipped.
Competitive Edge
Internally, new calls operator new (which often uses malloc) then calls the constructor via placement new. You can override operator new for custom memory pools. Mention this to show you understand the layers.
Ready to test C++ Core?
Practice the language details that usually decide C++ interview answers.