Top 50 C++ Interview Questions and Answers (Beginner to Advanced)
Technical interviews can feel like a gauntlet. You’re nervous, the interviewer is watching, and suddenly you can’t remember what a virtual function is.
This guide covers 50 real C++ interview questions—the ones you’ll actually encounter. Each question includes a clear, concise answer and practical code examples where helpful. Study these, understand the “why” behind each answer, and you’ll walk into that interview confident.
What to Expect in a C++ Technical Interview
C++ interviews typically cover:
- Fundamentals — data types, syntax, scope, compilation
- Object-Oriented Programming — classes, inheritance, polymorphism
- Memory Management — pointers, smart pointers, memory leaks
- Standard Library — STL containers, algorithms, iterators
- Advanced Topics — templates, move semantics, multithreading
- Practical Coding — you’ll write code to solve problems
Interviewers are looking for three things:
- Do you understand the language deeply?
- Can you think through problems systematically?
- Can you write clean, correct code?
This list helps with #1. Practice #2 and #3 with LeetCode or HackerRank.
Section 1: Fundamentals (Questions 1–12)
1. What are the basic data types in C++, and what are their typical sizes?
C++ has built-in types for different kinds of data. Sizes may vary by platform, but on modern 64-bit systems:
char - 1 byte (stores single character)
short - 2 bytes
int - 4 bytes
long - typically 4 bytes (but may be 8 on 64-bit)
long long - 8 bytes
float - 4 bytes (single precision)
double - 8 bytes (double precision)
bool - 1 byte (though only needs 1 bit)
Use sizeof(type) to check exact sizes on your system. Choose types based on your needs—use int for general-purpose integers, double for floating-point math, and bool for true/false values.
2. What’s the difference between int, unsigned int, and const int?
int stores signed integers (positive and negative): range approximately -2 billion to +2 billion.
unsigned int stores only non-negative integers (0 and positive): range 0 to approximately 4 billion. You gain more positive range but lose negative numbers.
const int creates an integer that cannot be modified after initialization. The const keyword is a promise to the compiler and reader that this value won’t change.
int x = 5; // Can be changed
x = 10; // OK
unsigned int y = 5;
y = 10; // OK, but y cannot be negative
const int z = 5;
z = 10; // ERROR: cannot modify const variable
3. What is scope in C++? Explain local, global, and static scope.
Scope determines where in the program a variable is accessible.
Local scope: Variables declared inside a function or block are accessible only within that block.
void myFunction() {
int x = 5; // Local to myFunction
// x is accessible here
}
// x is NOT accessible here
Global scope: Variables declared outside all functions are accessible everywhere.
int globalVar = 10; // Global scope
void myFunction() {
std::cout << globalVar; // Can access globalVar
}
Static scope: Gives a variable internal linkage (accessible only in this file) or extends its lifetime to the program’s duration.
static int counter = 0; // File-local, not visible to other translation units
4. What is the difference between const and constexpr?
Both restrict modification but serve different purposes.
const creates a constant whose value is determined at runtime and doesn’t change after initialization. The compiler doesn’t promise it’s compile-time constant.
constexpr explicitly declares that a value can be evaluated at compile-time. The compiler evaluates it during compilation if possible, resulting in zero runtime overhead.
const int x = getValue(); // Value determined at runtime
constexpr int y = 5 * 10; // Evaluated at compile-time (y = 50)
Use constexpr when you want compile-time evaluation for performance.
5. Explain the compilation process: source code to executable.
C++ code goes through several stages:
-
Preprocessing — The preprocessor handles
#include,#define, and removes comments. Result: modified source code. -
Compilation — The compiler converts the preprocessed code to assembly code, checking syntax and semantics.
-
Assembly — The assembler converts assembly to object code (.o or .obj files).
-
Linking — The linker combines multiple object files and resolves external references. Creates the final executable.
Source.cpp → [Preprocessor] → [Compiler] → [Assembler] → .obj
↓
[Linker] → a.exe
Library.obj → (fed into linker)
Understanding this helps debug linking errors and understanding why circular includes cause problems.
6. What is the difference between declaration and definition?
A declaration tells the compiler that a name exists and what type it is. It allocates no memory for variables.
A definition is a declaration that allocates memory and, for variables, may assign initial values.
extern int x; // Declaration (extern keyword says "defined elsewhere")
int x = 5; // Definition
void foo(); // Declaration (function declaration, no body)
void foo() { } // Definition (includes the body)
For variables, you can declare multiple times but define only once. This is why header files use extern for global variables.
7. What is a header file, and why do we use #include?
A header file (.h or .hpp) contains declarations (function prototypes, class definitions, constants). The #include directive pastes the entire header into your source file.
// mylib.h
#ifndef MYLIB_H
#define MYLIB_H
void greet(std::string name);
#endif
// main.cpp
#include "mylib.h" // Include declarations
int main() {
greet("Alice"); // Compiler knows greet() exists and its signature
}
Why? Separation of interface and implementation. Multiple source files can use the same library without rewriting declarations. The #ifndef guard prevents multiple inclusion.
8. What are preprocessor directives? Give examples.
Preprocessor directives start with # and are processed before compilation.
#include <iostream> // Include standard library header
#include "myheader.h" // Include local header
#define MAX 100 // Define a macro (text replacement)
#define SQUARE(x) ((x)*(x)) // Macro with arguments
#ifdef DEBUG // Conditional compilation
std::cout << "Debug mode\n";
#endif
#pragma once // Prevents multiple inclusion (non-standard but widely supported)
9. Explain the difference between C and C++.
C is a procedural language. It focuses on functions and data structures. No built-in object orientation, no exceptions, simpler memory model.
C++ extends C with object-oriented features: classes, inheritance, polymorphism, exceptions, templates, standard library containers. C++ is backward compatible with C (mostly).
// C approach
struct Point {
int x, y;
};
void printPoint(Point* p) {
printf("(%d, %d)\n", p->x, p->y);
}
// C++ approach (using OOP)
class Point {
public:
void print() const {
std::cout << "(" << x << ", " << y << ")\n";
}
private:
int x, y;
};
10. What is the main() function? Why must every program have one?
main() is the entry point of a C++ program. When you run an executable, the operating system calls main().
int main() {
// Program code
return 0; // Return 0 to indicate success
}
The return value tells the operating system if the program succeeded (0) or failed (non-zero). Every program needs main() because without it, the OS doesn’t know where to start execution.
11. What are the access modifiers in C++?
Access modifiers control visibility: public, private, protected.
- public: Accessible from anywhere
- private: Accessible only from within the class
- protected: Accessible from within the class and derived classes
class MyClass {
public:
int publicVar; // Accessible everywhere
private:
int privateVar; // Only within MyClass
protected:
int protectedVar; // Within MyClass and derived classes
};
Default access in a class is private; in a struct, it’s public.
12. What is a namespace? Why use them?
A namespace is a named scope for organizing code. It prevents naming conflicts and organizes related functionality.
namespace math {
int add(int a, int b) { return a + b; }
}
namespace string_utils {
std::string add(std::string a, std::string b) { return a + b; }
}
int main() {
math::add(2, 3); // Calls math::add
string_utils::add("a", "b"); // Calls string_utils::add
}
The std:: namespace contains all standard library components. Use namespaces to organize your code and avoid conflicts in large projects.
Section 2: Object-Oriented Programming (Questions 13–24)
13. What is a class? How does it differ from a struct?
A class is a blueprint for creating objects. It bundles data (member variables) and functions (methods) together.
class Dog {
private:
std::string name;
int age;
public:
void bark() { std::cout << "Woof!\n"; }
};
The only difference between class and struct in C++ is default access: classes default to private, structs to public. Functionally, they’re identical. Use class for objects with behavior; use struct for simple data containers.
14. What are constructors and destructors?
A constructor is called when an object is created. It initializes member variables and allocates resources.
A destructor is called when an object is destroyed. It cleans up resources (especially important for dynamic memory).
class Dog {
public:
Dog(std::string name) : name(name) { // Constructor
std::cout << "Dog " << name << " created\n";
}
~Dog() { // Destructor
std::cout << "Dog destroyed\n";
}
private:
std::string name;
};
The constructor uses member initializer list (: name(name)) to initialize members. Destructors are crucial for managing resources (RAII principle).
15. What is an initializer list? Why use it?
An initializer list (: member1(value1), member2(value2)) initializes member variables directly, avoiding unnecessary temporary objects.
class Point {
public:
Point(int x, int y) : x(x), y(y) { } // Initializer list
private:
int x, y;
};
Without the initializer list, members would be default-initialized first, then assigned. For complex types (like std::string), this creates temporary objects, wasting time. Initializer lists are more efficient and required for const and reference members.
16. What is inheritance? Give an example.
Inheritance allows a class (derived) to inherit properties and methods from another class (base). It enables code reuse and hierarchy.
class Animal { // Base class
public:
virtual void speak() { std::cout << "Some sound\n"; }
};
class Dog : public Animal { // Derived class
public:
void speak() override { std::cout << "Woof!\n"; } // Override
};
Dog inherits from Animal, meaning Dog has all of Animal’s public members and can override methods. The public keyword specifies inheritance type: public inheritance means “is-a” relationship.
17. What is the difference between public, private, and protected inheritance?
- public inheritance: Base class’s public members remain public in derived class. Creates “is-a” relationship. This is the most common.
- private inheritance: Base class’s public members become private in derived class. Used rarely, for “implemented in terms of.”
- protected inheritance: Base class’s public members become protected in derived class. Used for intermediate base classes.
class Base { };
class DerivedPublic : public Base { }; // is-a
class DerivedPrivate : private Base { }; // implemented in terms of
class DerivedProtected : protected Base { }; // for internal hierarchies
Most of the time, use public inheritance.
18. What is polymorphism? Explain compile-time vs. runtime polymorphism.
Polymorphism means “many forms.” A single interface can work with multiple types.
Compile-time polymorphism (static): The compiler determines which function to call. Example: function overloading and templates.
void print(int x) { std::cout << x << "\n"; }
void print(std::string s) { std::cout << s << "\n"; }
print(5); // Calls print(int)
print("hello"); // Calls print(std::string)
Runtime polymorphism (dynamic): Determined at runtime using virtual functions and inheritance.
class Animal { public: virtual void speak() { } };
class Dog : public Animal { public: void speak() override { std::cout << "Woof\n"; } };
Animal* a = new Dog();
a->speak(); // Calls Dog::speak() at runtime
Use runtime polymorphism when behavior depends on object type. Use compile-time for flexibility without performance overhead.
19. What are virtual functions? Why do we need them?
A virtual function is a function that can be overridden by derived classes. The actual function called is determined at runtime based on the object’s actual type, not the pointer type.
class Animal {
public:
virtual void speak() { std::cout << "Some sound\n"; }
};
class Dog : public Animal {
public:
void speak() override { std::cout << "Woof\n"; }
};
int main() {
Animal* a = new Dog();
a->speak(); // Calls Dog::speak(), not Animal::speak()
}
Without virtual, calling a function through a base class pointer would call the base class version. Virtual functions enable polymorphic behavior: the same interface handles multiple types correctly.
20. What is a pure virtual function? What is an abstract class?
A pure virtual function has no implementation in the base class. It’s declared with = 0.
class Shape {
public:
virtual void draw() = 0; // Pure virtual: must be overridden
};
An abstract class contains at least one pure virtual function. You cannot instantiate an abstract class; you can only derive from it. The derived class must implement all pure virtual functions to become concrete.
class Shape { public: virtual void draw() = 0; }; // Abstract
class Circle : public Shape { public: void draw() override { } }; // Concrete
Shape s; // ERROR: cannot instantiate abstract class
Circle c; // OK
Abstract classes define interfaces and force derived classes to implement specific behavior.
21. What is the difference between overloading and overriding?
Overloading (compile-time): Multiple functions with the same name but different parameters.
void print(int x);
void print(std::string s);
void print(double d);
Overriding (runtime): A derived class provides a different implementation of a base class’s virtual function.
class Base { public: virtual void foo() { } };
class Derived : public Base { public: void foo() override { } };
Overloading is resolved at compile time based on argument types. Overriding is resolved at runtime based on the actual object type.
22. What is the this pointer?
this is a pointer to the current object. It allows a member function to refer to the object it’s operating on and is implicitly available in all member functions.
class Dog {
public:
void setName(std::string name) {
this->name = name; // "this->name" is the member variable
}
Dog& getSelf() {
return *this; // Return reference to self
}
private:
std::string name;
};
It’s useful for returning a reference to the current object (for chaining) or explicitly indicating you’re accessing a member variable.
23. What are static members?
A static member variable is shared by all instances of the class. There’s only one copy, no matter how many objects you create.
A static member function can access only static members and doesn’t operate on a specific object.
class Counter {
public:
static void increment() { count++; }
static int getCount() { return count; }
private:
static int count; // Shared by all Counter objects
};
int Counter::count = 0; // Define and initialize static member
Counter c1, c2;
c1.increment();
c2.increment();
std::cout << Counter::getCount(); // Prints 2
Static members are useful for tracking global state or providing utility functions.
24. What is the difference between composition and inheritance?
Inheritance is an “is-a” relationship: Dog is-a Animal.
Composition is a “has-a” relationship: Car has-a Engine.
// Inheritance (is-a)
class Animal { };
class Dog : public Animal { };
// Composition (has-a)
class Engine { };
class Car {
private:
Engine engine; // Car has-an Engine
};
Composition is often preferred over inheritance for flexibility. You can change components without inheritance hierarchy constraints. Inheritance should represent true “is-a” relationships; use composition for most other cases.
Section 3: Memory Management (Questions 25–33)
25. Explain stack vs. heap memory.
Stack: Fast, automatic memory management. Variables have limited lifetime (function scope). Size is limited. Allocations are free (instant).
Heap: Slower, manual memory management (in raw C++). Large size. Allocations require explicit new, deallocation requires delete. Memory persists until freed.
void myFunction() {
int x = 5; // Stack: automatically freed when myFunction returns
int* p = new int(5); // Heap: must be freed with delete
delete p; // Clean up heap memory
}
Stack is appropriate for local variables with known sizes. Heap is for dynamic allocation or large objects.
26. What are the new and delete operators? How do they differ from malloc and free?
new and delete are C++ operators for dynamic memory allocation. They call constructors and destructors.
malloc and free are C-style memory allocation. They allocate raw memory without calling constructors/destructors.
// C++ style (preferred)
MyClass* obj = new MyClass(); // Calls constructor
delete obj; // Calls destructor
// C style (avoid in C++)
MyClass* obj = (MyClass*)malloc(sizeof(MyClass)); // No constructor
free(obj); // No destructor
Always use new/delete in C++ because they handle initialization and cleanup. malloc/free are dangerous with classes.
27. What is a memory leak? How do you prevent it?
A memory leak occurs when you allocate memory with new but never call delete, causing the memory to remain allocated even when no longer needed.
void badFunction() {
int* p = new int(5);
// ... code ...
return; // LEAK: p is never deleted!
}
Prevention:
- Always delete what you new.
- Use smart pointers (preferred).
- Use RAII (Resource Acquisition Is Initialization).
// Smart pointer approach (preferred)
void goodFunction() {
std::unique_ptr<int> p(new int(5));
// ... code ...
return; // Automatically deleted
}
28. What are smart pointers? Name the types.
Smart pointers are objects that act like pointers but manage memory automatically. The main types are:
std::unique_ptr: Exclusive ownership. Only one unique_ptr can own an object.
std::unique_ptr<Dog> d(new Dog()); // d owns the Dog
// Dog is automatically deleted when d goes out of scope
std::shared_ptr: Shared ownership. Multiple shared_ptrs can own the same object. Deleted when the last one goes out of scope.
std::shared_ptr<Dog> d1(new Dog());
std::shared_ptr<Dog> d2 = d1; // Both own the same Dog
// Dog deleted only when both d1 and d2 are gone
Prefer unique_ptr for simple cases; use shared_ptr when multiple owners are needed.
29. What is RAII (Resource Acquisition Is Initialization)?
RAII is a design pattern: resources (memory, files, locks) are acquired in the constructor and released in the destructor. As long as the object exists, the resource is valid. When the object is destroyed, the resource is automatically released.
class FileHandler {
private:
FILE* file;
public:
FileHandler(const char* filename) {
file = fopen(filename, "r"); // Acquire
}
~FileHandler() {
if (file) fclose(file); // Release
}
};
void processFile() {
FileHandler fh("myfile.txt"); // File opens
// ... use file ...
} // File automatically closes (destructor called)
RAII ensures resources are cleaned up even if exceptions occur. It’s the foundation of safe C++ programming.
30. What is the difference between shallow and deep copy?
A shallow copy copies only the pointers, not the data they point to. Multiple objects share the same data.
A deep copy copies the data itself, creating independent objects.
class MyClass {
public:
int* data;
MyClass(int* d) : data(d) { } // Shallow copy by default
};
int arr[5] = {1, 2, 3, 4, 5};
MyClass obj1(arr);
MyClass obj2 = obj1; // Shallow copy: both point to the same arr
This is problematic because both objects point to the same data. If one modifies it, the other sees the change. Worse, if arr is freed, both objects have dangling pointers.
Solution: Implement a deep copy.
MyClass(const MyClass& other) { // Copy constructor
data = new int[5];
for (int i = 0; i < 5; i++) {
data[i] = other.data[i]; // Copy the data
}
}
31. What are the Rule of Three, Rule of Five, and Rule of Zero?
Rule of Three: If you define a destructor, copy constructor, or copy assignment operator, you should define all three.
class MyClass {
public:
~MyClass() { delete[] data; }
MyClass(const MyClass& other); // Copy constructor
MyClass& operator=(const MyClass& other); // Copy assignment
private:
int* data;
};
Rule of Five: Add move constructor and move assignment operator to the three.
MyClass(MyClass&& other) noexcept; // Move constructor
MyClass& operator=(MyClass&& other) noexcept; // Move assignment
Rule of Zero: Don’t define any of them. Use standard containers and smart pointers instead. Let the compiler generate defaults.
Modern C++ favors the Rule of Zero: use std::vector, std::unique_ptr, and let the standard library handle memory.
32. What are memory addresses and how do you view them?
A memory address is a location in RAM, typically displayed in hexadecimal (e.g., 0x7fff5fbff8c0). Use the & operator to get an address and cout to view it.
int x = 5;
std::cout << &x << std::endl; // Prints memory address of x
std::cout << std::hex << &x << std::endl; // Hexadecimal format
Addresses are useful for understanding pointer behavior and debugging. The & operator shows where a variable is stored.
33. What is a dangling pointer? When does it occur?
A dangling pointer points to memory that has been freed. Dereferencing it causes undefined behavior (crash or corruption).
int* createPointer() {
int x = 5;
return &x; // DANGER: returning pointer to local variable
}
int main() {
int* p = createPointer();
std::cout << *p; // CRASH: p points to freed memory
}
The local variable x is destroyed when createPointer() returns, but the pointer still points to that location.
Prevention:
- Don’t return pointers to local variables.
- Check for null pointers before dereferencing.
- Use smart pointers (they prevent this).
Section 4: STL & Templates (Questions 34–41)
34. What is the Standard Template Library (STL)?
The STL is a library of templates providing containers, algorithms, and iterators. It’s a core part of modern C++. Main components:
- Containers:
vector,map,set,queue,stack,list - Algorithms:
sort,find,copy,transform - Iterators: Objects that iterate through containers
std::vector<int> v = {3, 1, 4, 1, 5};
std::sort(v.begin(), v.end()); // Sort the vector
for (int num : v) {
std::cout << num << " "; // Print sorted elements
}
The STL eliminates the need to write basic data structures. It’s optimized and well-tested.
35. What is a std::vector? How is it different from an array?
A std::vector is a dynamic array. Unlike C++ arrays, vectors can grow and shrink at runtime.
std::vector<int> v; // Empty vector
v.push_back(5); // Add element
v.push_back(10);
std::cout << v.size(); // Size is 2
std::cout << v[0]; // Access like array
Arrays are fixed-size and allocated on the stack (for small sizes) or manually on the heap. Vectors handle memory for you and provide useful methods like push_back, pop_back, size.
Always use std::vector instead of raw arrays in modern C++.
36. What are std::map and std::set? How do they differ?
std::map is a key-value container. Keys are unique; values can be duplicated. Ordered by key.
std::set stores unique values. No key-value pairs. Ordered.
std::map<std::string, int> ages;
ages["Alice"] = 30;
ages["Bob"] = 25;
std::cout << ages["Alice"]; // Prints 30
std::set<int> numbers = {5, 2, 8, 2}; // {2, 5, 8}—no duplicates
for (int n : numbers) {
std::cout << n << " "; // Prints in sorted order
}
Both are ordered containers (implemented as balanced trees). Use map for key-value data; use set for unique values.
37. What are iterators? Name the types.
An iterator is an object that points to elements in a container, similar to a pointer. It enables algorithm-container interaction.
Types (from weakest to strongest):
- Input iterator: Read-only, forward-only
- Output iterator: Write-only, forward-only
- Forward iterator: Read/write, forward-only (e.g.,
std::forward_list) - Bidirectional iterator: Read/write, forward and backward (e.g.,
std::list,std::map) - Random-access iterator: Full pointer semantics (e.g.,
std::vector,std::array)
std::vector<int> v = {1, 2, 3};
std::vector<int>::iterator it = v.begin();
std::cout << *it; // Prints 1
++it;
std::cout << *it; // Prints 2
it += 2; // Random access
Iterators enable writing algorithms that work with any container.
38. What is the difference between begin() and front()?
begin() returns an iterator to the first element. You must dereference it to access the value.
front() returns a reference to the first element directly.
std::vector<int> v = {5, 10, 15};
std::cout << *v.begin(); // Prints 5 (iterator, must dereference)
std::cout << v.front(); // Prints 5 (direct reference)
Use front() when you want the value; use begin() when you want an iterator for algorithms.
39. What are templates? Explain function templates and class templates.
A template is a blueprint for generating code based on type parameters. It allows writing generic code that works with any type.
Function template:
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}
std::cout << max(5, 10); // Works with int
std::cout << max(3.5, 2.1); // Works with double
Class template:
template <typename T>
class Container {
public:
void add(T item) { items.push_back(item); }
private:
std::vector<T> items;
};
Container<int> intContainer;
Container<std::string> stringContainer;
Templates enable writing once, using many times. The compiler generates type-specific versions as needed.
40. What is template specialization?
Template specialization allows providing custom implementations for specific types.
template <typename T>
void print(T value) {
std::cout << "Generic: " << value << "\n";
}
// Specialization for std::string
template <>
void print<std::string>(std::string value) {
std::cout << "String: [" << value << "]\n";
}
print(5); // Uses generic version
print("hello"); // Uses specialized version
This is useful when a generic implementation isn’t optimal for certain types.
41. What are lambda functions?
A lambda function is an anonymous function defined inline. Syntax: [captures](parameters) { body }
auto add = [](int a, int b) { return a + b; };
std::cout << add(3, 5); // Prints 8
std::vector<int> v = {3, 1, 4, 1, 5};
std::sort(v.begin(), v.end(), [](int a, int b) {
return a > b; // Sort descending
});
Lambdas are useful for passing custom logic to algorithms. The [...] part captures variables from the surrounding scope.
int multiplier = 3;
auto multiply = [multiplier](int x) { return x * multiplier; };
std::cout << multiply(5); // Prints 15
Section 5: Advanced Topics (Questions 42–50)
42. What are move semantics and rvalue references?
Move semantics allows efficient transfer of resources instead of copying. An rvalue reference (&&) refers to a temporary object.
std::string a = "hello";
std::string b = std::move(a); // Move instead of copy
// a is now empty; b contains "hello"
Without move semantics, b = a would copy the entire string. With move semantics, the data is transferred.
Rvalue references are declared with &&:
std::string&& temp = std::string("temporary");
// temp refers to the temporary string
Move semantics enable efficient passing of temporary objects and returning from functions without unnecessary copies.
43. What is std::move() and when should you use it?
std::move() casts an lvalue (persistent object) to an rvalue (temporary). It indicates “I’m done with this object; you can steal its resources.”
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1);
// v1 is now empty; v2 contains {1, 2, 3}
Use std::move() when returning objects from functions, passing to functions that accept rvalue references, or transferring ownership.
std::unique_ptr<Dog> d1(new Dog());
std::unique_ptr<Dog> d2 = std::move(d1); // Transfer ownership
// d1 is now null; d2 owns the Dog
44. What is the difference between lvalue and rvalue?
An lvalue is an object with a persistent address. You can take its address with &.
An rvalue is a temporary object. It exists briefly and you can’t take its address.
int x = 5; // x is lvalue
int& ref = x; // Can bind lvalue reference to x
int y = x + 5; // (x + 5) is rvalue (temporary)
int& ref2 = x + 5; // ERROR: cannot bind lvalue reference to rvalue
int&& rref = x + 5; // OK: can bind rvalue reference to rvalue
Rvalue references enable move semantics and perfect forwarding.
45. What is undefined behavior? Give examples.
Undefined behavior is code whose results are unpredictable. The C++ standard makes no guarantees. Your program might crash, produce garbage, or appear to work fine.
Examples:
int* p; // Uninitialized pointer
std::cout << *p; // UB: accessing uninitialized pointer
int arr[5];
std::cout << arr[10]; // UB: buffer overflow
int x = 1 / 0; // UB: division by zero
std::string s = "hello";
s[100]; // UB: accessing out of bounds
Prevention:
- Initialize all variables.
- Check bounds.
- Avoid dereferencing null pointers.
- Use compiler warnings (
-Wall -Wextra).
46. What are exceptions? How do you use try, catch, and throw?
Exceptions provide error handling. Code that might fail is wrapped in try; errors are handled in catch.
try {
int x = readUserInput();
if (x < 0) {
throw std::invalid_argument("Input must be positive");
}
std::cout << 100 / x; // Might throw std::exception
}
catch (std::invalid_argument& e) {
std::cout << "Error: " << e.what() << "\n";
}
catch (std::exception& e) {
std::cout << "Unexpected error: " << e.what() << "\n";
}
throw raises an exception; catch handles it. Multiple catch blocks handle different exception types. Use exceptions for truly exceptional conditions, not normal control flow.
47. What are design patterns? Name a few.
Design patterns are reusable solutions to common problems. Key patterns:
Singleton: Ensures only one instance of a class exists.
class Database {
public:
static Database& getInstance() {
static Database instance;
return instance;
}
private:
Database() { } // Private constructor
};
Factory: Creates objects without specifying exact classes.
std::unique_ptr<Shape> createShape(std::string type) {
if (type == "circle") return std::make_unique<Circle>();
else if (type == "square") return std::make_unique<Square>();
}
Observer: Objects notify others of state changes.
Other common patterns: Decorator, Strategy, Command, Adapter.
48. What is const-correctness?
Const-correctness means using const to promise that data won’t be modified. It improves safety and clarity.
class Dog {
public:
void speak() const { // Promise: doesn't modify the Dog
std::cout << "Woof\n";
}
void setAge(int age) { // Promise: modifies the Dog
this->age = age;
}
int getAge() const { // Promise: doesn't modify; const return
return age;
}
private:
int age;
};
Mark functions const if they don’t modify the object. Mark parameters const if they won’t be modified. The compiler enforces these promises.
49. What is the difference between std::unique_ptr and std::shared_ptr?
Both manage dynamic memory, but ownership differs.
std::unique_ptr: One owner only. When the owner is destroyed, the object is deleted.
std::unique_ptr<Dog> d(new Dog());
// d owns the Dog exclusively
std::unique_ptr<Dog> d2 = std::move(d); // Transfer ownership
// d is now null; d2 owns the Dog
std::shared_ptr: Multiple owners. Object is deleted when the last owner is destroyed.
std::shared_ptr<Dog> d1(new Dog());
std::shared_ptr<Dog> d2 = d1; // Both own the Dog
// Dog deleted only when both d1 and d2 are gone
Rule: Use unique_ptr unless multiple owners are necessary. It’s simpler and cheaper.
50. What is multithreading in C++? How do you create threads?
Multithreading runs multiple functions concurrently. Use std::thread.
#include <thread>
void printMessage(int id) {
std::cout << "Hello from thread " << id << "\n";
}
int main() {
std::thread t1(printMessage, 1);
std::thread t2(printMessage, 2);
t1.join(); // Wait for t1 to finish
t2.join(); // Wait for t2 to finish
return 0;
}
std::thread takes a function and arguments. join() blocks until the thread finishes. Threads enable concurrent execution but introduce complexity (race conditions, deadlocks). Use synchronization primitives (std::mutex, std::lock_guard) to protect shared data.
Section 6: Practice Problems
Reading answers is half the job. Writing code under pressure is the other half. These 10 problems cover the concepts from the 50 questions above — work through them before your interview.
Problem 1: Reverse a String In-Place
Problem: Write a function that reverses a std::string in-place without using std::reverse or any extra string.
Concepts tested: Loops, indexing, swap.
Hint: Use two indices — one at the start, one at the end — and swap characters while they haven’t crossed.
Solution:
#include <iostream>
#include <string>
void reverseString(std::string& s) {
int left = 0;
int right = s.size() - 1;
while (left < right) {
std::swap(s[left], s[right]);
left++;
right--;
}
}
int main() {
std::string s = "hello";
reverseString(s);
std::cout << s << std::endl; // Output: olleh
return 0;
}
What interviewers look for: Do you handle edge cases (empty string, single character)? Do you know why passing by reference matters here?
Problem 2: Implement a Stack Using std::vector
Problem: Implement a generic Stack<T> class with push(), pop(), top(), isEmpty(), and size() operations. Throw std::runtime_error if pop() or top() is called on an empty stack.
Concepts tested: Class design, templates, STL containers, exception handling.
Solution:
#include <iostream>
#include <vector>
#include <stdexcept>
template <typename T>
class Stack {
public:
void push(const T& value) {
data.push_back(value);
}
void pop() {
if (isEmpty()) throw std::runtime_error("Stack is empty");
data.pop_back();
}
T& top() {
if (isEmpty()) throw std::runtime_error("Stack is empty");
return data.back();
}
bool isEmpty() const { return data.empty(); }
size_t size() const { return data.size(); }
private:
std::vector<T> data;
};
int main() {
Stack<int> s;
s.push(10);
s.push(20);
s.push(30);
std::cout << s.top() << std::endl; // 30
s.pop();
std::cout << s.top() << std::endl; // 20
std::cout << s.size() << std::endl; // 2
return 0;
}
What interviewers look for: Correct use of templates, const-correctness on isEmpty() and size(), exception throwing over silent failures.
Problem 3: Count Word Frequencies
Problem: Given a std::vector<std::string> of words, return a std::map<std::string, int> with the count of each word. Then print the words and their frequencies in alphabetical order.
Concepts tested: STL map, range-based for loops, structured iteration.
Solution:
#include <iostream>
#include <vector>
#include <map>
#include <string>
std::map<std::string, int> countWords(const std::vector<std::string>& words) {
std::map<std::string, int> freq;
for (const auto& word : words) {
freq[word]++; // Creates entry with 0 if not present, then increments
}
return freq;
}
int main() {
std::vector<std::string> words = {"apple", "banana", "apple", "cherry", "banana", "apple"};
auto freq = countWords(words);
for (const auto& [word, count] : freq) { // C++17 structured binding
std::cout << word << ": " << count << "\n";
}
// Output (alphabetically):
// apple: 3
// banana: 2
// cherry: 1
return 0;
}
What interviewers look for: Knowledge that map[key]++ auto-initializes to 0. Using structured bindings for clean iteration. Understanding that std::map is sorted by key.
Problem 4: RAII — Safe File Reader
Problem: Write a FileReader class that opens a file in its constructor, reads all lines into a std::vector<std::string>, and closes the file in its destructor. The class should not be copyable.
Concepts tested: RAII, constructors/destructors, Rule of Three/Zero, deleting copy operations.
Solution:
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <stdexcept>
class FileReader {
public:
explicit FileReader(const std::string& filename) {
file.open(filename);
if (!file.is_open()) {
throw std::runtime_error("Could not open file: " + filename);
}
std::string line;
while (std::getline(file, line)) {
lines.push_back(line);
}
}
~FileReader() {
if (file.is_open()) file.close();
}
// Delete copy — this class manages a resource
FileReader(const FileReader&) = delete;
FileReader& operator=(const FileReader&) = delete;
const std::vector<std::string>& getLines() const { return lines; }
size_t lineCount() const { return lines.size(); }
private:
std::fstream file;
std::vector<std::string> lines;
};
int main() {
try {
FileReader reader("example.txt");
for (const auto& line : reader.getLines()) {
std::cout << line << "\n";
}
} catch (const std::runtime_error& e) {
std::cerr << "Error: " << e.what() << "\n";
}
return 0;
}
What interviewers look for: Why = delete on copy operations, why explicit on the constructor, why throwing in the constructor is correct RAII behaviour.
Problem 5: Polymorphism — Shape Area Calculator
Problem: Define an abstract Shape class with a pure virtual area() method. Implement Circle and Rectangle subclasses. Write a function that takes a std::vector<std::unique_ptr<Shape>> and prints the area of each shape.
Concepts tested: Abstract classes, virtual functions, polymorphism, smart pointers.
Solution:
#include <iostream>
#include <vector>
#include <memory>
#include <cmath>
class Shape {
public:
virtual double area() const = 0; // Pure virtual
virtual std::string name() const = 0;
virtual ~Shape() = default; // Virtual destructor is required!
};
class Circle : public Shape {
public:
explicit Circle(double radius) : radius(radius) {}
double area() const override { return M_PI * radius * radius; }
std::string name() const override { return "Circle"; }
private:
double radius;
};
class Rectangle : public Shape {
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const override { return width * height; }
std::string name() const override { return "Rectangle"; }
private:
double width, height;
};
void printAreas(const std::vector<std::unique_ptr<Shape>>& shapes) {
for (const auto& shape : shapes) {
std::cout << shape->name() << " area: " << shape->area() << "\n";
}
}
int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5.0));
shapes.push_back(std::make_unique<Rectangle>(4.0, 6.0));
shapes.push_back(std::make_unique<Circle>(3.0));
printAreas(shapes);
// Circle area: 78.5398
// Rectangle area: 24
// Circle area: 28.2743
return 0;
}
What interviewers look for: Virtual destructor on the base class (critical — forgetting this causes undefined behaviour), override keyword, std::make_unique, passing by const reference.
Problem 6: Remove Duplicates from a Vector
Problem: Given a std::vector<int>, remove all duplicate values and return a new vector with only unique elements, preserving the original order.
Concepts tested: std::unordered_set, iteration, algorithmic thinking.
Solution:
#include <iostream>
#include <vector>
#include <unordered_set>
std::vector<int> removeDuplicates(const std::vector<int>& input) {
std::unordered_set<int> seen;
std::vector<int> result;
for (int val : input) {
if (seen.find(val) == seen.end()) { // Not seen yet
seen.insert(val);
result.push_back(val);
}
}
return result;
}
int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
auto unique = removeDuplicates(v);
for (int x : unique) {
std::cout << x << " "; // Output: 3 1 4 5 9 2 6
}
return 0;
}
Variation an interviewer might ask: Do it in-place using std::sort + std::unique + erase. Discuss the tradeoff: the set approach is O(n) average time, O(n) space; the sort approach is O(n log n) time but preserves nothing about order.
Problem 7: Move Semantics — Buffer Class
Problem: Implement a Buffer class that wraps a heap-allocated int array. It should support move construction and move assignment so that transferring a Buffer doesn’t copy the data — it just moves ownership.
Concepts tested: Move constructor, move assignment, std::move, Rule of Five, noexcept.
Solution:
#include <iostream>
#include <utility>
class Buffer {
public:
explicit Buffer(size_t size) : size(size), data(new int[size]()) {
std::cout << "Constructed buffer of size " << size << "\n";
}
~Buffer() {
delete[] data;
std::cout << "Destroyed buffer\n";
}
// Copy: expensive (allocates new memory)
Buffer(const Buffer& other) : size(other.size), data(new int[other.size]) {
std::copy(other.data, other.data + size, data);
std::cout << "Copied buffer\n";
}
// Move: cheap (just steals the pointer)
Buffer(Buffer&& other) noexcept : size(other.size), data(other.data) {
other.data = nullptr;
other.size = 0;
std::cout << "Moved buffer\n";
}
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
size_t getSize() const { return size; }
private:
size_t size;
int* data;
};
int main() {
Buffer b1(1000);
Buffer b2 = std::move(b1); // Move, not copy
std::cout << "b2 size: " << b2.getSize() << "\n"; // 1000
std::cout << "b1 size: " << b1.getSize() << "\n"; // 0
return 0;
}
What interviewers look for: Setting the moved-from pointer to nullptr (critical — prevents double-free), noexcept on move operations (required for STL container optimisations), self-assignment check in move assignment.
Problem 8: Thread-Safe Counter
Problem: Implement a ThreadSafeCounter class that can be incremented safely from multiple threads. Demonstrate it by spawning 10 threads that each increment the counter 1000 times, then verify the final count is exactly 10,000.
Concepts tested: std::thread, std::mutex, std::lock_guard, race conditions.
Solution:
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
class ThreadSafeCounter {
public:
void increment() {
std::lock_guard<std::mutex> lock(mutex);
++count;
}
int get() const {
std::lock_guard<std::mutex> lock(mutex);
return count;
}
private:
int count = 0;
mutable std::mutex mutex; // mutable: allows locking in const methods
};
int main() {
ThreadSafeCounter counter;
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back([&counter]() {
for (int j = 0; j < 1000; ++j) {
counter.increment();
}
});
}
for (auto& t : threads) {
t.join();
}
std::cout << "Final count: " << counter.get() << "\n"; // Always 10000
return 0;
}
What interviewers look for: Why mutable on the mutex, why lock_guard instead of raw lock()/unlock(), what would happen without the mutex (race condition — non-deterministic results).
Problem 9: Find the Two Numbers That Sum to a Target
Problem: Given a std::vector<int> and a target integer, find and return the indices of the two numbers that add up to the target. Assume exactly one solution exists. Solve it in O(n) time.
Concepts tested: std::unordered_map, algorithmic thinking, one-pass hash table.
Solution:
#include <iostream>
#include <vector>
#include <unordered_map>
#include <stdexcept>
std::pair<int, int> twoSum(const std::vector<int>& nums, int target) {
std::unordered_map<int, int> seen; // value -> index
for (int i = 0; i < static_cast<int>(nums.size()); ++i) {
int complement = target - nums[i];
if (seen.count(complement)) {
return {seen[complement], i};
}
seen[nums[i]] = i;
}
throw std::runtime_error("No solution found");
}
int main() {
std::vector<int> nums = {2, 7, 11, 15};
auto [idx1, idx2] = twoSum(nums, 9);
std::cout << "Indices: " << idx1 << ", " << idx2 << "\n"; // 0, 1
// nums[0] + nums[1] = 2 + 7 = 9
return 0;
}
What interviewers look for: Why this is O(n) not O(n²), the complement trick, using unordered_map for O(1) average lookup. An O(n²) brute-force nested loop solution exists but would likely not pass in a real interview for large inputs.
Problem 10: Flatten a Nested Vector
Problem: Given a std::vector<std::vector<int>>, write a function that returns a single std::vector<int> with all elements flattened in order.
Concepts tested: Nested containers, insert(), range-based for loops, iterators.
Solution:
#include <iostream>
#include <vector>
std::vector<int> flatten(const std::vector<std::vector<int>>& nested) {
std::vector<int> result;
for (const auto& inner : nested) {
result.insert(result.end(), inner.begin(), inner.end());
}
return result;
}
int main() {
std::vector<std::vector<int>> nested = {
{1, 2, 3},
{4, 5},
{6, 7, 8, 9}
};
auto flat = flatten(nested);
for (int x : flat) {
std::cout << x << " "; // 1 2 3 4 5 6 7 8 9
}
return 0;
}
Bonus extension: Reserve capacity upfront for performance:
std::vector<int> flatten(const std::vector<std::vector<int>>& nested) {
size_t total = 0;
for (const auto& inner : nested) total += inner.size();
std::vector<int> result;
result.reserve(total); // Single allocation, no reallocations
for (const auto& inner : nested) {
result.insert(result.end(), inner.begin(), inner.end());
}
return result;
}
What interviewers look for: Knowledge of insert() with iterator range, awareness of reserve() for performance, clean loop structure.
Interview Tips
-
Understand the Why: Don’t just memorize answers. Understand why each feature exists and when to use it.
-
Practice Coding: Write code by hand and on a whiteboard. Practice on LeetCode or HackerRank.
-
Ask Clarifying Questions: If a question is ambiguous, ask for clarification before answering.
-
Explain Your Thinking: Talk through your approach. Interviewers want to see your thought process.
-
Handle Mistakes Gracefully: If you make an error, acknowledge it and correct it. It shows maturity.
-
Cover All Cases: Think about edge cases (null pointers, empty containers, negative numbers) and mention them.
-
Know Your Resources: Be ready to discuss your experience with the STL, smart pointers, and modern C++ practices.
Conclusion
These 50 questions cover the core of C++ knowledge. Study them, but don’t stop here:
- Code regularly: Build projects to reinforce concepts.
- Read others’ code: Understand different approaches and best practices.
- Follow modern practices: Use smart pointers, const-correctness, and the STL.
- Understand performance: Know when optimizations matter and when they don’t.
Want to go deeper on any of these topics? Our C++ interview prep ebook covers all 50 questions with full explanations, practice problems, and strategies for explaining your thinking to interviewers. Master not just the answers, but the concepts behind them.
Take Your C++ Further
If you’re looking to go deeper with C++, the C++ Better Explained Ebook is perfect for you — whether you’re a complete beginner or looking to solidify your understanding. Just $19.
👉 Get the C++ Better Explained Ebook — $19
Related Articles
- How to Use Pointers in C++: A Complete Beginner’s Guide — master the memory management concepts that appear in every C++ interview.
- Object-Oriented Programming in C++: Classes, Objects, and Constructors Explained — deep dive into the OOP topics covered in Questions 13–24.
- Smart Pointers in Modern C++: unique_ptr, shared_ptr, and weak_ptr Explained — essential reading to nail the memory management questions.
- C++ STL Containers Explained: Choosing the Right Container for Every Situation — covers the STL topics from Questions 34–41 in detail.
- C++ Cheat Sheet: Quick Reference for Syntax, STL, and OOP — a fast-reference card to review syntax before your interview.