Classes & RAII • Smart Pointers • Move Semantics • Polymorphism • STL • 2026

C++ Interview Questions

31 questions What each one tests, an answer frame, a spoken answer 36 min read

This page is for anyone facing a C++ round, from a first job to a senior systems or backend role. Most C++ interviews start with pointers, references and const, move to object lifetime, RAII and the rule of five, then test smart pointers, move semantics, virtual functions, templates and STL containers. Senior rounds add a debugging story and a judgement call about ownership or exceptions. Each question shows what the interviewer is really checking, the shape of a strong answer and a short answer you can say out loud. Practise saying them, then swap in your own stories.

Search all questions by round, difficulty and level, or save the ones you want to practise.

Core Language 5 questions

Easy Technical round Fresher, Mid-level Practice question

1. What is the difference between a pointer and a reference in C++, and how do you choose which one to use in a function signature?

What the interviewer is really testing:
Whether you know the real rules behind references, and whether your function signatures say something about ownership and optional arguments.
Answer frame:

Reference: an alias; must be bound when created, cannot be reseated, and has no null value in valid code.

Pointer: a variable holding an address; can be null, can point somewhere else later, supports arithmetic.

Choosing: reference when the argument must exist, pointer when it is optional or can change target.

Sample spoken answer:

"A reference is another name for an existing object. It has to be bound when it's declared, it can't be made to refer to something else later, and in valid code there's no such thing as a null reference. A pointer is its own variable that holds an address, so it can be null, it can be pointed at a different object, and you can do arithmetic on it. In function signatures I use a const reference for inputs that must exist and are too big to copy, a plain reference when the function has to modify the caller's object, and a pointer only when 'nothing' is a valid answer, like an optional output. For ownership I don't use raw pointers at all, I use smart pointers, so a raw pointer or reference in my code just means 'I'm borrowing this'."

Code:
void scale(Matrix& m, double k);          // must exist, will be changed
double norm(const Matrix& m);             // must exist, read only
bool find(const Tree& t, int key, Node** out = nullptr); // optional output
Red flag to avoid:

Saying references are just pointers with nicer syntax and can be reassigned, or using raw pointers to signal ownership.

They may ask next:
  • How can a reference end up dangling, even though it can never be null?
  • Why is returning a reference to a local variable a bug even though it compiles?
Say it in 60 seconds
Easy Technical round Fresher, Mid-level Practice question

2. What does const mean in each of these: const int* p, int* const p, and a member function marked const?

What the interviewer is really testing:
Whether you can read const declarations correctly and understand why const member functions matter for const objects and const references.
Answer frame:

Pointer to const: const int* p means you cannot change the int through p, but p can move.

Const pointer: int* const p means p always points to the same place, but the int can change.

Const member function: promises not to change the object, so it can be called on const objects and through const references.

Sample spoken answer:

"The trick is to read it right to left. const int* p is a pointer to a const int: I can make p point somewhere else, but I can't change the value through it. int* const p is a const pointer to an int: p is stuck on one address, but I can change the value. A member function with const after the parameter list promises not to change the object's state, and that matters a lot in practice, because if I pass an object by const reference, which I do all the time, I can only call its const member functions. So if someone forgets to mark a getter const, it can't be used on const objects. If a member really needs to change inside a const function, like a cache or a mutex, it gets marked mutable."

Code:
class Account {
public:
    int balance() const { return balance_; }  // callable on const Account&
    void deposit(int amount) { balance_ += amount; }
private:
    int balance_ = 0;
};

void print(const Account& a) {
    std::cout << a.balance();   // fine
    // a.deposit(10);           // compile error: not a const function
}
Red flag to avoid:

Mixing up pointer-to-const and const pointer, or treating const as a style choice with no effect on what can be called.

They may ask next:
  • When is it right to use mutable, and when is it a smell?
  • What does const_cast do, and when is using it undefined behaviour?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

3. What are the basic, strong and no-throw exception safety guarantees? And why should a destructor never throw?

What the interviewer is really testing:
Whether you can reason about the state a program is left in when an operation fails, and know how exceptions and destructors interact.
Answer frame:

Basic: after an exception, nothing leaks and objects are in a valid, though possibly changed, state.

Strong: the operation either fully succeeds or has no effect, like a transaction.

No-throw: the operation never throws; destructors, swaps and moves should aim for this.

Destructors: they are noexcept by default; a throw escaping one calls std::terminate.

Sample spoken answer:

"The basic guarantee says that if an operation throws, nothing leaks and every object is still valid, though the state may have changed. The strong guarantee says it either succeeds completely or leaves everything exactly as it was, like a transaction. Copy-and-swap is the usual way to get it: do the risky work on a copy, then swap it in with an operation that can't throw. The no-throw guarantee means the operation never throws, and that's what destructors, swap and move operations should give, because the other guarantees are built on them. Destructors are implicitly noexcept since C++11, so if an exception escapes one, std::terminate is called. Even without that, a destructor that throws while another exception is already unwinding the stack terminates the program. So if cleanup can fail, like a flush on close, I offer an explicit close function that can report errors, and the destructor only does a best effort and swallows or logs."

Red flag to avoid:

Saying it is fine to throw from a destructor as long as someone catches it.

They may ask next:
  • Which guarantee does std::vector::push_back give, and what does it depend on?
  • How would you design a file class whose close can fail?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level, Senior Practice question

4. How do lambda captures work in C++? Show me a lambda that captures by reference and ends up with a dangling reference.

What the interviewer is really testing:
Whether you understand that a lambda is an object holding its captures, and can spot lifetime bugs when it outlives the scope that made it.
Answer frame:

Closure: a lambda creates an object; captured variables become its members, copied or referenced.

By value vs reference: [x] copies at creation time; [&x] stores a reference that must stay valid.

Danger: a lambda that outlives its scope, stored as a callback or run on a thread, must not hold references to locals.

Sample spoken answer:

"A lambda expression creates an unnamed object, a closure, and each captured variable becomes a member of it. Capture by value copies the variable when the lambda is created. Capture by reference stores a reference, so it sees later changes but depends on the original still being alive. The bug happens when the lambda outlives the scope it was made in. If a function returns a lambda that captured a local by reference, or hands it to a thread or an event queue, the local is gone by the time the lambda runs, and it reads a dangling reference. That's undefined behaviour and often shows up as random values. Capturing this has the same risk if the object is destroyed first. So when a lambda escapes its scope I capture by value, use an init capture to move things in, or capture a shared_ptr or weak_ptr to keep the object alive or check it."

Code:
std::function<int()> makeCounter() {
    int count = 0;
    // return [&count] { return ++count; };   // dangling: count dies here
    return [count]() mutable { return ++count; };  // owns its own copy
}

auto job = [data = std::move(bigVector)] { process(data); };  // init capture
Red flag to avoid:

Using [&] for a callback that runs later without thinking about the lifetime of what it captures.

They may ask next:
  • Why does a lambda that changes a by-value capture need the mutable keyword?
  • What is the cost of storing a lambda in a std::function instead of auto?
Say it in 60 seconds
Medium Situational round Mid-level, Senior Practice question

5. After a crash from an uncaught exception, a teammate wants to wrap every function body in try and catch(...) that logs and carries on. What do you do?

What the interviewer is really testing:
Whether you know where exceptions should be caught, and can steer a well-meant fix towards one that keeps the program correct.
Answer frame:

Problem: catch(...) everywhere hides bugs and lets the program continue in a broken state.

Right place: catch at boundaries that can recover or report, like a request handler, a thread entry point or main.

Root cause: fix what threw, and use RAII so unwinding leaves state clean.

Sample spoken answer:

"I understand the reaction, a crash in production is painful. But catching everything in every function and carrying on is usually worse than the crash. The code after a swallowed exception runs on state that may be half updated, so we trade a loud crash for silent wrong results, and we lose the stack context that tells us what happened. I'd suggest two things. First, find out what actually threw and fix that. Second, catch at the few boundaries where we can really do something: the top of each request handler, where we can log with context and return an error, each thread's entry function, since an exception escaping a thread calls std::terminate, and main. Inside the code we rely on RAII so unwinding leaves everything clean, and we only catch specific exceptions where there's a real recovery, like retrying a network call."

Red flag to avoid:

Agreeing to catch everything and continue, or saying exceptions should never be caught.

They may ask next:
  • Where would you catch exceptions in a program that runs a pool of worker threads?
  • When would you prefer returning an error value over throwing in C++?
Say it in 60 seconds

Memory & RAII 3 questions

Easy Technical round Fresher Practice question

6. What's the difference between creating an object as a local variable and creating it with new? And why must new[] be matched with delete[]?

What the interviewer is really testing:
Whether you understand automatic versus dynamic lifetime, and know that mismatched new and delete is undefined behaviour, not a harmless slip.
Answer frame:

Automatic: a local object is destroyed at the end of its scope, with no code needed from you.

Dynamic: an object made with new lives until someone calls delete; forget it and you leak.

Matching: new pairs with delete, new[] with delete[]; mixing them is undefined behaviour.

Sample spoken answer:

"A local object has automatic lifetime. It's created where it's declared and its destructor runs when it goes out of scope, whether the function returns normally or an exception leaves it. An object created with new has dynamic lifetime: it lives on the free store until someone calls delete on it, and if nobody does, that's a leak. The pairing rule is strict. new goes with delete and new[] goes with delete[], because the array form has to run the destructor for every element and may track the count differently. Mixing them is undefined behaviour, which can mean a crash, heap corruption, or silently skipping destructors. In modern code I rarely write new or delete at all. I use locals where I can, std::vector instead of new[], and make_unique when I really need heap lifetime."

Red flag to avoid:

Saying delete and delete[] are interchangeable for simple types, or that the operating system cleans up leaks so they do not matter.

They may ask next:
  • Is it safe to call delete on a null pointer?
  • When do you genuinely need heap allocation instead of a local object?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level, Senior Practice question

7. What is RAII, and why do C++ developers say it matters more than try/finally-style cleanup?

What the interviewer is really testing:
Whether you see RAII as the core idea of C++ resource management, covering locks, files and sockets as well as memory.
Answer frame:

Idea: an object acquires a resource in its constructor and releases it in its destructor.

Guarantee: destructors run on every exit from a scope, including when an exception unwinds the stack.

Examples: unique_ptr for memory, lock_guard for mutexes, fstream for files.

Sample spoken answer:

"RAII means tying a resource's lifetime to an object's lifetime. The constructor acquires the resource, the destructor releases it, and because C++ guarantees destructors run when an object goes out of scope, including during stack unwinding from an exception, the cleanup can't be forgotten. It's not just memory. A lock_guard locks a mutex in its constructor and unlocks it in its destructor, so an early return or an exception can't leave the mutex locked. An fstream closes its file, a unique_ptr frees its object. Compared to manual cleanup, the big win is that correctness doesn't depend on every code path remembering to release things. Add a new early return next year and it's still correct. When I write a class that wraps a handle from a C library, I make it an RAII wrapper so the rest of the code never sees the raw close call."

Code:
void transfer(Account& from, Account& to, int amount) {
    std::lock_guard<std::mutex> lock(ledgerMutex);  // locked here
    if (amount <= 0) return;                         // unlocked here
    from.withdraw(amount);                           // if this throws, unwinding unlocks
    to.deposit(amount);
}                                                    // and unlocked here
Red flag to avoid:

Describing RAII as only about memory, or not knowing that destructors run during exception unwinding.

They may ask next:
  • How would you write an RAII wrapper for a C-style file handle?
  • What happens to RAII cleanup if the program calls std::exit or std::abort?
Say it in 60 seconds
Hard Situational round Mid-level, Senior Practice question

8. A crash happens only in the optimised release build. The debug build runs fine every time. What do you suspect, and how do you track it down?

What the interviewer is really testing:
Whether you think of undefined behaviour first, and have a calm, tool-based plan rather than blaming the compiler.
Answer frame:

Suspect: undefined behaviour that the optimiser exploits, such as uninitialised variables, out-of-bounds access, use-after-free or data races.

Tools: build with sanitizers, turn up warnings, and debug the release build with symbols.

Narrow: reproduce reliably, then bisect with a smaller input or recent commits.

Sample spoken answer:

"My first suspicion is undefined behaviour, not a compiler bug. Debug builds often hide it: memory gets filled with patterns, variables stay on the stack, and code runs in the order it's written. The optimiser is allowed to assume undefined behaviour never happens, so things like an uninitialised variable, reading past the end of an array, using freed memory, signed overflow in a loop or a data race between threads can suddenly break. So I'd get a reliable reproduction, then build with optimisation plus AddressSanitizer and UndefinedBehaviorSanitizer, and ThreadSanitizer if threads are involved. I'd turn warnings up and read them. I'd also build the release configuration with debug symbols so I can see a real stack trace from the crash. If that still doesn't find it, I'd bisect recent commits. Only after all of that would I suspect the compiler itself, and I'd want a tiny reproducer before claiming it."

Red flag to avoid:

Blaming the compiler first, or shipping the debug build to production to make the problem go away.

They may ask next:
  • Can you give an example of undefined behaviour that the optimiser turns into a skipped check or an endless loop?
  • Why can adding a print statement make the crash disappear?
Say it in 60 seconds

Classes & Lifetime 5 questions

Easy Technical round Fresher, Mid-level Practice question

9. What is a constructor's member initialiser list, why use it instead of assigning in the body, and what does explicit do?

What the interviewer is really testing:
Whether you know how an object is actually built, when an initialiser list is required, and the declaration-order trap that causes reads of uninitialised members.
Answer frame:

Initialiser list: builds each member directly with its value, before the constructor body runs.

Required for: const members, reference members, and members or bases with no default constructor.

Order: members are initialised in the order they are declared in the class, not the order in the list.

explicit: stops a one-argument constructor from being used for silent conversions.

Sample spoken answer:

"The initialiser list is the part after the colon. It constructs each member directly with its value. If I assign in the body instead, each member is first default-constructed and then assigned, which is wasted work for something like a string, and it doesn't work at all for const members, reference members, or members with no default constructor. One trap: members are always initialised in the order they're declared in the class, not the order I write them in the list. So if one initialiser reads a member that's declared later, it reads an uninitialised value. Compilers warn about a mismatched order, and I keep that warning on. explicit goes on constructors that take one argument, so the compiler won't use them for implicit conversions. Without it, a function that takes a Temperature would quietly accept a plain double, and that kind of bug is easy to miss in review."

Code:
class Temperature {
public:
    explicit Temperature(double celsius) : celsius_(celsius) {}
private:
    double celsius_;
};

void setTarget(Temperature t);
// setTarget(21.5);            // error: constructor is explicit
setTarget(Temperature{21.5});  // fine

struct Window {
    int width_;
    int area_;
    int height_;
    Window(int w, int h) : width_(w), height_(h), area_(width_ * height_) {}
    // bug: area_ is declared before height_, so it is built first
    // and reads height_ before it has a value
};
Red flag to avoid:

Believing members are initialised in the order written in the list, or that assigning in the body is the same as initialising.

They may ask next:
  • What is a delegating constructor, and when would you use one?
  • When would you deliberately leave a one-argument constructor without explicit?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level, Senior Practice question

10. Explain the rule of three, the rule of five and the rule of zero. Which one do you aim for in new code?

What the interviewer is really testing:
Whether you know how the special member functions depend on each other, and whether you design classes so you rarely need to write them.
Answer frame:

Three: if a class needs a custom destructor, copy constructor or copy assignment, it almost always needs all three.

Five: since C++11, add the move constructor and move assignment, or moves quietly fall back to copies.

Zero: hold resources in members that manage themselves, and write none of the five.

Sample spoken answer:

"The rule of three says that if a class needs a hand-written destructor, copy constructor or copy assignment, it almost certainly needs all three, because it's managing a resource directly. If you write only the destructor, the compiler's copy does a shallow copy of the pointer and you get a double delete. C++11 added move construction and move assignment, which makes it the rule of five. A detail people miss is that declaring a destructor or a copy operation stops the compiler from generating the move operations, so moves silently become copies. In new code I aim for the rule of zero. I put resources into members that already manage themselves, like std::vector, std::string or std::unique_ptr, and then the compiler-generated versions are correct and I write none of them. Only a small, dedicated resource-wrapper class should ever need the full five."

Red flag to avoid:

Listing the rules without knowing why the default copy is wrong for an owning raw pointer.

They may ask next:
  • If a class holds a unique_ptr member, can it still be copied? What does the compiler do?
  • What does = default and = delete on a special member function tell a reader?
Say it in 60 seconds
Medium Coding round Fresher, Mid-level Practice question

11. Your class owns a raw heap array. Write the copy constructor and copy assignment so that two copies never share the same buffer.

What the interviewer is really testing:
Whether you can write a correct deep copy that handles self-assignment and does not leak or double free if an allocation throws.
Answer frame:

Problem: the default copy copies the pointer, so both objects delete the same array.

Copy constructor: allocate a new array and copy the elements across.

Assignment: copy-and-swap handles self-assignment and gives the strong exception guarantee.

Sample spoken answer:

"The default copy just copies the pointer, so two objects would own one array and both destructors would delete it. That's a double free. The copy constructor allocates its own array of the same size and copies the elements. For assignment I use copy-and-swap: the parameter is taken by value, so the copy constructor does the allocation, then I swap my contents with the parameter's and let its destructor free my old array. It handles self-assignment for free and it's exception safe: if the allocation throws, it throws before my object has changed at all. The swap is marked noexcept, because the whole guarantee rests on that last step never failing. In real code I'd just use std::vector and write none of this, but it's a good test of whether you understand ownership."

Code:
class Buffer {
public:
    explicit Buffer(std::size_t n) : size_(n), data_(new int[n]{}) {}
    ~Buffer() { delete[] data_; }

    Buffer(const Buffer& other)
        : size_(other.size_), data_(new int[other.size_]) {
        std::copy(other.data_, other.data_ + size_, data_);
    }

    Buffer& operator=(Buffer other) {  // copy made by the constructor
        swap(other);
        return *this;                  // old buffer freed with other
    }

    void swap(Buffer& other) noexcept {
        std::swap(size_, other.size_);
        std::swap(data_, other.data_);
    }

private:
    std::size_t size_;
    int* data_;
};
Red flag to avoid:

Writing an assignment operator that deletes its own data before copying, which breaks on self-assignment and leaves a dangling pointer if new throws.

They may ask next:
  • What goes wrong in a naive assignment operator that deletes the old array first and then copies?
  • How would you add move support to this class?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level, Senior Practice question

12. What's an rvalue reference, and what does std::move actually do? Does calling it move anything?

What the interviewer is really testing:
Whether you understand that std::move is only a cast, and know the state a moved-from object is left in.
Answer frame:

Values: an lvalue has an identity you can refer to again; an rvalue is typically a temporary about to disappear.

std::move: a cast to an rvalue reference; it moves nothing by itself.

The move: happens when the cast result picks a move constructor or move assignment.

Sample spoken answer:

"An rvalue reference, written T&&, binds to things that are about to go away, like temporaries, which lets a class write a move constructor that steals their resources instead of copying them. std::move doesn't move anything. It's just a cast that says 'treat this as an rvalue'. The actual move happens only if that cast makes overload resolution pick a move constructor or move assignment. If the type has no move operations, you get a copy. If the object is const, you also get a copy, because a const rvalue can't bind to a non-const T&&. After a move, standard library objects are left valid but in an unspecified state, so I can assign to them or destroy them but shouldn't read their value. One more trap: a named rvalue reference parameter is itself an lvalue, so inside the function you need std::move again to pass it on as an rvalue."

Code:
std::string a = "long text that lives on the heap";
std::string b = std::move(a);  // move constructor runs here
// a is valid but unspecified: assign before reading it

const std::string c = "x";
std::string d = std::move(c);  // copies: c is const
Red flag to avoid:

Believing std::move performs the move itself, or reading a moved-from object as if it kept its value.

They may ask next:
  • Should you write return std::move(local) at the end of a function?
  • What is std::forward for, and how is it different from std::move?
Say it in 60 seconds
Hard Coding round Mid-level, Senior Practice question

13. Write a move constructor and move assignment for a class that owns a heap buffer. Why does marking them noexcept matter for std::vector?

What the interviewer is really testing:
Whether you can steal resources safely, leave the source valid, and know how noexcept changes what containers do when they grow.
Answer frame:

Steal: take the other object's pointer and size, then set its pointer to null so its destructor is harmless.

Assign: free your own buffer, take the other's, and guard against self-move.

noexcept: when a vector grows, it moves elements only if the move cannot throw; otherwise it copies.

Sample spoken answer:

"The move constructor takes the other object's pointer and size and leaves the other one empty, with a null pointer, so its destructor does nothing harmful. std::exchange makes that neat. Move assignment does the same, but first frees my own buffer, and I guard against moving an object into itself. The noexcept part really matters. When a vector runs out of capacity, it allocates a bigger array and transfers the elements. It promises that if something throws halfway through, the original vector is unchanged. It can only keep that promise with moves if the move can't throw, because a half-finished move can't be undone. So if the move constructor isn't marked noexcept and the type can be copied, the vector copies every element on growth instead. That's a silent performance hit I've seen in real code. Since these moves only swap pointers, they genuinely can't throw, so marking them noexcept is honest."

Code:
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;
}
Red flag to avoid:

Copying the pointer without nulling the source, which leads to a double delete.

They may ask next:
  • How would you check at compile time that your type's move constructor is noexcept?
  • What should a moved-from Buffer be allowed to do afterwards?
Say it in 60 seconds

Polymorphism 6 questions

Easy Technical round Fresher, Mid-level Practice question

14. Why does a base class used polymorphically need a virtual destructor? What actually goes wrong without one?

What the interviewer is really testing:
Whether you know the rule and the reason: deleting a derived object through a base pointer without a virtual destructor is undefined behaviour.
Answer frame:

Scenario: a derived object is deleted through a pointer to its base class.

Without virtual: undefined behaviour; in practice the derived destructor usually never runs.

Rule: a base meant for polymorphic delete gets a public virtual destructor, otherwise a protected non-virtual one.

Sample spoken answer:

"If I have Base* p = new Derived and later call delete p, the compiler needs to know the real type to destroy it properly. With a virtual destructor, the call goes through virtual dispatch, the Derived destructor runs first, then the Base one. Without it, the behaviour is undefined by the standard. What usually happens is that only the Base destructor runs, so anything Derived owns, like a file, a buffer or a socket, never gets released. The same applies to a unique_ptr of Base holding a Derived. So my rule is: if a class has any virtual functions and is meant to be deleted through a base pointer, it gets a public virtual destructor, usually just virtual ~Base() = default. If a base isn't meant to be deleted that way, I make its destructor protected and non-virtual so the mistake won't compile."

Red flag to avoid:

Saying the missing virtual destructor just leaks a little memory, instead of recognising it as undefined behaviour.

They may ask next:
  • Does shared_ptr of Base created from make_shared of Derived have the same problem?
  • Why don't the standard containers like std::vector have virtual destructors?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

15. How are virtual function calls usually implemented under the hood? Talk me through vtables and what they cost.

What the interviewer is really testing:
Whether you have a working mental model of dynamic dispatch and can reason about its memory and speed cost without exaggerating it.
Answer frame:

Table: each polymorphic class typically has one table of pointers to its virtual function implementations.

Pointer: each object carries a hidden pointer to its class table, set during construction.

Cost: an extra pointer per object, an indirect call, and usually no inlining unless the compiler can devirtualise.

Sample spoken answer:

"The standard doesn't say how, but every major compiler uses the same idea. For each class with virtual functions, the compiler builds a table of function pointers, one slot per virtual function, pointing at that class's version. Each object gets a hidden pointer to its class's table, set by the constructor. When I call p->draw() through a base pointer, the generated code loads the object's table pointer, picks the draw slot, and calls through it. So the cost is one extra pointer in every object, a couple of loads and an indirect call, and more importantly the compiler usually can't inline the call. In most code that's irrelevant. In a tight loop over millions of small objects it can matter, and then I look at marking classes final so the compiler can devirtualise, or at grouping objects by type."

Red flag to avoid:

Claiming there is one vtable per object, or that virtual calls are too slow to use in general.

They may ask next:
  • What changes about vtables with multiple inheritance?
  • When can the compiler call a virtual function directly without going through the table?
Say it in 60 seconds
Easy Technical round Fresher, Mid-level Practice question

16. How does overloading a function differ from overriding one in C++, and what does the override keyword protect you from?

What the interviewer is really testing:
Whether you separate compile-time overload resolution from runtime virtual dispatch, and know the silent mistakes override catches.
Answer frame:

Overloading: same name, different parameters, chosen by the compiler from the argument types.

Overriding: a derived class replaces a virtual function with the same signature, chosen at runtime by the real type.

override: makes the compiler check that you really override something, catching signature typos.

Sample spoken answer:

"Overloading is several functions with the same name but different parameter lists, and the compiler picks one at compile time from the argument types. Overriding is when a derived class provides its own version of a base class virtual function with the same signature, and the choice happens at runtime based on the object's real type. The override keyword matters because without it a tiny mismatch, like a missing const or an int where the base takes a long, silently creates a new function instead of overriding. The code compiles, and the base version gets called at runtime. With override, that's a compile error. There's also a related trap called name hiding: if the derived class declares any function named draw, it hides all the base class overloads of draw, and you need a using declaration to bring them back."

Code:
struct Shape {
    virtual void draw(int scale) const;
    virtual ~Shape() = default;
};
struct Circle : Shape {
    void draw(int scale) override;        // error: base version is const
    void draw(int scale) const override;  // correct
};
Red flag to avoid:

Saying overriding is resolved at compile time, or not knowing that a signature mismatch silently fails to override.

They may ask next:
  • What does the final keyword do on a class and on a virtual function?
  • How do default arguments behave on a virtual function called through a base pointer?
Say it in 60 seconds
Easy Technical round Fresher, Mid-level Practice question

17. C++ has no interface keyword. How do you define an interface, and what makes a class abstract?

What the interviewer is really testing:
Whether you know pure virtual functions, what abstract means in practice, and how a clean interface class is written.
Answer frame:

Pure virtual: a function declared with = 0; any class with one is abstract and cannot be instantiated.

Concrete: a derived class becomes instantiable only once it overrides every pure virtual function.

Interface: a class with only pure virtual functions and a virtual destructor.

Sample spoken answer:

"A class is abstract if it has at least one pure virtual function, which is a virtual function declared with = 0. You can't create an object of an abstract class, but you can have pointers and references to it. A derived class is still abstract until it overrides every pure virtual function it inherits. To get what other languages call an interface, I write a class with only pure virtual functions and a virtual destructor, no data members. Code then depends on that interface through a reference or a smart pointer, which makes it easy to swap implementations or pass a fake one in a unit test. One detail that surprises people is that a pure virtual function can still have a body, which derived classes can call explicitly, but it doesn't make the class concrete."

Code:
class Logger {
public:
    virtual ~Logger() = default;
    virtual void write(std::string_view msg) = 0;
};

class FileLogger : public Logger {
public:
    void write(std::string_view msg) override { /* append to file */ }
};
Red flag to avoid:

Thinking an abstract class cannot have any implemented functions or data, or forgetting the virtual destructor on the interface.

They may ask next:
  • Why give an interface class a virtual destructor even though it has no data?
  • How would you use an interface like this to test code that normally writes to disk?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

18. What happens if a base class constructor calls a virtual function that the derived class overrides? Why does C++ behave that way?

What the interviewer is really testing:
Whether you understand construction order and the dynamic type of an object while it is being built, a classic source of real bugs.
Answer frame:

Behaviour: during the base constructor, the call goes to the base version, not the derived one.

Reason: derived members are not constructed yet, so calling derived code would touch uninitialised state.

Pure virtual: calling a pure virtual this way is undefined behaviour, often a crash.

Sample spoken answer:

"It calls the base class version. While the base constructor is running, the object's dynamic type is the base class, because the derived part hasn't been constructed yet. That's deliberate: if C++ dispatched to the derived override, that function could read derived members that haven't been initialised, which would be far worse. The same thing happens in reverse in destructors, since by the time the base destructor runs, the derived part is already gone. If the function is pure virtual in the base, calling it from the constructor is undefined behaviour, and many implementations abort with a 'pure virtual function called' message. When I need derived-specific setup, I either pass the needed values into the base constructor as arguments, or I use a factory function that constructs the object fully and then calls an init step."

Red flag to avoid:

Saying the derived override runs, as it would in some other languages.

They may ask next:
  • How would you design a factory so nobody can use the object before its init step runs?
  • Would a static analyser or compiler warning catch this? What would you enable?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

19. What is object slicing? Show me a way it sneaks into ordinary code and how you avoid it.

What the interviewer is really testing:
Whether you understand value semantics well enough to spot why polymorphism quietly stops working when objects are copied by value.
Answer frame:

What: copying a derived object into a base object keeps only the base part.

Where: passing by value to a base parameter, or storing in a container of base objects.

Fix: pass by reference, store smart pointers to the base, or delete copying on polymorphic bases.

Sample spoken answer:

"Slicing happens when a derived object is copied into a variable of the base type. Only the base part is copied, the derived data is cut off, and the new object really is a Base, so virtual calls on it go to the base versions. The common places it sneaks in are a function that takes Shape by value instead of by reference, and a std::vector of Shape where people push Circles. Everything compiles, but every draw call does the base behaviour. The fixes are to pass polymorphic objects by reference or pointer, and to store them as std::vector of std::unique_ptr of Shape. On polymorphic base classes I often delete or protect the copy operations so that an accidental by-value copy doesn't compile at all. Catching exceptions by value is another place it bites, which is why I always catch by const reference."

Code:
void render(Shape s);            // slices: always calls Shape::draw
void render(const Shape& s);     // keeps the real type

std::vector<Shape> a;            // slices every Circle pushed in
std::vector<std::unique_ptr<Shape>> b;  // keeps polymorphism
Red flag to avoid:

Not recognising that a vector of base objects cannot hold derived objects polymorphically.

They may ask next:
  • Why is catching an exception by value a slicing risk?
  • If a polymorphic object really needs copying, how do you do it safely?
Say it in 60 seconds

Smart Pointers 4 questions

Easy Technical round Fresher, Mid-level Practice question

20. When do you use unique_ptr and when shared_ptr? Why do many teams make unique_ptr the default?

What the interviewer is really testing:
Whether you think about ownership first and know the real costs of shared ownership.
Answer frame:

unique_ptr: one owner, move-only, frees the object when the owner goes away; no reference count.

shared_ptr: many owners, a reference count in a control block, freed when the last owner goes.

Default: start unique; move to shared only when ownership is truly shared.

Sample spoken answer:

"unique_ptr means exactly one owner. It can't be copied, only moved, and when it goes out of scope the object is deleted. With the default deleter it costs about the same as a raw pointer. shared_ptr is for when several parts of the program really share ownership and nobody knows who'll be last. It keeps a reference count in a separate control block, and copying it updates that count atomically, so there's a real cost in memory and in every copy. I default to unique_ptr because it makes ownership obvious from the type, it's cheaper, and it's easy to turn into a shared_ptr later if needed, while going the other way isn't possible. Most of the time, a function that just uses an object shouldn't take a smart pointer at all. It takes a reference or a raw pointer, because it's only borrowing."

Code:
auto conn = std::make_unique<Connection>(host);   // sole owner
pool.add(std::move(conn));                          // ownership moves

auto cfg = std::make_shared<Config>(load());        // read by many workers
for (auto& w : workers) w.setConfig(cfg);           // each holds a copy
Red flag to avoid:

Using shared_ptr everywhere by default, or saying unique_ptr can be copied.

They may ask next:
  • How should a function take a smart pointer if it only needs to read the object?
  • Is shared_ptr thread-safe? What exactly is safe and what is not?
Say it in 60 seconds
Medium Technical round Mid-level, Senior Practice question

21. What problem does weak_ptr solve? Show me a reference cycle between shared_ptrs and how you break it.

What the interviewer is really testing:
Whether you know that shared_ptr leaks on cycles and can use weak_ptr correctly, including checking that the object still exists.
Answer frame:

Cycle: two objects hold shared_ptrs to each other, so neither count ever reaches zero.

weak_ptr: observes without owning; it does not keep the object alive.

Use: call lock() to get a shared_ptr, which is empty if the object is already gone.

Sample spoken answer:

"shared_ptr frees an object when its count reaches zero. If a parent holds a shared_ptr to its child and the child holds a shared_ptr back to its parent, then even when the rest of the program drops them, each keeps the other's count at one and both leak. weak_ptr fixes that. It points at an object managed by shared_ptr without adding to its owner count. So I decide which direction is ownership, here parent owns child, and make the back link a weak_ptr. To use it, I call lock(), which gives me a shared_ptr if the object still exists or an empty one if it's gone, and I check that before using it. It's also useful for caches and observer lists, where I want to know about an object without keeping it alive."

Code:
struct Node {
    std::vector<std::shared_ptr<Node>> children;  // owns children
    std::weak_ptr<Node> parent;                   // does not own parent
};

void visitParent(const Node& n) {
    if (auto p = n.parent.lock()) {   // shared_ptr or empty
        p->touch();
    }
}
Red flag to avoid:

Using a weak_ptr without lock() or without checking the result, or not knowing that shared_ptr cycles leak.

They may ask next:
  • Why is checking expired() and then calling lock() a race in threaded code?
  • How do you get a shared_ptr to this from inside a member function safely?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

22. Why is std::make_shared usually preferred over shared_ptr<T>(new T)? Is there any case where it is the worse choice?

What the interviewer is really testing:
Whether you understand the control block and allocation behaviour, not just the style advice.
Answer frame:

One allocation: make_shared puts the object and the control block in one block of memory.

Safety: no bare new in the code; in older standards this also avoided a leak in function arguments.

Downsides: no custom deleter, and the memory stays allocated until the last weak_ptr is gone.

Sample spoken answer:

"shared_ptr needs a control block for the counts. With shared_ptr<T>(new T), that's two allocations: one for the object, one for the control block. make_shared does a single allocation holding both, which is faster and friendlier to the cache. It also means there's no bare new in the code, and before C++17 it avoided a real leak when a shared_ptr was built inside a function call alongside another argument that could throw. There are cases where it's worse. You can't give make_shared a custom deleter. And because the object and control block share one allocation, the object is destroyed when the last shared_ptr goes, but the memory isn't returned until the last weak_ptr goes too. So for a large object watched by long-lived weak_ptrs, the separate allocation can be the better choice."

Red flag to avoid:

Saying the only difference is shorter syntax.

They may ask next:
  • What exactly is stored in the control block?
  • Why can make_shared not call a private constructor, and how would you work around it?
Say it in 60 seconds
Medium Situational round Mid-level, Senior Practice question

23. A teammate suggests making every pointer in the codebase a shared_ptr so nobody ever has to think about ownership again. How do you respond?

What the interviewer is really testing:
Whether you can push back with clear reasons and a practical alternative, while respecting the real problem your teammate is trying to solve.
Answer frame:

Agree on the goal: fewer leaks and fewer ownership bugs is the right aim.

Costs: atomic reference counting, hidden lifetimes, cycles that leak, and code that no longer says who owns what.

Alternative: unique_ptr by default, shared_ptr where ownership is truly shared, references for borrowing.

Sample spoken answer:

"I'd start by agreeing with the goal, because raw owning pointers really do cause leaks and crashes. But I'd explain that shared_ptr everywhere doesn't remove the need to think about ownership, it hides it. Every copy updates an atomic count, which costs something in hot paths. Objects live as long as anyone holds a copy, so lifetimes become hard to predict, which is bad for things like file handles or locks. And any two objects pointing at each other form a cycle that leaks, which is exactly the bug we were trying to kill. I'd suggest a simple rule instead: unique_ptr by default, shared_ptr only when several owners genuinely share the object and we can say why, and plain references or raw pointers for functions that only borrow. I'd offer to write that up as a short guideline and pair on converting one module so we can compare."

Red flag to avoid:

Either agreeing without thinking about cost and cycles, or dismissing the teammate without offering a better rule.

They may ask next:
  • What would convince you that a specific object really does need shared ownership?
  • How would you enforce the guideline without slowing every code review?
Say it in 60 seconds

Templates 2 questions

Medium Technical round Fresher, Mid-level Practice question

24. How do templates work at compile time, and why do template definitions usually have to live in header files?

What the interviewer is really testing:
Whether you understand instantiation well enough to explain linker errors, compile times and code size.
Answer frame:

Instantiation: the compiler generates real code for each set of template arguments that is used.

Visibility: to do that it needs the full definition, not just a declaration, in the file being compiled.

Costs: longer builds, larger binaries and long error messages; explicit instantiation and concepts help.

Sample spoken answer:

"A template isn't code by itself, it's a recipe. When I use max<int> or vector<Order>, the compiler instantiates the template for those arguments and generates real functions or classes. To do that it needs to see the whole definition in the file it's compiling. If I put a template's definition in a .cpp file and only the declaration in the header, other files compile fine but nothing generates the code for their types, so the build fails at link time with undefined symbols. That's why templates live in headers. There's an alternative, explicit instantiation, where the .cpp lists the exact types to generate, which works when the set of types is known. The costs of templates are compile time, since every file that uses them does the work, larger binaries from many instantiations, and hard error messages, which C++20 concepts improve by stating requirements up front."

Red flag to avoid:

Describing templates as working like runtime generics with type erasure, or not being able to explain the linker error.

They may ask next:
  • Why does each file instantiating the same template not cause a duplicate symbol error at link time?
  • What does a C++20 concept give you over an unconstrained template parameter?
Say it in 60 seconds
Medium Coding round Mid-level, Senior Practice question

25. What is template specialization? Show how you would let a custom struct be used as the key of an unordered_map.

What the interviewer is really testing:
Whether you can use full specialization for a practical job and know what an unordered container requires of its key.
Answer frame:

Full specialization: a separate definition of a template for one exact set of arguments.

Partial specialization: allowed for class templates only; function templates use overloading instead.

Hash keys: an unordered_map key needs a hash and an equality check; specialising std::hash provides the hash.

Sample spoken answer:

"Specialization lets me give a template a different definition for particular arguments. A full specialization covers one exact type. A partial specialization covers a pattern, like any pointer type, but that's only allowed for class templates. For function templates I use an overload instead. A very common real use is hashing. unordered_map needs to hash its keys and compare them for equality. For my own struct there's no hash, so I either specialise std::hash for it, which the standard allows for user-defined types, or pass a hasher as a template argument to the map. I also need operator== so the map can tell apart keys that hash to the same bucket. The hash doesn't need to be perfect, but it should spread keys well, because a bad hash puts everything into a few buckets and lookups slow down towards linear."

Code:
struct Point {
    int x, y;
    bool operator==(const Point& o) const { return x == o.x && y == o.y; }
};

namespace std {
template <>
struct hash<Point> {
    size_t operator()(const Point& p) const noexcept {
        size_t h = hash<int>{}(p.x);
        return h ^ (hash<int>{}(p.y) + 0x9e3779b9 + (h << 6) + (h >> 2));
    }
};
}

std::unordered_map<Point, std::string> labels;
Red flag to avoid:

Forgetting equality, or claiming function templates can be partially specialised.

They may ask next:
  • Why is a hash that just adds x and y a poor choice for grid points?
  • What would you need instead to use Point as the key of a std::map?
Say it in 60 seconds

STL 3 questions

Medium Technical round Fresher, Mid-level, Senior Practice question

26. How does std::vector grow, and which operations invalidate iterators, pointers and references to its elements?

What the interviewer is really testing:
Whether you understand size versus capacity and can predict when a held iterator or pointer silently becomes invalid.
Answer frame:

Growth: contiguous storage; when size reaches capacity it allocates a bigger block and moves the elements.

Reallocation: invalidates every iterator, pointer and reference into the vector.

Insert and erase: without reallocation, invalidate only from the changed position to the end.

Sample spoken answer:

"A vector keeps its elements in one contiguous block, with a size and a capacity. When push_back finds size equal to capacity, it allocates a bigger block, usually growing by a multiple so the average cost of push_back stays constant, moves or copies the elements across and frees the old block. At that point every iterator, pointer and reference into the vector is invalid, because the elements live somewhere else now. If there's no reallocation, insert and erase invalidate things only at and after the position you changed, since those elements shift. The classic bug is taking a pointer to an element, pushing more items, and then using the pointer. If I know the final size, reserve avoids the reallocations, and if I need stable addresses while adding, I store indexes, or use std::list, or std::deque if I only add at the ends, since that keeps references to existing elements valid."

Red flag to avoid:

Assuming a pointer to a vector element stays valid after push_back.

They may ask next:
  • Does clear() give the memory back? What would you do if you needed it to?
  • Which operations invalidate iterators in std::unordered_map?
Say it in 60 seconds
Medium Coding round Fresher, Mid-level Practice question

27. Remove every negative number from a std::vector<int> in place. Show the loop version and the idiomatic version, and say why the naive loop crashes.

What the interviewer is really testing:
Whether you know that erase invalidates the iterator you are holding, and whether you know the standard one-pass idiom.
Answer frame:

Naive bug: after erase(it), it is invalid, so ++it is undefined behaviour.

Loop fix: use the iterator erase returns and only advance when nothing was erased.

Idiom: erase-remove with remove_if, or std::erase_if in C++20, in one pass.

Sample spoken answer:

"The naive loop erases at it and then does ++it, but erase invalidates it, so incrementing is undefined behaviour. It often skips elements or runs past the end. The fix in a loop is to use the return value of erase, which is an iterator to the element after the one removed, and only increment when I didn't erase. That's correct but slow for a vector, because every erase shifts the rest of the elements, so it can be quadratic. The idiomatic version is erase-remove. std::remove_if walks once, moves the elements I want to keep to the front and returns the new logical end, then one erase call chops off the tail. That's a single linear pass. In C++20 std::erase_if wraps the whole thing in one call, and it's what I'd write today."

Code:
// Loop: correct, but can be quadratic on a vector
for (auto it = v.begin(); it != v.end(); ) {
    if (*it < 0) it = v.erase(it);
    else ++it;
}

// Erase-remove: one linear pass
v.erase(std::remove_if(v.begin(), v.end(),
                       [](int x) { return x < 0; }),
        v.end());

// C++20
std::erase_if(v, [](int x) { return x < 0; });
Red flag to avoid:

Calling remove_if without erase and believing the vector got shorter.

They may ask next:
  • What is left in the vector between the new end and the old end after remove_if, before you call erase?
  • How does the loop version change for a std::map?
Say it in 60 seconds
Easy Technical round Fresher, Mid-level Practice question

28. std::map or std::unordered_map: how are they different inside, and how do you decide which one to use?

What the interviewer is really testing:
Whether you choose containers on ordering needs and real access patterns, and know the worst case of a hash table.
Answer frame:

map: a balanced search tree, keys kept sorted, logarithmic lookup, needs a less-than comparison.

unordered_map: a hash table, average constant-time lookup, worst case linear, needs hash and equality.

Choose: ordered iteration or range queries mean map; pure key lookups usually mean unordered_map.

Sample spoken answer:

"std::map is a balanced binary search tree, in practice a red-black tree. Keys stay sorted, lookups, inserts and erases are logarithmic, and I can iterate in order or ask for ranges with lower_bound and upper_bound. It needs a less-than comparison on the key. std::unordered_map is a hash table. Lookups are constant time on average, but with a bad hash or many collisions they degrade towards linear, and iteration order is unspecified and can change after a rehash. It needs a hash function and equality. So I pick map when order matters, like a price ladder sorted by price or 'give me everything between these two times', and unordered_map for pure lookups by key, like a cache by ID. For small collections or hot paths I measure, because a sorted vector is sometimes faster than both thanks to contiguous memory."

Red flag to avoid:

Saying unordered_map is always constant time, or not knowing that map keeps keys sorted.

They may ask next:
  • What happens if you use operator[] on a map to check whether a key exists?
  • How can a hash table be attacked with chosen keys, and how would you defend against it?
Say it in 60 seconds

Real Work 3 questions

Hard Behavioral round Mid-level, Senior Practice question

29. Tell me about a crash or memory leak in C++ code that took real effort to find. How did you track it down, and what did you change after?

What the interviewer is really testing:
Whether you debug with tools and evidence rather than guesses, and whether you fix the class of bug and not just the one instance.
Answer frame:

Symptom: what failed, how often, and why it was hard to reproduce.

Evidence: the tools you used, like sanitizers, a debugger, core dumps or a heap profiler.

Cause and fix: the real root cause in plain words and the change you made.

Prevention: what you changed so the same kind of bug could not come back.

Sample spoken answer:

"At my last company a service that parsed market data crashed maybe once a day, always in a different place, which usually means memory corruption. I couldn't reproduce it locally, so I built it with AddressSanitizer and replayed a recorded day of traffic. Within an hour it reported a heap use-after-free. A handler kept a raw pointer to an element inside a vector, and a later message added elements, the vector reallocated, and the handler wrote through the stale pointer. The fix was to store an index instead of a pointer. Afterwards I added a sanitizer build to our CI, ran the replay test nightly, and wrote a short guideline for the team about not holding pointers into containers that can grow. We didn't see that crash again, and the sanitizer job caught two similar bugs in review within the next few months."

Red flag to avoid:

A story that ends with adding null checks or a try/catch until the crash stopped, with no root cause.

They may ask next:
  • Why did the crash show up in a different place each time?
  • How do AddressSanitizer and Valgrind differ, and when would you pick each?
Say it in 60 seconds
Medium Behavioral round Mid-level, Senior Practice question

30. Have you modernised older C++ code, for example raw new and delete to smart pointers or an old standard to a newer one? How did you keep it safe?

What the interviewer is really testing:
Whether you can improve a risky codebase in small, tested steps instead of a big rewrite, and weigh the benefit against the risk.
Answer frame:

Why: the concrete pain, such as leaks, crashes or slow onboarding.

Approach: small steps behind tests, one module or one pattern at a time.

Tools: compiler warnings, static analysis, sanitizers and code review.

Result: what improved and what you chose not to touch.

Sample spoken answer:

"In my last role we had a large codebase full of raw new and delete, and most of our crash reports were leaks or double frees. Rather than a big rewrite, I proposed doing it one module at a time. First we raised the compiler to a newer standard with warnings turned up and fixed what that showed. Then for each module I added tests around the public behaviour, replaced owning raw pointers with unique_ptr, and left non-owning ones as raw pointers or references, so the types finally said who owned what. We ran every change through AddressSanitizer in CI. Some places had genuinely shared lifetimes and got shared_ptr, but only after we'd argued about it in review. Over a few months the leak reports mostly stopped. I deliberately left one old, stable module alone, because nothing was failing there and the risk wasn't worth it."

Red flag to avoid:

Describing a big-bang rewrite with no tests, or modernising purely for style with no problem to solve.

They may ask next:
  • How did you tell owning raw pointers apart from non-owning ones in code with no comments?
  • What would make you stop a modernisation effort partway?
Say it in 60 seconds
Medium Behavioral round Mid-level, Senior Practice question

31. Walk me through a time you made slow C++ code faster. How did you find where the time was going before you changed anything?

What the interviewer is really testing:
Whether you measure first with a profiler, and understand the usual real causes such as allocations, copies and memory layout.
Answer frame:

Baseline: a repeatable benchmark and a number before any change.

Profile: the profiler output that showed the hot spot, not a guess.

Fix: what you changed and why it helped at the machine level.

Check: the new number, and a test so it does not quietly regress.

Sample spoken answer:

"In a project that processed large sensor logs, one stage was taking minutes. First I wrote a benchmark on a fixed input so I had a number to compare. Then I ran it under perf, and the surprise was that most of the time wasn't in the maths at all. It was in memory allocation and string copies. A function took a std::string by value in a hot loop, and we built a small temporary vector for every record. I changed the parameter to a string_view, reused one vector across iterations with clear and a reserved capacity, and added a missing reserve on the output vector. The stage went from minutes to under twenty seconds. I added the benchmark to our CI with a threshold, so a later change that brought the allocations back would be caught before release."

Red flag to avoid:

Optimising by instinct, for example rewriting loops by hand, without a profile or a before-and-after number.

They may ask next:
  • When is string_view dangerous to use as a parameter or a member?
  • How do you make sure a micro-benchmark measures what you think it measures?
Say it in 60 seconds
Were you asked something else? Share it A person checks every question before it goes on the site. No name is shown.
For the call itself

The questions above are the prep. The call has ten more.

ClapAssist is an AI interview assistant for Mac and Windows. It listens to the interview on your computer and shows you what to say, in short lines you can read while you talk. Your resume and notes are never stored on our servers. It stays out of screen share on every plan; only you can see it.

Download ClapAssist with 10 free minutes
Mac and Windows · Stays out of screen share · No card