C++ Language Core

Top 10 Must-Asked Questions — Master these, and C++ interviews become your comfort zone.

Stack vs Heap Memory

CoreMost Asked4 min
Stack is fast, auto-managed, limited memory for local variables (LIFO). Heap is large, manually managed, slower memory you control with new/malloc. Stack = plate stack, Heap = warehouse.

Think 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 main() frame func1() frame func2() frame Grows Downward LIFO · Auto-managed · Fast HEAP obj A array B freed obj C obj D big_buffer Grows Upward Fragmented · Manual · Large

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?
Yes, via compiler flags or OS settings (e.g., 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 min
Compile-time = decision during compilation (function overloading, templates). Runtime = decision while running (virtual functions, overriding via vtable). Compile-time is faster, runtime is more flexible.

Polymorphism 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.

C++ — Compile-Time (Overloading)
// 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; }
};
C++ — Runtime (Virtual + Override)
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 min
Source code goes through 4 stages: Preprocessing (#include, #define) → Compilation (syntax check, generate assembly) → Assembly (.s → .o machine code) → Linking (combine .o files, resolve symbols → executable).
Preprocessor #include, #define .cpp → .i Compiler Syntax, types, AST .i → .s (assembly) Assembler Machine code .s → .o (object) Linker Resolve symbols .o → executable

Each .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 min
Virtual function enables runtime polymorphism. Compiler creates a VTable (array of function pointers) per class and a hidden vptr per object. Call flow: object → vptr → vtable → actual function.

When 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.

Base Object vptr ─────→ + data members Base VTable [0] → Base::speak() [1] → Base::move() Derived Object vptr ─────→ + extra members Derived VTable [0] → Derived::speak() [1] → Base::move() ← overridden ← inherited

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 min
Shallow copy copies pointer values (both objects point to same memory). Deep copy allocates new memory and copies actual data. Shallow = sharing a Google Doc link. Deep = downloading your own copy.

In 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.

C++ — Shallow vs Deep Copy
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 min
Copy duplicates data (expensive). Move steals resources from a dying object (cheap) — transfers ownership instead of copying. Copy = photocopying a 500-page book. Move = just taking the book from someone about to throw it away.
C++ — Copy vs Move
class 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
Smart pointers are RAII wrappers that automatically manage heap memory lifetime — no manual delete needed. Three types: unique_ptr (sole owner), shared_ptr (shared ownership via ref count), weak_ptr (observer, breaks cycles).
C++ — Smart Pointers
#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_shared instead of new — exception-safe + potentially faster.
  • unique_ptr has zero overhead vs raw pointer. Use by default.
  • shared_ptr has overhead: atomic ref count + control block.
  • Circular shared_ptr references = memory leak. Break with weak_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 min
Process = independent program with its own memory space (the house). Thread = lightweight worker inside a process sharing the same memory (roommates in the house). Thread creation is ~100x cheaper than process.

Process 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.

PROCESS A Code + Data (shared by all threads) Heap Memory (shared) Thread 1 Own Stack Own Regs Thread 2 Own Stack Own Regs Thread 3 Own Stack Own Regs ISOLATED PROCESS B Completely separate memory Cannot directly access Process A's memory

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 min
Memory leak = allocated memory but forgot to free it (lost forever until program ends). Segfault = tried to access memory you're not allowed to (instant crash). Leak = booking hotel rooms, never checking out. Segfault = breaking into someone's house.

Memory 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.

C++ — Memory Leak Examples
// ─── 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
}
C++ — Segfault Examples
// ─── 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
malloc/free are C functions — allocate raw bytes, no constructor/destructor. new/delete are C++ operators — allocate memory AND call constructor/destructor. Always prefer new/delete (or better, smart pointers) in C++.
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)
C++ — malloc vs new
// ─── 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.
  • new throws on failure. malloc returns NULL.
  • new/delete are operators (can be overloaded). malloc/free are functions.
  • Never mix: Don't free() what you new'd, don't delete what you malloc'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.

Start practice
Home 01. DBMS 02. OOPs 03. Computer Networks 04. Operating System 05. C++ Core 05. Java Core 05. Python Core 06. Puzzles 07. Linux 08. Git 09. SQL 10. Projects 11. System Design 12. DSA Concepts