⚙️

Top 44 C++ Interview Questions & Answers (2026)

44 Questions 20 Beginner 16 Intermediate 8 Advanced

About C++

Top 50 C++ interview questions covering OOP, memory management, STL, templates, smart pointers, move semantics, concurrency, and modern C++17/20 features. Companies hiring for C++ roles test this knowledge at every stage — from first-round screens that check the basics to final-round conversations about real-world trade-offs.

What to Expect in a C++ Interview

Expect a mix of conceptual and practical C++ questions: clear definitions and core-concept checks for junior roles, hands-on scenario questions for mid-level roles, and architecture or trade-off discussions for senior roles. Interviewers usually move from foundational topics toward the kind of problems you'd actually face on the job.

How to Use This Guide

Work through the C++ questions in order — Beginner, then Intermediate, then Advanced — so each concept builds on the last. Every question has its own page; bookmark the ones that trip you up and revisit them the day before your interview.

Curated by Tech Baithak Editorial Team  ·  Last updated: May 2026

Beginner 20 questions

Core concepts every C++ developer must know.

01

What is C++ and how does it differ from C?

C++ is a general-purpose, statically typed, compiled programming language created by Bjarne Stroustrup at Bell Labs in 1985 as an extension of the C language. It is often described as "C with classes." While C is a procedural language, C++ adds several powerful paradigms on top of it. Key differences: (1) Object-Oriented Programming (OOP): C++ supports classes, objects, inheritance, polymorphism, and encapsulation — C has no built-in OOP support; (2) Function overloading: C++ allows multiple functions with the same name but different parameter types — not possible in C; (3) References: C++ introduces reference variables (&) in addition to C's pointers; (4) Templates: C++ generic programming with function and class templates — C uses macros or void pointers; (5) STL: C++ ships with the Standard Template Library (containers, algorithms, iterators) — C has a much smaller standard library; (6) Operator overloading: custom behavior for operators like +, <<; (7) Exception handling: try/catch/throw — C uses setjmp/longjmp or error codes; (8) new/delete: type-safe memory allocation vs C's malloc/free; (9) Namespaces: avoid name collisions; (10) inline functions: replace C macros safely. C++ is fully backward-compatible with C — valid C code is (mostly) valid C++.

Open this question on its own page
02

What are the four pillars of OOP in C++?

C++ supports all four pillars of Object-Oriented Programming: (1) Encapsulation: bundling data (member variables) and methods (member functions) that operate on that data within a class, and restricting direct access using access specifiers. class BankAccount { private: double balance; // hidden public: void deposit(double amount) { if (amount > 0) balance += amount; } double getBalance() const { return balance; } }; Encapsulation protects invariants and hides implementation details; (2) Abstraction: exposing only essential features and hiding complexity. Pure virtual functions define abstract interfaces: class Shape { public: virtual double area() const = 0; // pure virtual virtual ~Shape() = default; }; Clients use Shape without knowing the concrete implementation; (3) Inheritance: a derived class inherits members from a base class, enabling code reuse and "is-a" relationships. class Circle : public Shape { double radius; public: Circle(double r) : radius(r) {} double area() const override { return 3.14159 * radius * radius; } }; Types: public, protected, private inheritance. Multiple inheritance is supported; (4) Polymorphism: the same interface behaves differently based on the actual type. Compile-time (function overloading, templates) and runtime (virtual functions — dynamic dispatch via vtable): Shape* s = new Circle(5); std::cout << s->area(); // Calls Circle::area(). Virtual dispatch enables programming to interfaces, not implementations.

Open this question on its own page
03

What is a class and an object in C++?

A class is a user-defined blueprint or template that defines the data members (attributes) and member functions (methods) that objects of the class will have. A object is an instance of a class — a concrete entity created at runtime that holds its own copy of the data members. class Car { public: std::string brand; // data member int year; void startEngine() { // member function std::cout << brand << " engine started!\n"; } }; // Creating objects Car myCar; // Stack allocation myCar.brand = "Toyota"; myCar.year = 2022; myCar.startEngine(); Car* heapCar = new Car(); // Heap allocation heapCar->brand = "Honda"; delete heapCar;. Access specifiers: public — accessible from anywhere; private (default for class) — accessible only within the class; protected — accessible within class and derived classes. struct vs class: both define user-defined types. The only difference: struct members are public by default; class members are private by default. Use struct for passive data aggregates (POD types); use class for objects with behavior and invariants. Member initialization: prefer constructor initializer lists: Car(std::string b, int y) : brand(b), year(y) {} — more efficient than assignment in body (avoids default-construction then copy-assignment). this pointer: implicit pointer inside member functions pointing to the current object.

Open this question on its own page
04

What are constructors and destructors in C++?

A constructor is a special member function with the same name as the class and no return type. It is automatically called when an object is created, used to initialize the object's state. A destructor is the complement — called automatically when an object goes out of scope or is deleted, used to release resources. Types of constructors: (1) Default constructor: no parameters — Car() : year(0) {}; (2) Parameterized constructor: accepts arguments — Car(std::string b, int y) : brand(b), year(y) {}; (3) Copy constructor: initializes from another object of the same type — Car(const Car& other) : brand(other.brand), year(other.year) {}; (4) Move constructor (C++11): transfers resources from a temporary — Car(Car&& other) noexcept : brand(std::move(other.brand)), year(other.year) {}; (5) Delegating constructor (C++11): one constructor calls another: Car() : Car("Unknown", 0) {}. Destructor: ~Car() { std::cout << "Car destroyed\n"; }. Only one destructor per class — no parameters, no overloading. Rule of Three: if you define any of copy constructor, copy assignment, or destructor, define all three (managing resources manually). Rule of Five (C++11): also define move constructor and move assignment. Rule of Zero: prefer to use smart pointers and containers so the compiler-generated defaults handle everything correctly. RAII (Resource Acquisition Is Initialization): acquire resources in constructors, release in destructors — the foundation of exception-safe C++ programming.

Open this question on its own page
05

What is the difference between new/delete and malloc/free in C++?

Both allocate and deallocate heap memory, but with critical differences: new/delete (C++): int* p = new int(42); // Allocates + initializes Car* c = new Car("Toyota", 2022); // Calls constructor! delete p; // Deallocates delete c; // Calls destructor then deallocates! int* arr = new int[10]; // Array allocation delete[] arr; // Must use delete[] for arrays!. Key: new calls the constructor; delete calls the destructor. Type-safe — no cast needed. Throws std::bad_alloc on failure (not null). malloc/free (C, inherited): int* p = (int*)malloc(sizeof(int)); // Manual cast required! *p = 42; // No initialization Car* c = (Car*)malloc(sizeof(Car)); // Does NOT call constructor! free(p); // Does NOT call destructor! free(c); // Destructor never runs -- resource leak!. Returns nullptr on failure. No type information — returns void*. Critical rule: never mix new/delete with malloc/free on the same allocation. Never use delete on something allocated with new[] (undefined behavior — use delete[]). Modern C++: avoid raw new/delete entirely — use std::make_unique<T> and std::make_shared<T> (smart pointers) for automatic memory management. They call constructors/destructors correctly and prevent leaks. auto c = std::make_unique<Car>("Toyota", 2022); // No manual delete needed

Open this question on its own page
06

What are pointers and references in C++?

Pointers store the memory address of another variable. References are aliases — alternative names for existing variables. Pointer basics: int x = 42; int* ptr = &x; // ptr holds address of x *ptr = 100; // Dereference: change x through ptr ptr = nullptr; // Pointer can be reassigned int** pptr = &ptr; // Pointer to pointer. Reference basics: int x = 42; int& ref = x; // ref is an alias for x ref = 100; // Changes x directly // int& ref; // ERROR: reference must be initialized // ref = y; // This reassigns x, not the reference. Key differences: (1) References MUST be initialized at declaration; pointers can be uninitialized (or null); (2) References cannot be null; pointers can be nullptr; (3) References cannot be reseated (made to refer to a different variable); pointers can be reassigned; (4) No arithmetic on references; pointer arithmetic is valid (ptr + 1 moves to next element); (5) Syntax: reference uses ref.member; pointer uses ptr->member or (*ptr).member. const with pointers: const int* ptr; // Pointer to const int (can't change value) int* const ptr; // Const pointer (can't change address) const int* const ptr; // Both const. When to use each: use references when the alias must not be null and won't be reseated (function parameters, return values); use pointers when null is a valid state, when reassignment is needed, or for dynamic allocation.

Open this question on its own page
07

What is function overloading in C++?

Function overloading allows multiple functions to have the same name but different parameter lists (different number, types, or order of parameters). The compiler selects the correct version at compile time — this is compile-time polymorphism. void print(int n) { std::cout << "int: " << n << "\n"; } void print(double d) { std::cout << "double: " << d << "\n"; } void print(std::string s) { std::cout << "string: " << s << "\n"; } void print(int n, std::string label) { std::cout << label << ": " << n << "\n"; } // Calls: print(42); // int version print(3.14); // double version print("hello"); // string version print(42, "count"); // two-param version. Rules for overloading: functions must differ in parameter types, number, or order — NOT just return type. int foo(); double foo(); is INVALID. How the compiler resolves: exact match → promotion (int→double) → standard conversion → user-defined conversion → variadic. Ambiguous calls (multiple equally good matches) are compile errors. Name mangling: C++ compiler encodes function name + parameter types in the compiled symbol name — this is how multiple overloads coexist. Use extern "C" to disable mangling for C interoperability. Default arguments (related): void print(int n, int base = 10) — default arguments reduce the need for some overloads. Default args must be at the end of the parameter list and can't be specified in both declaration and definition. Overloading vs overriding: overloading = same name, different params, same or different class (compile-time); overriding = same name, same params, derived class virtual function (runtime).

Open this question on its own page
08

What is inheritance in C++?

Inheritance allows a class (derived/child) to inherit members and behaviors from another class (base/parent), enabling code reuse and "is-a" relationships. Syntax: class Animal { public: std::string name; Animal(std::string n) : name(n) {} virtual void speak() const { std::cout << name << " makes a sound\n"; } virtual ~Animal() = default; // Virtual destructor! }; class Dog : public Animal { public: Dog(std::string n) : Animal(n) {} void speak() const override { std::cout << name << " says: Woof!\n"; } void fetch() { std::cout << name << " fetches the ball!\n"; } };. Access inheritance types: public — public/protected base members keep their access; protected — public base members become protected; private — all base members become private. Public is standard. Calling base class methods: Dog::speak() { Animal::speak(); // Explicit base call std::cout << "Also barks\n"; }. Constructor chaining: derived class must call base constructor via initializer list: Dog(std::string n, std::string breed) : Animal(n), breed(breed) {}. Virtual destructor — critical: if you delete a derived object through a base pointer, the base destructor MUST be virtual or only the base destructor runs (resource leak/UB): Animal* a = new Dog("Rex"); delete a; // Calls ~Dog then ~Animal only if virtual. Multiple inheritance: class FlyingFish : public Fish, public Bird {} — C++ supports it. Diamond problem solved with virtual base classes.

Open this question on its own page
09

What are virtual functions and polymorphism in C++?

Virtual functions enable runtime polymorphism — the correct function is selected based on the actual type of the object pointed to (dynamic dispatch), not the declared type. How it works: when a class declares a virtual function, the compiler creates a vtable (virtual dispatch table) — an array of function pointers for all virtual functions. Each object of that class has a hidden vptr (vtable pointer) set in the constructor. At runtime, function calls via pointer/reference go through the vtable. class Shape { public: virtual double area() const = 0; // pure virtual virtual void draw() const { std::cout << "Drawing shape\n"; } virtual ~Shape() = default; }; class Circle : public Shape { double r; public: Circle(double r) : r(r) {} double area() const override { return 3.14159 * r * r; } void draw() const override { std::cout << "Drawing circle\n"; } }; class Square : public Shape { double side; public: Square(double s) : side(s) {} double area() const override { return side * side; } }; // Runtime polymorphism: std::vector<std::unique_ptr<Shape>> shapes; shapes.push_back(std::make_unique<Circle>(5)); shapes.push_back(std::make_unique<Square>(4)); for (auto& s : shapes) { std::cout << s->area() << "\n"; // Correct area() called! }. override keyword (C++11): explicitly marks a function as overriding a virtual — compiler error if no matching virtual base function. Always use override. final keyword: prevents further overriding: void draw() const final;. Pure virtual: = 0 — makes class abstract (cannot instantiate). Derived class must implement all pure virtuals to be concrete. Non-virtual function hiding: if a non-virtual function is "overridden" in derived class, it hides (not overrides) the base version.

Open this question on its own page
10

What are access specifiers in C++?

C++ has three access specifiers that control the visibility of class members: (1) public: accessible from anywhere — inside the class, derived classes, and external code. class Car { public: std::string brand; // Any code can access void drive() {} // Any code can call };; (2) private: (default for class) accessible only within the class itself and friend classes/functions. Not accessible from derived classes: class BankAccount { private: double balance = 0; // Only BankAccount methods can access public: void deposit(double a) { balance += a; } // OK double getBalance() const { return balance; } // OK };; (3) protected: accessible within the class and its derived classes, but NOT from external code: class Animal { protected: std::string name; // Accessible in Dog, Cat, etc. }; class Dog : public Animal { void bark() { std::cout << name; } // OK -- protected access };. In structs: default access is public (everything else same as class). Inheritance and access: public inheritance preserves public/protected access; protected inheritance makes public members protected; private inheritance makes all inherited members private. Friend functions/classes: declared inside a class with friend keyword — bypass access restrictions. Use sparingly as they break encapsulation: class Box { private: double volume; friend double calcVolume(const Box& b); };. Getters/setters: expose controlled access to private data: double getBalance() const; void setName(std::string n);.

Open this question on its own page
11

What is the Standard Template Library (STL)?

The STL (Standard Template Library) is a collection of generic, reusable components in C++ — containers, algorithms, and iterators — that work together through a unified interface. Containers: Sequence: vector (dynamic array, O(1) access, O(n) insert middle), deque (double-ended queue), list (doubly linked, O(1) insert anywhere, no random access), array (fixed-size stack array); Associative (sorted): set (unique keys), map (key-value, sorted), multiset, multimap — all O(log n) via red-black tree; Unordered (hash): unordered_set, unordered_map — O(1) average, O(n) worst; Adaptors: stack, queue, priority_queue. Algorithms (from <algorithm>): std::sort(v.begin(), v.end()); std::find(v.begin(), v.end(), 5); std::count_if(v.begin(), v.end(), [](int x){ return x > 0; }); std::transform(v.begin(), v.end(), out.begin(), [](int x){ return x*2; }); std::accumulate(v.begin(), v.end(), 0); std::binary_search(v.begin(), v.end(), 5); std::max_element(v.begin(), v.end());. Iterators: generalized pointers that abstract container traversal. for (auto it = v.begin(); it != v.end(); ++it) *it *= 2;. Or range-based for: for (auto& x : v) x *= 2;. Why STL matters: type-safe, efficient (hand-optimized), consistent interface across containers, composable with algorithms — the backbone of modern C++ development.

Open this question on its own page
12

What are templates in C++?

Templates enable generic programming — writing code that works with any type without repeating code. The compiler generates type-specific code at compile time (zero runtime overhead). Function templates: template<typename T> T maximum(T a, T b) { return (a > b) ? a : b; } // Usage: auto m1 = maximum(3, 7); // T = int auto m2 = maximum(3.14, 2.71); // T = double auto m3 = maximum(std::string("cat"), std::string("dog")); // T = string. Class templates: template<typename T> class Stack { std::vector<T> data; public: void push(T value) { data.push_back(value); } T pop() { T v = data.back(); data.pop_back(); return v; } bool empty() const { return data.empty(); } }; Stack<int> intStack; Stack<std::string> strStack;. Multiple type parameters: template<typename K, typename V> class Pair { public: K key; V value; };. Non-type template parameters: template<int N> class FixedArray { int data[N]; }; FixedArray<10> arr;. Template specialization: provide a specific implementation for a particular type: template<> void print<bool>(bool b) { std::cout << (b ? "true" : "false"); }. Variadic templates (C++11): template<typename... Args> void log(Args... args) { (std::cout << ... << args) << "\n"; } log(1, " + ", 2, " = ", 3);. Templates power the entire STL — containers and algorithms are all templates.

Open this question on its own page
13

What are smart pointers in C++?

Smart pointers (from <memory>) are RAII wrappers around raw pointers that automatically manage the lifetime of heap-allocated objects — preventing memory leaks and dangling pointers without manual delete. Three types: (1) std::unique_ptr: exclusive ownership — only one unique_ptr can point to an object at a time. Cannot be copied; can be moved. Object is destroyed when the unique_ptr goes out of scope: auto p = std::make_unique<Car>("Toyota", 2022); p->drive(); // Direct use -- no need to delete! // Pass ownership: std::unique_ptr<Car> p2 = std::move(p); // p is now null. Best for: single-owner resources, factory functions, class members; (2) std::shared_ptr: shared ownership — multiple shared_ptrs can point to the same object. Uses reference counting — object destroyed when the last shared_ptr goes away: auto sp1 = std::make_shared<Car>("Honda"); auto sp2 = sp1; // Both own the same Car std::cout << sp1.use_count(); // 2 sp1.reset(); // sp1 releases its share -- count = 1 // Car destroyed when sp2 goes out of scope. Slightly more expensive due to atomic reference count; (3) std::weak_ptr: non-owning observer — doesn't participate in reference counting. Breaks circular references (the main cause of shared_ptr leaks). Lock before use: std::weak_ptr<Car> wp = sp1; if (auto locked = wp.lock()) { locked->drive(); } // Safe to use. Golden rule: prefer make_unique / make_shared over new. Never use raw pointers for ownership.

Open this question on its own page
14

What is the difference between stack and heap memory in C++?

C++ programs use two main memory regions: Stack memory: fast, automatically managed, LIFO (Last In First Out). Local variables, function parameters, return addresses, and register saves are placed on the stack. void foo() { int x = 42; // Stack -- x lives until foo() returns Car c("Toyota", 2022); // Stack -- c destroyed at } } // x and c automatically destroyed here. Characteristics: allocated and freed automatically (function call/return); very fast (just move stack pointer); limited size (typically 1-8MB — stack overflow if exceeded); all variables destroyed when scope ends. Heap memory (Free Store): manual management, dynamically sized, large. Car* c = new Car("Toyota", 2022); // Heap -- exists until deleted c->drive(); delete c; // Must manually free! auto sp = std::make_unique<Car>("Honda"); // Heap via smart ptr. Characteristics: you control lifetime (allocate with new, free with delete/smart ptr); slower than stack (calls allocator, may need to search free blocks); large (limited by RAM/virtual memory); NOT automatically freed — forgetting delete = memory leak. When to use each: stack for small, short-lived data (primitives, small objects); heap for: large objects (can't fit in stack), unknown size at compile time (vectors, user input), objects that must outlive their creating function, sharing between parts of the program. Memory layout: Code → Data (globals) → Heap (grows up) ... Stack (grows down). Stack and heap grow toward each other — if they meet, crash.

Open this question on its own page
15

What are namespaces in C++?

Namespaces organize code and prevent name collisions — allowing different libraries or modules to have functions/classes with the same name without conflict. Defining a namespace: namespace Graphics { class Color { ... }; void drawLine(int x1, int y1, int x2, int y2); } namespace Audio { class Sound { ... }; void play(std::string filename); }. Accessing namespace members: Graphics::Color red; Graphics::drawLine(0, 0, 100, 100); Audio::play("music.mp3");. using declaration: bring a specific name into scope: using Graphics::drawLine; drawLine(0, 0, 100, 100); // OK now. using directive (use carefully): bring ALL names from a namespace: using namespace std; // Brings in EVERYTHING from std cout << "Hello"; // OK but pollutes namespace. Never use using namespace std in header files — it pollutes every file that includes the header. OK in .cpp implementation files. Nested namespaces: namespace Company::Project::Module { void function(); } // C++17 inline namespace. Inline namespace (C++11): members are accessible as if in the enclosing namespace — used for versioning: namespace API { inline namespace v2 { void func(); } } API::func(); // Calls v2::func. Anonymous namespace: namespace { int internalHelper() {} // Only visible in this translation unit }. Replaces C's static functions for file-local linkage. std namespace: all standard library components live in the std namespace — std::vector, std::string, std::cout.

Open this question on its own page
16

What are lambda expressions in C++?

Lambda expressions (C++11) are anonymous inline functions — closures that can capture variables from the surrounding scope. They eliminate the need for named helper functions for short, one-use operations. Syntax: [capture](parameters) -> return_type { body }. Examples: // Basic lambda auto square = [](int x) { return x * x; }; std::cout << square(5); // 25 // Capture by value (copy): int base = 10; auto addBase = [base](int x) { return x + base; }; // Capture by reference: auto addToBase = [&base](int x) { base += x; }; // Capture all by value: auto f = [=]() { return base * 2; }; // Capture all by ref: auto g = [&]() { base = 0; }; // Capture specific mix: auto h = [base, &total](int x) { total += base + x; };. With STL algorithms: std::vector<int> v = {5, 3, 8, 1, 9, 2}; std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // Descending auto sum = std::accumulate(v.begin(), v.end(), 0, [](int acc, int x) { return acc + x; }); auto it = std::find_if(v.begin(), v.end(), [](int x) { return x > 5; });. Generic lambda (C++14): auto print = [](auto x) { std::cout << x; }; print(42); print("hello");. Immediately invoked: int result = [](int a, int b) { return a + b; }(3, 4); // 7. Mutable: by default, captured values are const in the lambda body. Use mutable: auto f = [count = 0]() mutable { return ++count; };. Under the hood: compiler generates an unnamed closure class with operator() for each lambda.

Open this question on its own page
17

What is the const keyword in C++?

The const keyword declares that a value cannot be modified after initialization, enabling compiler-enforced immutability and enabling optimizations. Const variables: const int MAX_SIZE = 100; // Must be initialized MAX_SIZE = 200; // ERROR!. Const pointers (four combinations): const int* p1 = &x; // Pointer to const int -- can't change *p1 int* const p2 = &x; // Const pointer to int -- can't change p2 const int* const p3 = &x; // Const pointer to const -- can't change either int* p4 = &x; // Neither const -- can change both. Memory aid: read right to left: "p3 is a const pointer to const int." Const function parameters: void printName(const std::string& name) { // name is read-only std::cout << name; }. Pass by const reference: efficient (no copy) + safe (can't modify). Const member functions: a function that doesn't modify object state — can be called on const objects: class Car { std::string brand; public: std::string getBrand() const { return brand; } // Const function void setBrand(std::string b) { brand = b; } // Non-const }; const Car c("Toyota"); c.getBrand(); // OK -- const function c.setBrand("Honda"); // ERROR -- non-const on const object. mutable: a member declared mutable can be modified even in const functions (for caching/lazy initialization): mutable int cacheCount = 0;. constexpr (C++11): evaluated at compile time: constexpr int square(int x) { return x*x; } constexpr int val = square(5); // 25 at compile time.

Open this question on its own page
18

What is RAII in C++?

RAII (Resource Acquisition Is Initialization) is the most fundamental C++ idiom. The idea: tie the lifetime of a resource (memory, file handles, mutex locks, network connections) to the lifetime of an object — acquire in the constructor, release in the destructor. Since C++ guarantees destructors run when objects go out of scope (even during exceptions), resources are always properly released. Classic problem without RAII: void badFunction() { FILE* f = fopen("data.txt", "r"); processFile(f); // What if this throws? fclose(f); // May never run -- RESOURCE LEAK! }. RAII solution (std::fstream): void goodFunction() { std::ifstream f("data.txt"); processFile(f); // f.close() called automatically on scope exit, even if exception! }. Custom RAII class: class FileGuard { FILE* file; public: FileGuard(const char* path, const char* mode) : file(fopen(path, mode)) { if (!file) throw std::runtime_error("Can't open file"); } ~FileGuard() { if (file) fclose(file); } // Destructor releases // Non-copyable (owns unique resource): FileGuard(const FileGuard&) = delete; FileGuard& operator=(const FileGuard&) = delete; FILE* get() const { return file; } };. RAII in the standard library: std::unique_ptr/std::shared_ptr (memory), std::lock_guard/std::unique_lock (mutex), std::fstream (files), std::vector/std::string (memory). Why RAII matters: exception safety — resources released even when exceptions propagate; correct behavior in all code paths without try/finally; no need for garbage collection — deterministic destruction.

Open this question on its own page
19

What is exception handling in C++?

C++ exception handling provides a mechanism to handle runtime errors gracefully — separating error-handling code from normal logic. Mechanism: try { // Code that might throw int* arr = new int[1000000000]; // Might fail if (arr == nullptr) throw std::bad_alloc(); processFile("data.txt"); // Might throw std::ios_base::failure if (age < 0) throw std::invalid_argument("Age can't be negative"); } catch (const std::invalid_argument& e) { std::cerr << "Invalid argument: " << e.what() << "\n"; } catch (const std::bad_alloc& e) { std::cerr << "Out of memory: " << e.what() << "\n"; } catch (const std::exception& e) { std::cerr << "Standard exception: " << e.what() << "\n"; } catch (...) { std::cerr << "Unknown exception!\n"; }. Exception hierarchy: std::exception is the base. Key subclasses: std::runtime_error (logic_error, overflow_error), std::bad_alloc, std::bad_cast, std::ios_base::failure. Catch by const reference to avoid slicing. Custom exceptions: class DatabaseError : public std::runtime_error { public: DatabaseError(const std::string& msg) : std::runtime_error("DB Error: " + msg) {} }; throw DatabaseError("Connection failed");. noexcept (C++11): declare a function won't throw: double sqrt(double x) noexcept;. Allows compiler optimizations; terminates if exception is thrown from noexcept function. Exception safety guarantees: No-throw (never throws — strongest); Strong (commit or rollback — exception leaves no observable state change); Basic (no leaks but state may change); No guarantee (weakest). Prefer designing functions with strong or at least basic guarantees.

Open this question on its own page
20

What are iterators in C++?

Iterators are objects that generalize pointers — they provide a uniform way to traverse elements of any container regardless of the underlying data structure. Algorithms operate on iterators, not containers directly, enabling them to work with any container type. Iterator categories (from weakest to strongest): (1) Input — single pass, read only (std::istream_iterator); (2) Output — single pass, write only (std::ostream_iterator); (3) Forward — multi-pass, read/write (std::forward_list); (4) Bidirectional — forward + backward (std::list, std::set, std::map); (5) Random Access — jump to any position in O(1) (std::vector, std::deque, raw arrays); (6) Contiguous (C++17) — random access + contiguous memory guarantee (std::vector, std::array). Basic usage: std::vector<int> v = {1, 2, 3, 4, 5}; // Iterator-based loop: for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) { std::cout << *it << " "; } // Modern auto: for (auto it = v.begin(); it != v.end(); ++it) { *it *= 2; } // Range-based for (preferred when possible): for (auto& x : v) { x *= 2; } // Reverse iteration: for (auto it = v.rbegin(); it != v.rend(); ++it) { std::cout << *it << " "; } // Const iterators (read-only): for (auto it = v.cbegin(); it != v.cend(); ++it) { std::cout << *it; }. Iterator arithmetic (random access only): auto it = v.begin(); it += 3; // Jump to 4th element std::cout << *(it - 1); // 3rd element auto mid = v.begin() + v.size()/2;. std::advance, std::distance, std::next, std::prev: work with any iterator category: std::advance(it, 3); auto dist = std::distance(v.begin(), v.end()); auto next = std::next(it, 2);

Open this question on its own page
Intermediate 16 questions

Practical knowledge for developers with hands-on experience.

01

What is move semantics in C++?

Move semantics (C++11) allow transferring resources from a temporary (rvalue) object to another object instead of copying — dramatically improving performance when dealing with expensive-to-copy objects like vectors, strings, and unique_ptrs. The problem before C++11: returning a vector from a function caused a full deep copy: std::vector<int> makeData() { std::vector<int> v(1000000); /* ... */ return v; // Copy of 1M ints before C++11 }. Rvalue references (&&): bind to temporaries (rvalues — things you can't take the address of): void process(std::vector<int>&& v) { /* v is moved-from */ }. Move constructor and move assignment: class Buffer { char* data; size_t size; public: // Move constructor: Buffer(Buffer&& other) noexcept : data(other.data), size(other.size) { other.data = nullptr; // Leave other in valid but empty state other.size = 0; } // Move assignment: Buffer& operator=(Buffer&& other) noexcept { if (this != &other) { delete[] data; // Release own resources data = other.data; size = other.size; other.data = nullptr; other.size = 0; } return *this; } };. std::move: casts an lvalue to rvalue reference — enabling move: std::vector<int> v1(1000000); std::vector<int> v2 = std::move(v1); // Moves data, v1 is now empty std::cout << v2.size(); // 1000000 std::cout << v1.size(); // 0. Perfect forwarding: template<typename T> void wrapper(T&& arg) { target(std::forward<T>(arg)); // Preserves lvalue/rvalue-ness }. RVO/NRVO: compiler often elides copies entirely (Return Value Optimization) — move semantics are the fallback when RVO can't apply.

Open this question on its own page
02

What are lvalues and rvalues in C++?

Value categories classify expressions in C++. Understanding them is essential for move semantics. Lvalue (locator value): an expression that identifies a persistent object with a memory address — can appear on the left side of assignment. int x = 42; // x is an lvalue int& ref = x; // Can take address: &x std::string s = "hello"; // s is an lvalue s = "world"; // OK -- persistent object int arr[5]; arr[2] = 10; // arr[2] is an lvalue. Rvalue (right-hand value): a temporary expression with no persistent memory address — appears on the right side, cannot be assigned to. int y = 42 + 8; // 42+8 is rvalue (temporary) std::string s = std::string("temp"); // "temp" literal + string() are rvalues &42; // ERROR -- can't take address of rvalue. Rvalue reference (&&): binds specifically to rvalues, enabling move semantics: int&& rref = 42; // Binds to rvalue -- extends lifetime void func(int&& x) { /* only called with rvalue */ }. C++11 value categories (full picture): lvalue (named variables, dereferenced pointers), prvalue (pure rvalue — literals, temporaries), xvalue (eXpiring value — std::move result), glvalue (generalized lvalue = lvalue or xvalue), rvalue (prvalue or xvalue). Reference binding rules: lvalue reference (T&) binds to lvalues; const lvalue reference (const T&) binds to both lvalues and rvalues (extends lifetime of temporaries!); rvalue reference (T&&) binds to rvalues. Practical implication: when you pass a named variable to std::move(), it becomes an xvalue — the object remains alive but signals "you can steal my resources."

Open this question on its own page
03

What is the Rule of Three/Five/Zero in C++?

These rules guide when and how to implement special member functions (constructor, destructor, copy/move operations): Rule of Three (pre-C++11): if you define any of these three, define all three: (1) Destructor, (2) Copy constructor, (3) Copy assignment operator. Needed when a class manually manages a resource (raw pointer, file handle). class String { char* data; size_t len; public: ~String() { delete[] data; } // Destructor String(const String& o) : data(new char[o.len+1]), len(o.len) { std::memcpy(data, o.data, len+1); } // Copy ctor String& operator=(const String& o) { // Copy assign if (this != &o) { delete[] data; data = new char[o.len+1]; len = o.len; std::memcpy(data, o.data, len+1); } return *this; } };. Rule of Five (C++11+): if you define any of the three above, also define: (4) Move constructor, (5) Move assignment operator — for efficiency: String(String&& o) noexcept : data(o.data), len(o.len) { o.data = nullptr; o.len = 0; } String& operator=(String&& o) noexcept { if (this != &o) { delete[] data; data = o.data; len = o.len; o.data = nullptr; o.len = 0; } return *this; }. Rule of Zero (preferred): design classes so they don't need any custom special member functions — use RAII wrappers (smart pointers, std::string, std::vector) as members. The compiler-generated versions will be correct: class Person { std::string name; // Handles its own memory int age; std::unique_ptr<Address> address; // Owns address }; // No special member functions needed!. The Rule of Zero is the cleanest — prefer it by using standard containers and smart pointers as members.

Open this question on its own page
04

What is template specialization and SFINAE in C++?

Template specialization provides a custom implementation for a specific type while the general template handles everything else: // Primary template: template<typename T> std::string toString(T value) { return std::to_string(value); } // Full specialization for bool: template<> std::string toString<bool>(bool value) { return value ? "true" : "false"; } // Partial specialization (class templates only): template<typename T> class Container<T*> { // Specialized for pointer types };. SFINAE (Substitution Failure Is Not An Error): when a template substitution fails (e.g., a type doesn't have a member), the compiler silently removes that overload from consideration instead of producing an error. Enables conditional template behavior: // Enable this function only if T is integral: template<typename T> std::enable_if_t<std::is_integral_v<T>, void> process(T value) { std::cout << "Integral: " << value; } // Enabled only for floating point: template<typename T> std::enable_if_t<std::is_floating_point_v<T>, void> process(T value) { std::cout << "Float: " << value; }. C++20 Concepts (replace SFINAE): cleaner syntax: template<std::integral T> void process(T value) { std::cout << "Integral: " << value; } template<std::floating_point T> void process(T value) { std::cout << "Float: " << value; } // Custom concept: template<typename T> concept Printable = requires(T t) { std::cout << t; }; template<Printable T> void print(T value) { std::cout << value; }. Type traits (<type_traits>): compile-time type information: std::is_integral_v<int>, std::is_pointer_v<int*>, std::is_same_v<T, U>, std::decay_t<T>, std::remove_const_t<T>. These are the building blocks of template metaprogramming.

Open this question on its own page
05

What is std::vector and how does it work internally?

std::vector is the most used STL container — a dynamic array that grows automatically. Understanding its internals explains its performance characteristics. Internal structure: three pointers: begin (pointer to first element), end (pointer past last element), capacity_end (pointer to end of allocated memory). All elements stored in a contiguous block of heap memory. Size vs capacity: std::vector<int> v; v.reserve(100); // Allocate for 100 without adding elements std::cout << v.size(); // 0 -- no elements std::cout << v.capacity(); // 100+ -- allocated space. Growth strategy (amortized O(1) push_back): when capacity is exceeded, vector allocates a new block (typically 1.5x or 2x larger), copies/moves all elements, frees old block. Example: capacity 1 → 2 → 4 → 8 → 16... Total copy operations for n pushes: O(n) amortized → O(1) per push. Key operations and complexity: v.push_back(x); // O(1) amortized v.pop_back(); // O(1) v[i]; // O(1) -- random access v.insert(it, x); // O(n) -- shifts elements v.erase(it); // O(n) -- shifts elements v.clear(); // O(n) -- destroys elements v.size(); // O(1) v.empty(); // O(1) v.begin()/end(); // O(1). Reserve to avoid reallocations: v.reserve(1000); // Single allocation for (int i = 0; i < 1000; ++i) v.push_back(i); // No reallocations!. Shrink to fit: v.shrink_to_fit() — release excess capacity. emplace_back vs push_back: emplace_back constructs in-place (no temporary): v.emplace_back(42); // Constructs directly v.push_back(MyClass(42)); // Constructs temporary then moves. Memory layout: contiguous memory enables cache-friendly iteration and pointer arithmetic — vector is often faster than list even for insert/delete due to cache effects.

Open this question on its own page
06

What is std::map vs std::unordered_map?

Both are key-value associative containers but with different internal structures and performance: std::map (ordered): implemented as a self-balancing binary search tree (typically Red-Black tree). Elements always sorted by key. std::map<std::string, int> wordCount; wordCount["apple"] = 5; wordCount["banana"] = 3; wordCount.insert({"cherry", 7}); wordCount.emplace("date", 2); // Access: std::cout << wordCount["apple"]; // 5 -- inserts 0 if not found! auto it = wordCount.find("banana"); // Returns iterator or end() if (it != wordCount.end()) std::cout << it->second; // Iteration (always sorted): for (auto& [key, val] : wordCount) { // Structured bindings std::cout << key << ": " << val << "\n"; }. Complexity: O(log n) for insert/find/erase. Use when: need sorted order, need ordered range queries (lower_bound, upper_bound). std::unordered_map (hash map): implemented as a hash table with chaining. No ordering guarantee. std::unordered_map<std::string, int> wordCount; // Same interface, but: // O(1) average, O(n) worst case for all operations wordCount.reserve(1000); // Pre-allocate buckets wordCount.max_load_factor(0.7); // Control rehashing. Complexity: O(1) average for insert/find/erase. Use when: O(1) lookups needed, don't care about order, keys are hashable. Custom hash: struct PairHash { size_t operator()(const std::pair<int,int>& p) const { return std::hash<int>{}(p.first) ^ std::hash<int>{}(p.second) << 32; } }; std::unordered_map<std::pair<int,int>, int, PairHash> m;. Summary: map for ordered operations and range queries; unordered_map for fast lookups. unordered_map is typically 3-10x faster for typical workloads.

Open this question on its own page
07

What is operator overloading in C++?

Operator overloading defines custom behavior for operators when applied to user-defined types — making them work naturally like built-in types. class Vector2D { public: double x, y; Vector2D(double x = 0, double y = 0) : x(x), y(y) {} // Addition: Vector2D operator+(const Vector2D& other) const { return Vector2D(x + other.x, y + other.y); } // Compound assignment: Vector2D& operator+=(const Vector2D& other) { x += other.x; y += other.y; return *this; } // Equality: bool operator==(const Vector2D& other) const { return x == other.x && y == other.y; } bool operator!=(const Vector2D& other) const { return !(*this == other); } // Unary negation: Vector2D operator-() const { return Vector2D(-x, -y); } // Subscript: double& operator[](int i) { return i == 0 ? x : y; } // Comparison (C++20 -- spaceship operator): auto operator<=>(const Vector2D&) const = default; // Friend: output stream: friend std::ostream& operator<<(std::ostream& os, const Vector2D& v) { return os << "(" << v.x << ", " << v.y << ")"; } }; // Usage: Vector2D a(1, 2), b(3, 4); Vector2D c = a + b; // (4, 6) a += b; std::cout << c << "\n"; // (4, 6). Rules: can't overload: ::, ., .*, ?:; can't change arity or precedence; at least one operand must be a user-defined type. Member vs free function: use member for operators that modify the object (+=, [], (), ->); use free (friend) for symmetric operators (+, -, ==, <<) to allow commutativity. Spaceship operator (<=>, C++20): define once, get all six comparison operators automatically.

Open this question on its own page
08

What is multithreading in C++?

C++11 standardized the threading library (<thread>, <mutex>, <condition_variable>, <future>) for portable multithreaded programming. std::thread: #include <thread> void worker(int id) { std::cout << "Thread " << id << " running\n"; } int main() { std::thread t1(worker, 1); // Launch thread std::thread t2(worker, 2); t1.join(); // Wait for t1 to finish t2.join(); // join() or detach() required! }. Race conditions — use mutex: #include <mutex> std::mutex mtx; int counter = 0; void increment() { for (int i = 0; i < 1000; ++i) { std::lock_guard<std::mutex> lock(mtx); // RAII lock ++counter; // Protected critical section } // Lock automatically released }. std::lock_guard vs std::unique_lock: lock_guard — simpler, non-moveable, locks until scope ends; unique_lock — more flexible (defer lock, try_lock, timed_lock, usable with condition variables, moveable). Condition variables: std::condition_variable cv; std::mutex mtx; bool ready = false; // Producer: { std::unique_lock<std::mutex> lk(mtx); ready = true; cv.notify_all(); } // Consumer: std::unique_lock<std::mutex> lk(mtx); cv.wait(lk, []{ return ready; }); // Wait until ready. std::async and std::future: #include <future> auto fut = std::async(std::launch::async, [](int a, int b) { return a + b; }, 3, 4); int result = fut.get(); // Blocks until ready std::cout << result; // 7. std::atomic: lock-free atomic operations: std::atomic<int> counter{0}; counter++; // Thread-safe without mutex. Deadlock prevention: always acquire locks in the same order; use std::scoped_lock(m1, m2) (C++17) to lock multiple mutexes atomically.

Open this question on its own page
09

What is the difference between shallow copy and deep copy?

When copying an object that contains pointer members, the behavior depends on whether you do a shallow or deep copy: Shallow copy: copies only the pointer value — both objects point to the same underlying data. The compiler-generated copy constructor performs a shallow copy. class ShallowArray { int* data; int size; public: ShallowArray(int s) : data(new int[s]), size(s) {} // Default copy constructor (shallow): // Copies data pointer, not the array // Both original and copy point to same memory! }; ShallowArray a(5); ShallowArray b = a; // Shallow -- b.data == a.data delete[] a.data; // Now b.data is dangling!. Problems: double deletion (undefined behavior when both objects are destroyed), modifying through one object affects the other. Deep copy: allocates new memory and copies the contents: class DeepArray { int* data; int size; public: DeepArray(int s) : size(s), data(new int[s]) {} // Deep copy constructor: DeepArray(const DeepArray& other) : size(other.size), data(new int[other.size]) { std::copy(other.data, other.data + size, data); } // Deep copy assignment: DeepArray& operator=(const DeepArray& other) { if (this != &other) { delete[] data; size = other.size; data = new int[size]; std::copy(other.data, other.data + size, data); } return *this; } ~DeepArray() { delete[] data; } }; DeepArray a(5); DeepArray b = a; // Deep -- separate copy of array. Modern solution: use std::vector<int> instead of raw arrays — its copy constructor performs a deep copy automatically (Rule of Zero). Shallow copy is appropriate when sharing ownership (std::shared_ptr handles this with reference counting).

Open this question on its own page
10

What are static members in C++?

Static members belong to the class itself rather than any individual object. Shared by all instances — exists independently of any object creation. Static data member: class Counter { static int count; // Declaration public: Counter() { ++count; } ~Counter() { --count; } static int getCount() { return count; } }; int Counter::count = 0; // Definition and initialization (outside class, in .cpp) Counter c1, c2, c3; std::cout << Counter::getCount(); // 3 -- class-level access. Inline static member (C++17): class Config { public: inline static const std::string VERSION = "1.0"; // No separate .cpp definition needed inline static int maxRetries = 3; };. Static member functions: can be called without an object; no this pointer (can only access static members directly): class Math { public: static double pi() { return 3.14159265358979; } static int abs(int x) { return x < 0 ? -x : x; } }; double p = Math::pi(); // Called on class, not object. Singleton pattern (common static use): class Database { static Database* instance; Database() {} // Private constructor public: static Database& getInstance() { static Database inst; // Thread-safe since C++11 (magic statics) return inst; } };. Static local variables: initialized once on first call, persists for program lifetime — different from class-level statics: int generateId() { static int id = 0; // Initialized once return ++id; // Returns 1, 2, 3, ... each call }. Key uses: shared counters, caches, singletons, factory methods, constants, utility functions that don't need object state.

Open this question on its own page
11

What are the differences between struct and class in C++?

In C++, struct and class are nearly identical — only one default difference: The only difference: struct has public members and public inheritance by default; class has private members and private inheritance by default. struct Point { int x; int y; }; // x and y are public class Point { int x; int y; }; // x and y are PRIVATE struct Point { }; // Same as class Point : public Base {}; // class Point : private Base {}; // Same as. That's it — struct supports all C++ features: constructors, destructors, virtual functions, templates, inheritance, access specifiers. Idiomatic usage (convention, not language rule): use struct for: passive data aggregates without invariants (POD — Plain Old Data), simple data holders, things conceptually "like C structs," function parameter bundles, return multiple values. struct Point { double x, y; }; struct Config { std::string host; int port = 8080; bool ssl = true; };. Use class for: objects with behavior, encapsulation, invariants, non-trivial lifetimes. class BankAccount { // invariant: balance >= 0 double balance; public: void deposit(double amount); };. POD (Plain Old Data) types: structs with no user-defined constructors, no virtual functions, no reference members, no private/protected members — layout-compatible with C. Can be memcpy'd. Aggregate initialization: Point p = {1.0, 2.0}; // Requires aggregate type. std::pair and std::tuple: standard library provides pre-built value aggregates for most cases.

Open this question on its own page
12

What is type casting in C++?

C++ provides four named cast operators that are safer and more descriptive than C-style casts: 1. static_cast — compile-time cast: checked at compile time. Safe for related types (numeric conversions, hierarchy casts, void* to typed pointer). double d = 3.14; int i = static_cast<int>(d); // 3 -- truncates Base* b = static_cast<Base*>(derivedPtr); // Upcast -- always safe void* v = ptr; int* p = static_cast<int*>(v); // void* to typed. 2. dynamic_cast — runtime polymorphic cast: validates at runtime via RTTI. Requires polymorphic type (at least one virtual function). Returns nullptr (pointer) or throws std::bad_cast (reference) on failure. Base* b = new Derived(); Derived* d = dynamic_cast<Derived*>(b); // OK if (!d) { /* b isn't a Derived */ } Animal* a = new Cat(); Dog* dog = dynamic_cast<Dog*>(a); // nullptr -- not a Dog. Avoid dynamic_cast in hot paths — has runtime overhead. Prefer virtual dispatch or std::variant. 3. const_cast — add/remove const: ONLY cast that can remove const. Very narrow use: call a non-const function on a const object when you know it won't modify: void print(char* s); // Legacy non-const param const char* cs = "hello"; print(const_cast<char*>(cs)); // Use only if print doesn't modify!. Undefined behavior to actually modify a const object via const_cast. 4. reinterpret_cast — bitwise reinterpretation: lowest-level, most dangerous. Reinterprets bit pattern as a different type. Only safe for specific patterns (round-trip through void*): int n = 42; char* bytes = reinterpret_cast<char*>(&n); // Examine bytes. C-style cast (avoid): (int)3.14 — tries static_cast, then const_cast, then reinterpret_cast — unpredictable and unsafe. Always use named casts.

Open this question on its own page
13

What are abstract classes and interfaces in C++?

An abstract class contains at least one pure virtual function (= 0) — it cannot be instantiated directly. Derived classes must implement all pure virtual functions to become concrete. class Shape { // Abstract class public: virtual double area() const = 0; // Pure virtual virtual double perimeter() const = 0; // Pure virtual virtual void draw() const { // Non-pure virtual -- has default impl std::cout << "Drawing a shape\n"; } virtual ~Shape() = default; // Virtual destructor! }; // Can't do: Shape s; // ERROR -- abstract class // Concrete subclass must implement all pure virtuals: class Circle : public Shape { double radius; public: Circle(double r) : radius(r) {} double area() const override { return 3.14159 * radius * radius; } double perimeter() const override { return 2 * 3.14159 * radius; } };. "Interface" in C++: C++ has no interface keyword (unlike Java). An interface is modeled as an abstract class with ONLY pure virtual functions and a virtual destructor: class Drawable { public: virtual void draw() const = 0; virtual ~Drawable() = default; }; class Serializable { public: virtual std::string serialize() const = 0; virtual void deserialize(const std::string& data) = 0; virtual ~Serializable() = default; }; // Multiple "interfaces": class Document : public Drawable, public Serializable { void draw() const override { /* ... */ } std::string serialize() const override { /* ... */ } void deserialize(const std::string& data) override { /* ... */ } };. Why virtual destructor: without it, deleting a derived object through a base pointer calls only the base destructor — resource leak. Shape* s = new Circle(5); delete s; // Calls ~Circle then ~Shape only if ~Shape is virtual.

Open this question on its own page
14

What is std::string and common string operations?

std::string is C++'s managed string class — automatically handles memory. Always prefer over C-style char* strings. Creation: std::string s1 = "hello"; std::string s2("world"); std::string s3(5, '*'); // "*****" std::string s4 = s1 + " " + s2; // "hello world". Common operations: // Size and access: s1.size() / s1.length(); // 5 s1.empty(); // false s1[0]; // 'h' -- no bounds check s1.at(0); // 'h' -- bounds checked (throws) s1.front(); s1.back(); // Modifying: s1 += " there"; // Append s1.append(" more"); s1.push_back('!'); s1.insert(5, " new"); // Insert at position s1.erase(5, 4); // Erase 4 chars from pos 5 s1.replace(0, 5, "hi"); // Replace range with new string s1.clear(); // Searching: s1.find("world"); // Position or string::npos s1.rfind("o"); // From right s1.find_first_of("aeiou"); // First vowel // Substrings: s1.substr(6, 5); // 5 chars starting at pos 6 // Comparison: s1 == s2; s1 < s2; s1.compare(s2); // -1, 0, 1 // Conversion: int n = std::stoi("42"); double d = std::stod("3.14"); std::string str = std::to_string(42); // std::string_view (C++17) -- non-owning reference: std::string_view sv = s1; // No copy -- just a view. std::string vs const char*: string manages its own memory (safe), supports all operations, can be empty. Use string_view for read-only string parameters — avoids copies. SSO (Small String Optimization): short strings (typically <15-23 chars) are stored inline within the string object — no heap allocation, very fast.

Open this question on its own page
15

What are function pointers and std::function in C++?

Function pointers store the address of a function and allow calling functions indirectly. Basic function pointer: int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } // Declare: return_type (*ptr_name)(param_types) = &function; int (*op)(int, int) = &add; std::cout << op(3, 4); // 7 op = subtract; // Reassign std::cout << op(3, 4); // -1 // Typedef for readability: typedef int (*BinaryOp)(int, int); BinaryOp operations[] = {add, subtract};. std::function (C++11): a type-erased wrapper that can hold any callable — function pointer, lambda, functor, member function: #include <functional> std::function<int(int, int)> op; // Holds int(int,int) callable op = add; // Regular function op = [](int a, int b) { return a * b; }; // Lambda struct Multiplier { int operator()(int a, int b) { return a * b; } }; op = Multiplier{}; // Functor std::cout << op(3, 4); // Works for all!. Storing in containers: std::map<std::string, std::function<double(double)>> mathFns; mathFns["sin"] = std::sin; mathFns["cos"] = std::cos; mathFns["square"] = [](double x) { return x * x; }; mathFns["sin"](3.14159); // Call by name. std::function overhead: std::function uses type erasure — heap allocation and virtual dispatch. For performance-critical code, use auto with lambdas or templates instead: template<typename F> void applyTo(std::vector<int>& v, F func) { for (auto& x : v) x = func(x); }. Binding member functions: std::function<void(int)> f = std::bind(&MyClass::method, &obj, std::placeholders::_1);.

Open this question on its own page
16

What are design patterns commonly used in C++?

Key design patterns implemented idiomatically in C++: 1. Singleton: one instance only. C++11 magic statics make this thread-safe: class Logger { static Logger& instance() { static Logger log; return log; } Logger() = default; public: static Logger& get() { return instance(); } void log(std::string msg) { std::cout << msg; } }; Logger::get().log("hello");. 2. Factory Method: create objects without specifying exact class: class ShapeFactory { public: static std::unique_ptr<Shape> create(std::string type) { if (type == "circle") return std::make_unique<Circle>(); if (type == "square") return std::make_unique<Square>(); throw std::invalid_argument("Unknown shape"); } };. 3. Observer: notify multiple subscribers of changes: class EventEmitter { std::vector<std::function<void(int)>> listeners; public: void subscribe(std::function<void(int)> listener) { listeners.push_back(listener); } void emit(int event) { for (auto& l : listeners) l(event); } };. 4. CRTP (Curiously Recurring Template Pattern) — static polymorphism: template<typename Derived> class Base { void interface() { static_cast<Derived*>(this)->implementation(); } }; class Derived : public Base<Derived> { void implementation() { /* ... */ } };. No vtable overhead — compile-time dispatch. 5. RAII + Policy-based design: inject behavior through templates (Policy classes) for maximum flexibility without runtime overhead. 6. Type Erasure (std::any, std::variant, std::function): store heterogeneous types without inheritance.

Open this question on its own page
Advanced 8 questions

Deep expertise questions for senior and lead roles.

01

What is undefined behavior in C++ and how do you avoid it?

Undefined behavior (UB) occurs when a C++ program violates the language rules — the standard makes no guarantee about what happens. The compiler assumes UB never occurs and may optimize around it, producing surprising results: crashes, wrong output, security vulnerabilities, or seemingly correct behavior that breaks on a different compiler/optimization level. Common sources of UB: (1) Out-of-bounds access: int arr[5]; arr[5] = 10; // UB -- buffer overflow; (2) Null/dangling pointer dereference: int* p = nullptr; *p = 5; // UB delete p; *p = 5; // UB -- use after free; (3) Integer overflow (signed): int x = INT_MAX; x++; // UB! (unsigned overflow is defined -- wraps); (4) Uninitialized variable use: int x; std::cout << x; // UB -- garbage value; (5) Strict aliasing violation: accessing memory through a pointer of a different type: int i = 42; float* f = reinterpret_cast<float*>(&i); *f; // UB; (6) Data races: two threads access same non-atomic variable, one writes; (7) Shift by negative/overflow: int x = 1 << -1; // UB; 1 << 32 // UB on 32-bit; (8) Modifying a string literal; (9) Double-free: calling delete twice on the same pointer. Detection tools: AddressSanitizer (-fsanitize=address) — finds memory errors at runtime; UBSanitizer (-fsanitize=undefined) — detects signed overflow, null deref, OOB; ThreadSanitizer (-fsanitize=thread); Valgrind; static analyzers (clang-tidy, cppcheck). Avoidance: use smart pointers, std::vector/string (eliminate raw arrays), -Wall -Wextra flags, sanitizers in CI, code reviews, modern C++ idioms.

Open this question on its own page
02

What is template metaprogramming in C++?

Template Metaprogramming (TMP) is a programming technique where templates perform computations at compile time — moving work from runtime to compile time for zero-overhead abstractions. Compile-time factorial (classic TMP): template<int N> struct Factorial { static constexpr int value = N * Factorial<N-1>::value; }; template<> // Base case specialization struct Factorial<0> { static constexpr int value = 1; }; static_assert(Factorial<5>::value == 120); // Computed at compile time!. Modern approach — constexpr functions (C++11+): constexpr int factorial(int n) { return n <= 1 ? 1 : n * factorial(n - 1); } constexpr int f5 = factorial(5); // 120 -- compile time. Easier to read and debug than TMP structs. Type-level programming: // Type list: template<typename...> struct TypeList {}; // Get head: template<typename T, typename... Rest> struct Head<TypeList<T, Rest...>> { using type = T; }; // Conditional type selection: template<bool Cond, typename Then, typename Else> struct Conditional { using type = Then; }; template<typename Then, typename Else> struct Conditional<false, Then, Else> { using type = Else; }; using MyType = Conditional<sizeof(int)==4, int32_t, int64_t>::type;. std::tuple internals: recursive variadic template: template<typename T, typename... Rest> class tuple : tuple<Rest...> { T value; };. if constexpr (C++17): replaces SFINAE for compile-time branching: template<typename T> void process(T value) { if constexpr (std::is_integral_v<T>) { // Only compiled for integral types } else { // Only compiled for non-integral types } }. TMP powers the STL, Boost.MPL, and many high-performance libraries.

Open this question on its own page
03

What are C++20 features?

C++20 is one of the largest updates to the language. Key features: 1. Concepts: constrain template parameters with readable requirements: template<typename T> concept Numeric = std::integral<T> || std::floating_point<T>; template<Numeric T> T sum(T a, T b) { return a + b; } // Requires clause: template<typename T> requires requires(T t) { t.serialize(); t.size(); } void process(T obj) { obj.serialize(); }; 2. Ranges: composable, lazy range algorithms: #include <ranges> std::vector<int> v = {1,2,3,4,5,6,7,8,9,10}; auto result = v | std::views::filter([](int x){ return x % 2 == 0; }) | std::views::transform([](int x){ return x * x; }) | std::views::take(3); // Lazy -- no intermediate containers for (int x : result) std::cout << x << " "; // 4 16 36; 3. Coroutines: suspendable functions: cppcoro::generator<int> fibonacci() { int a = 0, b = 1; while (true) { co_yield a; std::tie(a, b) = std::make_tuple(b, a + b); } }; 4. Modules: replace header files with faster, cleaner module system: // math.cppm export module math; export int add(int a, int b) { return a + b; } // main.cpp import math; int x = add(3, 4);; 5. std::span: non-owning view over contiguous memory: void process(std::span<const int> data) { for (int x : data) std::cout << x; } process(std::vector<int>{1,2,3}); // Works process(std::array<int,3>{1,2,3}); // Works; 6. std::format: type-safe printf-like formatting: std::string s = std::format("Hello {}, you are {} years old", name, age); std::format("{:.2f}", 3.14159); // "3.14"; 7. Three-way comparison (<=> spaceship operator): auto operator<=>(const MyClass&) const = default; // All 6 comparisons!; 8. consteval, constinit; 9. Abbreviated function templates: auto square(auto x) { return x * x; }; 10. std::jthread: joinable thread that auto-joins on destruction + supports stop tokens.

Open this question on its own page
04

What is memory alignment and padding in C++?

Memory alignment is the requirement that data of a given type be stored at memory addresses that are multiples of the type's alignment requirement. Modern CPUs access aligned data more efficiently — misaligned access may require multiple memory operations or cause hardware faults on some architectures. Alignment of types: alignof(char) == 1 // 1-byte aligned alignof(int) == 4 // 4-byte aligned alignof(double) == 8 // 8-byte aligned. Struct padding: the compiler inserts padding bytes to ensure each member is properly aligned: struct Wasteful { char a; // 1 byte // 3 bytes padding (int needs 4-byte alignment) int b; // 4 bytes char c; // 1 byte // 3 bytes padding (struct end aligned to largest member) }; // sizeof(Wasteful) = 12 struct Efficient { int b; // 4 bytes char a; // 1 byte char c; // 1 byte // 2 bytes padding }; // sizeof(Efficient) = 8. Rule: order struct members from largest to smallest alignment to minimize padding. Checking: static_assert(sizeof(Wasteful) == 12); static_assert(offsetof(Wasteful, b) == 4); // 3 bytes padding. #pragma pack / __attribute__((packed)): reduce or eliminate padding — risky (unaligned access UB, slower, breaks SIMD): #pragma pack(1) struct Packed { char a; int b; }; // sizeof = 5 #pragma pack(). alignas (C++11): override default alignment: alignas(64) char cacheLinePadding[64]; // Force 64-byte cache line alignment alignas(16) float vec[4]; // SIMD alignment. std::aligned_storage, std::aligned_alloc: allocation with specific alignment requirements. Cache line awareness: false sharing — two threads modifying variables in the same 64-byte cache line causes performance issues. Pad to separate cache lines: struct PaddedCounter { alignas(64) std::atomic<int> count; char padding[60]; // Ensure next counter is on separate line };

Open this question on its own page
05

What is the C++ memory model and atomic operations?

The C++ memory model (C++11) formally defines how threads interact with shared memory — establishing rules for when writes by one thread become visible to other threads. Memory ordering options (from relaxed to strict): std::memory_order_relaxed — no ordering constraints, only atomicity (fastest — for counters that don't need synchronization guarantees); std::memory_order_acquire — all reads/writes after this point are NOT moved before this operation (load barrier); std::memory_order_release — all reads/writes before this point are NOT moved after this operation (store barrier); std::memory_order_acq_rel — both acquire and release; std::memory_order_seq_cst — total sequential consistency (default — safest, most expensive). std::atomic examples: std::atomic<int> count{0}; // Simple counter (relaxed OK): count.fetch_add(1, std::memory_order_relaxed); // Publish data pattern (producer): std::atomic<bool> ready{false}; data = compute(); // Prepare data ready.store(true, std::memory_order_release); // Consume data pattern (consumer): while (!ready.load(std::memory_order_acquire)); // Spin until ready use(data); // Safe -- guaranteed to see compute() result // Compare-and-swap (lock-free algorithms): int expected = 0; count.compare_exchange_strong(expected, 1); // Atomic: if count==0, set to 1. Lock-free data structures: use compare_exchange_weak/strong loops to build lock-free stacks, queues. Avoid ABA problem with versioned pointers. std::memory_order_relaxed use case: std::atomic<int> hitCount{0}; hitCount.fetch_add(1, std::memory_order_relaxed); // Cache stats -- order doesn't matter. Happens-before relationship: if thread A does a release store and thread B does an acquire load of the same atomic, all operations before A's store are visible after B's load.

Open this question on its own page
06

What are C++ optimization techniques?

C++ performance optimization operates at multiple levels: 1. Algorithmic (most impactful): choose O(log n) over O(n), O(n) over O(n²). Profile first — optimize the actual bottleneck, not assumptions. 2. Memory layout (cache efficiency): AoS (Array of Structs) → SoA (Struct of Arrays) for SIMD and cache: // AoS (cache-unfriendly for processing x,y separately): struct Point { float x, y, z; }; Point points[1000]; // SoA (cache-friendly for processing all x values): float x[1000], y[1000], z[1000];. Keep hot data contiguous (std::vector over std::list). Reserve vector capacity upfront. 3. Avoid unnecessary copies: pass by const reference, return by value (RVO), use std::move for temporaries, use emplace_back over push_back. 4. Inlining and constexpr: small functions marked inline or implemented in header avoid call overhead. constexpr moves computation to compile time. 5. Branch prediction: sort data before processing, use likely/unlikely hints: if (__builtin_expect(isHotPath, 1)) { /* ... */ }. C++20: [[likely]] if (condition) { }. 6. SIMD vectorization: use auto-vectorization-friendly loops (simple indexed loops, no aliasing), or use intrinsics/SIMD libraries (xsimd, Eigen) for explicit vectorization. 7. Compiler optimizations: -O2/-O3 enables most optimizations; -march=native enables CPU-specific instructions; LTO (Link Time Optimization) -flto; PGO (Profile-Guided Optimization). 8. Zero-cost abstractions: templates, inlined algorithms, constexpr — generate efficient code with no runtime overhead. The C++ "zero overhead principle." 9. Profiling tools: perf (Linux), Valgrind/Callgrind, Intel VTune, gprof, Google Benchmark for microbenchmarks. 10. Small String Optimization (SSO), Small Buffer Optimization (SBO): store small objects inline without heap allocation.

Open this question on its own page
07

What is Variadic Templates and Parameter Packs?

Variadic templates (C++11) allow templates to accept any number of type parameters — enabling type-safe, compile-time generic code for variable-argument functions. Basic variadic function: // Base case (empty pack): void print() {} // Recursive case: template<typename T, typename... Rest> void print(T first, Rest... rest) { std::cout << first << " "; print(rest...); // Recursive call with remaining args } print(1, "hello", 3.14, true); // 1 hello 3.14 1. Fold expressions (C++17) — much simpler: template<typename... Args> auto sum(Args... args) { return (args + ...); // Unary right fold } template<typename... Args> void printAll(Args&&... args) { (std::cout << ... << args) << "\n"; // Binary left fold } template<typename... Args> bool allTrue(Args... args) { return (... && args); // All must be true } sum(1, 2, 3, 4); // 10 printAll("Hello ", "World ", 42); // Hello World 42 allTrue(true, true, false); // false. sizeof... operator: count parameters: template<typename... Args> constexpr size_t argCount(Args...) { return sizeof...(Args); } argCount(1, "hi", 3.14); // 3. Perfect forwarding with variadic: template<typename T, typename... Args> std::unique_ptr<T> make_unique(Args&&... args) { return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); }. std::tuple internals: variadic template: template<typename... Ts> class tuple; auto t = std::make_tuple(1, "hello", 3.14); std::get<0>(t); // 1 std::get<1>(t); // "hello". Index sequences: template<size_t... Is> void process(std::index_sequence<Is...>) { ((std::cout << Is), ...); // 0 1 2 3 } process(std::make_index_sequence<4>{});

Open this question on its own page
08

What is the PIMPL idiom in C++?

The PIMPL idiom (Pointer to Implementation, also Opaque Pointer, Compilation Firewall) hides implementation details from the class interface, reducing compilation dependencies and enabling binary compatibility. Problem without PIMPL: any change to private members in a header file forces recompilation of all files that include that header — even if the public API didn't change. PIMPL solution: // widget.h (public interface -- stable): class Widget { public: Widget(); ~Widget(); // Must be defined where Impl is complete Widget(Widget&&) noexcept; Widget& operator=(Widget&&) noexcept; void doSomething(); int getValue() const; private: struct Impl; // Forward declaration -- Impl is hidden std::unique_ptr<Impl> pImpl; // Pointer to hidden impl }; // widget.cpp (implementation -- can change freely): #include "widget.h" #include <vector> // Heavy headers only in .cpp! #include "heavy_library.h" struct Widget::Impl { std::vector<int> data; HeavyLibraryClass helper; int cachedValue = 0; void expensiveCalculation() { /* ... */ } }; Widget::Widget() : pImpl(std::make_unique<Impl>()) {} Widget::~Widget() = default; // Must be in .cpp where Impl is complete! Widget::Widget(Widget&&) noexcept = default; Widget& Widget::operator=(Widget&&) noexcept = default; void Widget::doSomething() { pImpl->expensiveCalculation(); } int Widget::getValue() const { return pImpl->cachedValue; }. Benefits: (1) Include heavy dependencies only in .cpp (faster builds); (2) Changes to Impl don't require recompiling users of Widget; (3) ABI stability — binary interface remains the same even when implementation changes; (4) Hide implementation details from users (IP protection). Costs: extra heap allocation (unique_ptr); one indirection per member access; must implement destructor, move ctor/assignment in .cpp. Used by: Qt's d-pointer, many library APIs requiring stable ABI.

Open this question on its own page
Back to All Topics 44 questions total