C++ Interview Questions

All 40 C++ interview questions are now live, covering fundamentals through performance: pointers and references, STL, templates, smart pointers, concurrency, tooling, and systems-level optimization.

40Questions Live
8 / 8Batches
5Levels Covered
240Answer Sections
Showing 40 of 40 questions
0 of 40 viewed
01 What is C++ and how does it differ from C? basic

C++ is a general-purpose language that builds on C with classes, templates, RAII, stronger type abstraction, and a large standard library. It still allows low-level control, but it adds tools for safer and more expressive design.

C is mostly procedural, while C++ supports procedural, object-oriented, generic, and modern value-based styles. In interviews, the key difference is not just syntax but the richer abstraction model and resource-management patterns in C++.

#include <iostream>
#include <string>

struct User {
    std::string name;
    void greet() const {
        std::cout << "Hello, " << name << "\n";
    }
};

int main() {
    User user{"Amit"};
    user.greet();
}

A trading system used C++ instead of C to keep low latency while using safer abstractions for ownership and container management.

C++ keeps C-level control but adds abstractions that reduce manual boilerplate and design friction.
⚠️ Common Mistake

A weak answer says C++ is just C with classes. That ignores templates, RAII, overloads, and the STL.

Weak
C++ is basically C plus OOP.
Better
C++ extends C-style systems programming with object, generic, and RAII-based design tools.
🔁 Follow-Up Question

Why is RAII considered one of the biggest practical advantages of C++ over C?

02 Explain pointers and references — what's the difference? basic

A pointer stores an address and can be null, reassigned, or used for dynamic memory and indirection. You access the pointee explicitly with * and ->.

A reference is an alias to an existing object. It must be initialized when declared, cannot be reseated, and is usually easier to reason about for function parameters because it avoids null checks by default.

#include <iostream>

void incrementByPointer(int* value) {
    if (value) {
        ++(*value);
    }
}

void incrementByReference(int& value) {
    ++value;
}

int main() {
    int score = 10;
    int* ptr = &score;
    int& ref = score;

    incrementByPointer(ptr);
    incrementByReference(ref);

    std::cout << score << "\n";
}

API code often prefers references for required inputs and pointers for optional objects or ownership-aware interfaces.

Use references for required aliases and pointers when nullability or reseating is part of the contract.
⚠️ Common Mistake

Many candidates say references are just constant pointers. They are not objects with pointer semantics.

Wrong
int& r = a;
r = b; // reseats the reference
Correct
r = b; // assigns through the reference to a
🔁 Follow-Up Question

When would you deliberately choose a raw pointer over a reference in an interface?

03 What are the fundamental data types and type modifiers? basic

Fundamental types include boolean, character, integer, floating-point, and void. Common examples are bool, char, int, float, and double.

Type modifiers such as signed, unsigned, short, and long adjust range or representation. In interviews, emphasize that exact sizes can vary by platform, so fixed-width types like std::int32_t are often safer for portable code.

#include <cstdint>
#include <iostream>

int main() {
    unsigned int retries = 3;
    long long bytesProcessed = 5000000;
    double price = 149.99;
    std::int32_t userId = 1024;

    std::cout << retries << " " << bytesProcessed << " " << price << " " << userId << "\n";
}

A network protocol parser used fixed-width integer types so message layouts stayed identical across compilers and servers.

Know the core built-in types, but prefer fixed-width integers when binary compatibility matters.
⚠️ Common Mistake

A common mistake is assuming int is always 32-bit and long is always 64-bit.

🔁 Follow-Up Question

Why are fixed-width integer types often preferred in systems and serialization code?

04 How do arrays and strings (C-strings vs std::string) work? basic

Built-in arrays store contiguous elements of one type and have fixed size. When passed to functions, they often decay to pointers, so size information is easily lost unless passed separately or wrapped in a safer type.

C-strings are null-terminated character arrays, which makes them fast and interoperable but error-prone. std::string manages memory, size, and operations safely, so it is usually the better default in modern C++.

#include <cstring>
#include <iostream>
#include <string>

int main() {
    char legacyName[] = "Amit";
    std::string modernName = "Amit Kumar";

    std::cout << std::strlen(legacyName) << "\n";
    std::cout << modernName.size() << "\n";
    modernName += " Singh";
    std::cout << modernName << "\n";
}

A file parser used C-strings only at the OS boundary, then converted immediately to std::string for safer processing.

Prefer std::string for ownership and manipulation, and use C-strings mainly for legacy or C API boundaries.
⚠️ Common Mistake

Many candidates treat C-strings and std::string as interchangeable, but one is a raw character buffer and the other is a managed class.

🔁 Follow-Up Question

What risks appear when a character array is not properly null-terminated?

05 Explain pass-by-value, pass-by-reference, and pass-by-pointer. basic

Pass-by-value copies the argument, so changes inside the function affect only the local copy. It is simple and safe, and for small types it is often cheap.

Pass-by-reference aliases the original object, while pass-by-pointer passes its address explicitly. References are cleaner for required inputs, and pointers are useful when nullability, optional output, or ownership signaling is needed.

#include <iostream>
#include <string>

void renameByValue(std::string name) {
    name = "Changed";
}

void renameByReference(std::string& name) {
    name = "Changed";
}

void renameByPointer(std::string* name) {
    if (name) {
        *name = "Changed";
    }
}

int main() {
    std::string user = "Amit";
    renameByValue(user);
    renameByReference(user);
    renameByPointer(&user);
    std::cout << user << "\n";
}

A hot path accepted large request objects by const reference to avoid copies while keeping the call site simple.

Choose value for isolation, reference for required aliasing, and pointer when absence must be expressible.
⚠️ Common Mistake

A shallow answer says reference and pointer parameters are the same. Their contracts differ, especially around null and call-site clarity.

🔁 Follow-Up Question

Why is pass-by-const-reference common for large read-only objects?

06 What are classes, objects, constructors, and destructors? basic

A class defines a user-defined type with data and behavior, and an object is a concrete instance of that type. Classes let you model invariants and keep related logic together.

Constructors initialize objects when they are created, and destructors run automatically when objects are destroyed. In C++, destructors are central to RAII because they release resources such as files, locks, and heap memory.

#include <fstream>
#include <string>

class AuditWriter {
public:
    explicit AuditWriter(const std::string& path) : file(path) {}
    ~AuditWriter() {
        if (file.is_open()) {
            file << "closing\n";
        }
    }

    void write(const std::string& line) {
        file << line << "\n";
    }

private:
    std::ofstream file;
};

int main() {
    AuditWriter writer("audit.log");
    writer.write("login success");
}

A service wrapped file and socket handles in classes so cleanup happened automatically during normal returns and exceptions.

Constructors establish valid state and destructors enforce deterministic cleanup.
⚠️ Common Mistake

Many candidates describe destructors only as memory cleanup, but they manage any owned resource, not just heap allocation.

🔁 Follow-Up Question

Why should resource-owning classes usually define clear copy and move behavior?

07 How does inheritance work (single, multiple, virtual)? basic

Inheritance lets a derived class reuse and extend a base class interface or implementation. Single inheritance means one direct base, while multiple inheritance means a class can derive from more than one base.

Virtual inheritance is used to solve the diamond problem by ensuring only one shared base subobject exists. In practice, inheritance should model an is-a relationship; otherwise composition is usually cleaner.

#include <iostream>

struct Device {
    void powerOn() const { std::cout << "power on\n"; }
};

struct Printer : virtual Device {};
struct Scanner : virtual Device {};

struct MultiFunctionUnit : Printer, Scanner {};

int main() {
    MultiFunctionUnit unit;
    unit.powerOn();
}

A hardware SDK used virtual inheritance to avoid duplicate device state when combining printer and scanner capabilities.

Inheritance is for shared contracts and specialization, and virtual inheritance mainly exists to control shared base layout.
⚠️ Common Mistake

A common mistake is using inheritance for code reuse when the relationship is not truly substitutable.

🔁 Follow-Up Question

When would composition be better than inheritance in C++ design?

08 What is const correctness and why does it matter? basic

Const correctness means marking values, parameters, pointers, and member functions as immutable when they should not modify state. It documents intent and lets the compiler prevent accidental mutation.

It matters because it improves API clarity, enables safer reuse, and allows const objects to call only const-qualified methods. In larger codebases, this reduces bugs caused by unexpected side effects.

#include <iostream>
#include <string>

class Report {
public:
    explicit Report(std::string name) : name_(std::move(name)) {}
    const std::string& name() const { return name_; }

private:
    std::string name_;
};

void printReport(const Report& report) {
    std::cout << report.name() << "\n";
}

int main() {
    Report report("Q2 Revenue");
    printReport(report);
}

A shared analytics library added const member functions so read-only data objects became usable across more pipelines without defensive copying.

Const correctness turns intent into compiler-checked contracts.
⚠️ Common Mistake

Candidates often think const is cosmetic. In C++, it directly affects overload resolution, usability, and mutation safety.

🔁 Follow-Up Question

What is the difference between const int* and int* const?

09 How does basic I/O work (cin, cout, file streams)? basic

C++ stream I/O is built around stream objects such as std::cin, std::cout, and file streams from <fstream>. Extraction with >> reads formatted input, and insertion with << writes formatted output.

File streams like std::ifstream and std::ofstream wrap file handles with the same stream interface. In interviews, mention state checking because stream operations can fail due to bad input or I/O errors.

#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ofstream out("users.txt");
    out << "Amit,42\n";
    out.close();

    std::ifstream in("users.txt");
    std::string line;
    if (std::getline(in, line)) {
        std::cout << line << "\n";
    }
}

A batch job wrote audit rows through std::ofstream and rejected malformed input by checking stream state after each parse step.

C++ streams unify console and file I/O behind one formatted interface, but you must check for failure.
⚠️ Common Mistake

A frequent mistake is assuming stream operations always succeed and skipping state checks after reads.

🔁 Follow-Up Question

What is the difference between using operator>> and std::getline?

10 What is the difference between struct and class? basic

In C++, struct and class are almost the same language feature. Both can have methods, constructors, inheritance, and access control.

The main default difference is that struct members and inheritance are public by default, while class members and inheritance are private by default. Teams often use structs for plain data and classes for richer encapsulated behavior, but that is convention, not a language restriction.

#include <string>

struct Point {
    int x;
    int y;
};

class Account {
public:
    explicit Account(std::string owner) : owner_(std::move(owner)) {}
private:
    std::string owner_;
};

int main() {
    Point p{10, 20};
    Account a("Amit");
}

A rendering engine used structs for simple geometry records and classes for resource-owning objects with invariants.

The real difference is default access, while usage style is mostly a design convention.
⚠️ Common Mistake

Many candidates incorrectly say structs cannot have methods or constructors in C++.

🔁 Follow-Up Question

Why do some codebases prefer structs for passive data types?

11 How does operator overloading work? intermediate

Operator overloading lets user-defined types define natural syntax for operations such as comparison, addition, indexing, or streaming. It makes custom types feel like built-in types when the semantics are intuitive.

Good overloads preserve expected meaning and avoid surprises. In interviews, mention that overloaded operators are just functions with special names and should not be abused for unrelated behavior.

#include <iostream>

class Money {
public:
    explicit Money(int cents) : cents_(cents) {}

    Money operator+(const Money& other) const {
        return Money(cents_ + other.cents_);
    }

    friend std::ostream& operator<<(std::ostream& os, const Money& money) {
        return os << money.cents_ << " cents";
    }

private:
    int cents_;
};

int main() {
    Money subtotal(500), tax(90);
    std::cout << (subtotal + tax) << "\n";
}

A pricing library overloaded arithmetic and comparison operators so monetary calculations stayed readable and type-safe.

Operator overloading should improve clarity, not hide surprising side effects.
⚠️ Common Mistake

A bad overload changes the expected meaning of an operator, which makes code harder to trust and review.

🔁 Follow-Up Question

Which operators are commonly overloaded well, and which ones are usually risky?

12 Explain polymorphism, virtual functions, and vtables. intermediate

Polymorphism lets code call behavior through a base interface while the derived implementation runs at runtime. In C++, runtime polymorphism is typically achieved with virtual functions.

Compilers usually implement virtual dispatch with a hidden virtual table pointer in polymorphic objects, though the exact layout is ABI-specific. The key interview point is dynamic dispatch cost versus the design flexibility it provides.

#include <iostream>
#include <memory>
#include <vector>

class PaymentMethod {
public:
    virtual ~PaymentMethod() = default;
    virtual void pay(double amount) const = 0;
};

class CardPayment : public PaymentMethod {
public:
    void pay(double amount) const override {
        std::cout << "Charged card: " << amount << "\n";
    }
};

int main() {
    std::vector<std::unique_ptr<PaymentMethod>> methods;
    methods.push_back(std::make_unique<CardPayment>());
    methods[0]->pay(499.0);
}

A plugin system used virtual interfaces so new handlers shipped independently without changing dispatch code.

Virtual functions enable runtime substitution through base types, typically via vtable-based dispatch.
⚠️ Common Mistake

Some candidates confuse function overloading with polymorphism. Overloading is compile-time; virtual dispatch is runtime.

🔁 Follow-Up Question

When would you prefer static polymorphism over virtual dispatch?

13 What are abstract classes and pure virtual functions? intermediate

An abstract class is a class that cannot be instantiated directly because it contains at least one pure virtual function. A pure virtual function is declared with = 0 and defines a required interface for derived classes.

Abstract classes are useful when you want a shared base type plus some common implementation, while still forcing concrete subclasses to provide specific behavior.

#include <iostream>
#include <memory>

class Exporter {
public:
    virtual ~Exporter() = default;
    virtual void exportData() const = 0;
};

class CsvExporter : public Exporter {
public:
    void exportData() const override {
        std::cout << "exporting csv\n";
    }
};

int main() {
    std::unique_ptr<Exporter> exporter = std::make_unique<CsvExporter>();
    exporter->exportData();
}

A reporting platform exposed an abstract exporter interface so teams could add CSV, JSON, and PDF outputs independently.

Pure virtual functions define required behavior and make the base class an interface-like contract.
⚠️ Common Mistake

A common mistake is saying an abstract class cannot have implemented methods or data members. It can.

🔁 Follow-Up Question

How is an abstract class different from a pure interface style base class?

14 How do templates work (function templates, class templates)? intermediate

Templates let you write generic code that the compiler instantiates for specific types. Function templates generalize algorithms, and class templates generalize data structures or wrappers.

Templates are resolved at compile time, which can produce efficient type-specific code without runtime overhead. The tradeoff is heavier compile-time complexity and sometimes harder error messages.

#include <iostream>
#include <vector>

template <typename T>
T maxValue(const T& a, const T& b) {
    return (a < b) ? b : a;
}

template <typename T>
class Buffer {
public:
    void add(const T& value) { values_.push_back(value); }
    std::size_t size() const { return values_.size(); }
private:
    std::vector<T> values_;
};

int main() {
    Buffer<int> ids;
    ids.add(10);
    std::cout << maxValue(4, 9) << " " << ids.size() << "\n";
}

A core library used templates to share one container implementation across multiple domain types without virtual overhead.

Templates enable zero-cost generic programming when the type rules are enforced at compile time.
⚠️ Common Mistake

Many candidates describe templates as text substitution. They are a compile-time type system feature, not just macros.

🔁 Follow-Up Question

Why are template definitions usually placed in header files?

15 Explain the STL containers (vector, map, set, unordered_map, deque). intermediate

STL containers provide standard data structures with defined complexity tradeoffs. vector is a dynamic contiguous array, map and set are ordered tree-based containers, unordered_map is hash-based, and deque supports efficient insertion at both ends.

The interview goal is not memorizing names but choosing the right container based on ordering, lookup cost, iteration patterns, memory layout, and insertion behavior.

#include <deque>
#include <map>
#include <set>
#include <unordered_map>
#include <vector>

int main() {
    std::vector<int> ids{1, 2, 3};
    std::map<int, const char*> ordered{{2, "two"}, {1, "one"}};
    std::set<int> uniqueIds{3, 1, 3};
    std::unordered_map<int, const char*> fastLookup{{1, "Amit"}};
    std::deque<int> tasks{10, 20, 30};
}

A matching engine kept hot sequential data in vector, user metadata in unordered_map, and ranked views in map.

Container choice should follow access pattern, not habit.
⚠️ Common Mistake

A weak answer treats unordered_map as always faster than map, ignoring ordering, memory, and worst-case behavior.

🔁 Follow-Up Question

Why is vector often preferred over linked structures in modern C++?

16 What are iterators and how do they work with STL algorithms? intermediate

Iterators generalize pointers for navigating container elements. Different iterator categories support different operations, such as single-pass traversal, bidirectional movement, or random access.

STL algorithms work on iterator ranges rather than specific container types. That separation is powerful because one algorithm like std::find_if can work across many containers as long as the iterator requirements are met.

#include <algorithm>
#include <iostream>
#include <vector>

int main() {
    std::vector<int> scores{42, 87, 91, 65};

    auto it = std::find_if(scores.begin(), scores.end(), [](int value) {
        return value >= 90;
    });

    if (it != scores.end()) {
        std::cout << *it << "\n";
    }
}

A metrics pipeline reused the same STL algorithms across vectors and deques by coding against iterator ranges instead of concrete container types.

Iterators decouple algorithms from container implementations.
⚠️ Common Mistake

A common mistake is thinking iterators are always raw pointers. Some are pointer-like, but many are richer abstractions.

🔁 Follow-Up Question

What breaks when you use an algorithm requiring random-access iterators on a list-like container?

17 Explain smart pointers (unique_ptr, shared_ptr, weak_ptr). intermediate

Smart pointers wrap raw pointers with ownership semantics. std::unique_ptr represents exclusive ownership, std::shared_ptr represents shared reference-counted ownership, and std::weak_ptr is a non-owning observer for breaking cycles.

The main interview point is to match pointer type to ownership intent. Prefer unique_ptr by default because it is simpler, cheaper, and makes lifetime easier to reason about.

#include <iostream>
#include <memory>

class CacheEntry {
public:
    explicit CacheEntry(int id) : id_(id) {}
    int id() const { return id_; }
private:
    int id_;
};

int main() {
    auto owner = std::make_unique<CacheEntry>(101);

    auto shared = std::make_shared<CacheEntry>(202);
    std::weak_ptr<CacheEntry> observer = shared;

    std::cout << owner->id() << " " << shared->id() << "\n";
}

A service replaced scattered raw owning pointers with unique_ptr and removed a class of shutdown leaks during error handling.

Smart pointers express lifetime rules explicitly, which makes ownership safer and reviewable.
⚠️ Common Mistake

Many candidates overuse shared_ptr. Shared ownership should be deliberate, not the default.

🔁 Follow-Up Question

How can shared_ptr cycles cause leaks, and how does weak_ptr help?

18 How does exception handling work (try, catch, throw, noexcept)? intermediate

C++ exceptions signal failure by throwing an object that unwinds the stack until a matching catch block is found. During unwinding, destructors run, which is why RAII integrates well with exception safety.

noexcept declares that a function is not expected to throw. It matters for optimization and for standard-library behavior, especially move operations, but violating it usually terminates the program.

#include <iostream>
#include <stdexcept>
#include <string>

int parsePort(const std::string& text) {
    int port = std::stoi(text);
    if (port <= 0) {
        throw std::invalid_argument("port must be positive");
    }
    return port;
}

int main() {
    try {
        std::cout << parsePort("8080") << "\n";
    } catch (const std::exception& ex) {
        std::cout << ex.what() << "\n";
    }
}

Configuration loading used exceptions to abort startup on invalid settings while RAII cleaned partially constructed resources automatically.

Exceptions separate normal logic from failure paths, but APIs should still define clear guarantees.
⚠️ Common Mistake

A shallow answer says exceptions are just like return codes but cleaner. The key difference is stack unwinding and exception-safety guarantees.

🔁 Follow-Up Question

What is the difference between basic, strong, and no-throw exception safety?

19 What are namespaces and how do you use them? intermediate

Namespaces group related declarations and prevent name collisions across large codebases or libraries. They are especially important in C++ because templates, utilities, and domain types often share generic names.

You can qualify names explicitly with ::, bring selected names into scope with using, or open nested namespaces. In production code, broad using namespace directives are usually avoided in headers.

#include <iostream>
#include <string>

namespace billing {
    struct Invoice {
        std::string id;
    };

    void print(const Invoice& invoice) {
        std::cout << invoice.id << "\n";
    }
}

int main() {
    billing::Invoice invoice{"INV-42"};
    billing::print(invoice);
}

A monorepo used namespaces to separate billing, auth, and reporting models that all had types like Request and Result.

Namespaces are a low-cost way to make symbols predictable and avoid collisions.
⚠️ Common Mistake

A common mistake is putting using namespace std; in headers, which pollutes every includer.

🔁 Follow-Up Question

Why is using namespace in a header considered risky?

20 How do lambda expressions work in C++? intermediate

Lambdas create unnamed function objects inline. They are useful for short local behavior such as predicates, callbacks, and custom transformations.

The capture list controls what outside variables the lambda can access and whether they are captured by value or reference. In interviews, explain captures clearly because correctness often depends on object lifetime and mutation semantics.

#include <algorithm>
#include <iostream>
#include <vector>

int main() {
    std::vector<int> prices{120, 80, 300};
    int threshold = 100;

    auto expensiveCount = std::count_if(prices.begin(), prices.end(), [threshold](int price) {
        return price > threshold;
    });

    std::cout << expensiveCount << "\n";
}

A UI layer used lambdas for event callbacks without creating dozens of one-off function objects.

Lambdas are compact function objects whose power comes from precise capture behavior.
⚠️ Common Mistake

Candidates often ignore capture lifetime and accidentally return lambdas that reference dead local variables.

🔁 Follow-Up Question

What is the difference between capturing by value and capturing by reference?

21 Explain move semantics and rvalue references (std::move). advanced

Move semantics let C++ transfer resources from temporary or expiring objects instead of copying them. This is enabled by rvalue references, written as T&&, and move constructors or move assignment operators.

std::move does not move by itself; it casts an object to an rvalue so move-aware operations can run. This is valuable for resource-owning types like strings, vectors, and file wrappers because it avoids unnecessary deep copies.

#include <iostream>
#include <string>
#include <utility>
#include <vector>

int main() {
    std::string payload = "large response body";
    std::vector<std::string> queue;

    queue.push_back(std::move(payload));

    std::cout << "queue size: " << queue.size() << "\n";
    std::cout << "payload after move: " << payload << "\n";
}

A messaging service cut allocation churn by moving large buffers between stages instead of copying them repeatedly.

Move semantics optimize transfer of ownership-like state, but moved-from objects must still remain valid.
⚠️ Common Mistake

A common mistake is saying std::move physically moves data. It only enables a move operation if one exists.

🔁 Follow-Up Question

What state guarantees exist for a moved-from standard library object?

22 What is perfect forwarding and std::forward? advanced

Perfect forwarding preserves the value category of a function argument when passing it onward, so lvalues stay lvalues and rvalues stay rvalues. This matters in generic wrapper code that should not accidentally force copies or moves.

std::forward is used with forwarding references in templates to preserve the original category. The typical use case is factory helpers like emplace or wrapper APIs that construct objects in place.

#include <iostream>
#include <string>
#include <utility>

struct User {
    explicit User(std::string name) : name(std::move(name)) {}
    std::string name;
};

template <typename... Args>
User makeUser(Args&&... args) {
    return User(std::forward<Args>(args)...);
}

int main() {
    auto user = makeUser("Amit");
    std::cout << user.name << "\n";
}

A framework used perfect forwarding in factory helpers so constructor arguments flowed through without extra temporary objects.

Perfect forwarding is about preserving argument category in generic code, not about blindly calling std::move everywhere.
⚠️ Common Mistake

Many candidates use std::move inside forwarding wrappers, which wrongly turns lvalues into rvalues.

🔁 Follow-Up Question

What is a forwarding reference, and when does T&& become one?

23 What is RAII and the Rule of 0/3/5? advanced

RAII means tying resource lifetime to object lifetime: acquire resources in constructors and release them in destructors. This makes cleanup deterministic and exception-safe.

The Rule of 0 says types should usually rely on compiler-generated special members if they do not own resources directly. The Rules of 3 and 5 say that if you define one ownership-sensitive special member, you often need the related copy and move operations as well.

#include <mutex>

class ScopedLock {
public:
    explicit ScopedLock(std::mutex& m) : mutex_(m) { mutex_.lock(); }
    ~ScopedLock() { mutex_.unlock(); }

    ScopedLock(const ScopedLock&) = delete;
    ScopedLock& operator=(const ScopedLock&) = delete;

private:
    std::mutex& mutex_;
};

int main() {
    std::mutex m;
    ScopedLock lock(m);
}

A storage client used RAII wrappers for sockets and locks, which removed many early-return cleanup bugs.

RAII is the core C++ lifetime pattern, and the Rule of 0 is the cleanest outcome when possible.
⚠️ Common Mistake

A common mistake is manually pairing allocate and free calls across many code paths instead of encapsulating ownership in a type.

🔁 Follow-Up Question

When would you need to implement the Rule of 5 instead of relying on the Rule of 0?

24 How does template metaprogramming work? advanced

Template metaprogramming uses templates to compute types, values, or behavior at compile time. Older styles relied on recursive template instantiation and type traits, while modern C++ often mixes templates with constexpr and concepts for better clarity.

Its value is generating specialized code and enforcing rules before runtime. The tradeoff is more complex compile-time logic, so it should be used when it materially improves performance, expressiveness, or correctness.

#include <iostream>
#include <type_traits>

template <typename T>
struct IsPointer {
    static constexpr bool value = false;
};

template <typename T>
struct IsPointer<T*> {
    static constexpr bool value = true;
};

int main() {
    std::cout << IsPointer<int>::value << "\n";
    std::cout << IsPointer<int*>::value << "\n";
}

A serialization library chose code paths at compile time based on whether a type was trivially copyable or pointer-like.

Template metaprogramming shifts decisions from runtime to compile time when type information is the key input.
⚠️ Common Mistake

A weak answer treats metaprogramming as only obscure recursion tricks, ignoring modern traits, constexpr, and constrained templates.

🔁 Follow-Up Question

How has modern constexpr reduced the need for older TMP patterns?

25 What are SFINAE and C++20 concepts? advanced

SFINAE means substitution failure is not an error: when template substitution fails in certain contexts, that overload is simply removed from consideration. It was widely used to constrain templates before the language had clearer constraint syntax.

C++20 concepts provide readable, first-class constraints on template parameters. They make intent clearer, improve diagnostics, and are usually easier to maintain than heavy SFINAE-based enable_if patterns.

#include <concepts>
#include <iostream>

template <std::integral T>
T doubleValue(T value) {
    return value * 2;
}

int main() {
    std::cout << doubleValue(21) << "\n";
}

A generic math library migrated from enable_if-heavy code to concepts and made template errors far easier for downstream teams to understand.

SFINAE is the older exclusion mechanism; concepts are the clearer modern way to express template requirements.
⚠️ Common Mistake

Many candidates can define SFINAE but cannot explain the practical benefit: controlling which templates participate in overload resolution.

🔁 Follow-Up Question

Why do concepts usually produce better compiler diagnostics than SFINAE?

26 Explain variadic templates and fold expressions. advanced

Variadic templates let a template accept an arbitrary number of template or function arguments. They are the type-safe replacement for old C-style variadic functions in many generic APIs.

Fold expressions, introduced in C++17, simplify operating over parameter packs. They remove a lot of recursive boilerplate and make code like summing or combining arguments much clearer.

#include <iostream>

template <typename... Args>
auto sum(Args... args) {
    return (args + ...);
}

int main() {
    std::cout << sum(10, 20, 30, 40) << "\n";
}

A logging utility used variadic templates to accept typed fields without unsafe printf-style formatting.

Variadic templates generalize argument packs, and fold expressions make pack-wide operations concise.
⚠️ Common Mistake

A common mistake is confusing variadic templates with C varargs, which lack type safety and compile-time checking.

🔁 Follow-Up Question

What is the difference between a left fold and a right fold?

27 What are constexpr and consteval (compile-time computation)? advanced

constexpr allows values and functions to be evaluated at compile time when given constant inputs, while still remaining usable at runtime in many cases. It is a major tool for zero-overhead abstractions and precomputed logic.

consteval is stricter: it requires compile-time evaluation. Use it when a result must exist during compilation, such as validated generated constants or checked metaprogramming helpers.

#include <array>
#include <iostream>

constexpr int square(int value) {
    return value * value;
}

consteval int tableSize() {
    return 8;
}

int main() {
    std::array<int, tableSize()> values{};
    std::cout << square(12) << " " << values.size() << "\n";
}

A protocol library computed lookup tables at compile time to reduce startup work and eliminate runtime branching.

Use constexpr for compile-time-capable logic and consteval when compile-time evaluation is mandatory.
⚠️ Common Mistake

Many candidates say constexpr always means compile-time execution. It only does so when the context and inputs permit it.

🔁 Follow-Up Question

When would consteval be better than constexpr in a public API?

28 How do C++20 coroutines work? advanced

C++20 coroutines let a function suspend and resume without blocking a thread. The compiler transforms the function into a state machine that stores its local state across suspension points such as co_await, co_yield, or co_return.

The language provides the transformation model, but real usefulness comes from libraries that define awaitable types and schedulers. In interviews, focus on suspension semantics, not on assuming coroutines automatically create threads.

#include <coroutine>
#include <iostream>

struct SimpleTask {
    struct promise_type {
        SimpleTask get_return_object() { return {}; }
        std::suspend_never initial_suspend() noexcept { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};

SimpleTask runTask() {
    std::cout << "before suspend point\n";
    co_return;
}

int main() {
    runTask();
}

An async gateway used coroutines to express request pipelines sequentially while still integrating with an event loop.

Coroutines are compiler-generated state machines for async or generator-like flows, not implicit threading.
⚠️ Common Mistake

A common mistake is saying a coroutine runs on another thread by default. Scheduling is library-defined, not built into the keyword itself.

🔁 Follow-Up Question

How is a coroutine different from a callback chain or a thread?

29 Explain the C++ memory model and std::atomic. experienced

The C++ memory model defines how threads interact through memory and what counts as a data race. If two threads access the same object concurrently and at least one write is not synchronized, the behavior is undefined.

std::atomic provides lock-free or lock-based atomic operations with explicit memory ordering semantics such as relaxed, acquire, and release. The main interview point is that atomicity alone is not enough; visibility and ordering matter too.

#include <atomic>
#include <thread>

int main() {
    std::atomic<bool> ready{false};
    int data = 0;

    std::thread producer([&] {
        data = 42;
        ready.store(true, std::memory_order_release);
    });

    std::thread consumer([&] {
        while (!ready.load(std::memory_order_acquire)) {}
        int value = data;
        (void)value;
    });

    producer.join();
    consumer.join();
}

A low-latency feed handler used acquire-release atomics to publish parsed message batches without coarse locking.

Thread safety in C++ depends on synchronization rules, not just on code looking harmless.
⚠️ Common Mistake

Many candidates think volatile is a threading primitive in C++. It is not a replacement for atomic synchronization.

🔁 Follow-Up Question

When is memory_order_relaxed correct, and what does it not guarantee?

30 What are lock-free data structures and when should you use them? experienced

Lock-free data structures guarantee that at least one thread makes progress even under contention, usually through atomic operations like compare-and-swap. They can reduce blocking and avoid some scheduler-induced latency spikes.

They are hard to design correctly because of ABA issues, memory reclamation, ordering constraints, and debugging complexity. You should use them only when profiling shows contention is a real bottleneck and simpler synchronization is insufficient.

#include <atomic>

struct Node {
    int value;
    Node* next;
};

int main() {
    std::atomic<Node*> head{nullptr};
    Node node{42, nullptr};

    Node* expected = nullptr;
    head.compare_exchange_strong(expected, &node);
}

A telemetry ingest path used a lock-free queue only after mutex contention showed up clearly under burst traffic.

Lock-free structures are specialized performance tools, not a default replacement for well-designed locks.
⚠️ Common Mistake

A bad answer assumes lock-free automatically means faster. Under low contention, the added complexity may not pay off.

🔁 Follow-Up Question

What memory reclamation problem appears in lock-free linked structures?

31 Explain common C++ design patterns (CRTP, Pimpl, type erasure). experienced

CRTP uses a derived type as a template parameter to a base class, enabling static polymorphism and compile-time reuse. Pimpl hides implementation details behind an opaque pointer to reduce compile dependencies and stabilize interfaces.

Type erasure hides concrete types behind a uniform runtime wrapper, as seen in tools like std::function. These patterns solve different problems: CRTP for zero-overhead polymorphism, Pimpl for build and ABI hygiene, and type erasure for flexible runtime abstraction.

#include <iostream>

template <typename Derived>
class Printable {
public:
    void print() const {
        static_cast<const Derived*>(this)->printImpl();
    }
};

class Invoice : public Printable<Invoice> {
public:
    void printImpl() const {
        std::cout << "invoice\n";
    }
};

int main() {
    Invoice invoice;
    invoice.print();
}

A public SDK used Pimpl to hide private members and reduce rebuilds across teams consuming the library.

Choose the pattern based on whether the problem is compile-time polymorphism, binary stability, or runtime abstraction.
⚠️ Common Mistake

Many candidates list the patterns without explaining what concrete engineering problem each one solves.

🔁 Follow-Up Question

Why does Pimpl often help ABI stability and incremental build times?

32 How does CMake work for building C++ projects? experienced

CMake is a build-system generator. You describe targets, sources, include paths, compile features, and dependencies in CMakeLists.txt, and CMake generates native build files such as Ninja or Visual Studio projects.

Modern CMake is target-based: you attach properties and dependencies to targets rather than using many global flags. In interviews, that target-centric model is more important than memorizing every command.

cmake_minimum_required(VERSION 3.20)
project(BillingApp LANGUAGES CXX)

add_executable(billing main.cpp)
target_compile_features(billing PRIVATE cxx_std_20)
target_include_directories(billing PRIVATE include)

A cross-platform product used one CMake configuration to generate Linux Ninja builds and Windows Visual Studio projects from the same source tree.

Think of CMake as target description plus dependency wiring, not as a compiler itself.
⚠️ Common Mistake

A common mistake is treating CMake like a Makefile replacement rather than a generator with its own target model.

🔁 Follow-Up Question

Why is target_link_libraries preferred over manually appending global linker flags?

33 What is ABI compatibility and why does it matter? experienced

ABI compatibility means separately compiled binaries agree on details such as symbol names, calling conventions, object layout, exception handling, and standard-library expectations. It matters whenever compiled components must link or load together across versions.

Changing member layout, virtual functions, template exposure, compiler settings, or standard library versions can break ABI even when source code still compiles. That is why library maintainers often hide implementation details and version public interfaces carefully.

class ApiClient {
public:
    void send();
private:
    int timeoutMs;
};

A shared enterprise SDK had to preserve ABI so older plugins continued loading after the core application updated.

Source compatibility is not enough when distributing compiled C++ libraries; binary compatibility is a separate contract.
⚠️ Common Mistake

Many candidates assume recompiling is always an option. ABI matters precisely when independent binaries are versioned or distributed separately.

🔁 Follow-Up Question

How does the Pimpl pattern help reduce ABI breakage risk?

34 How do you write effective unit tests (GTest, Catch2)? experienced

Effective unit tests isolate one behavior, make assertions on observable outcomes, and run quickly enough to be part of normal development. Frameworks like GTest and Catch2 mainly provide structure, fixtures, assertions, and reporting.

Strong tests cover success paths, edge cases, and failure behavior without over-coupling to implementation details. In interviews, emphasize deterministic inputs, small scope, and meaningful naming over framework trivia.

#include <gtest/gtest.h>

int discount(int price, int percent) {
    return price - (price * percent / 100);
}

TEST(DiscountTest, AppliesPercentageCorrectly) {
    EXPECT_EQ(discount(200, 10), 180);
    EXPECT_EQ(discount(500, 0), 500);
}

A pricing library caught a rounding regression in CI because narrow unit tests covered the contract before integration tests ran.

Good unit tests validate behavior clearly and cheaply, so they can run constantly.
⚠️ Common Mistake

A weak test asserts internal steps instead of stable outputs, which makes refactoring harder without improving confidence.

🔁 Follow-Up Question

When should you use mocks, and when do they make tests too brittle?

35 How do you use static analysis tools (clang-tidy, cppcheck, sanitizers)? experienced

Static analysis tools inspect code for likely bugs, style issues, lifetime problems, and unsafe constructs without relying only on runtime failures. Tools like clang-tidy and cppcheck are useful for catching patterns that reviews may miss.

Sanitizers such as ASan, UBSan, and TSan instrument binaries to detect memory errors, undefined behavior, or data races at runtime. The best practice is to use both static and dynamic analysis in CI because they catch different classes of defects.

# Example build flags
-fsanitize=address,undefined -fno-omit-frame-pointer

# Example clang-tidy
clang-tidy src/order.cpp --checks=modernize-*,bugprone-*

A backend team found a use-after-free in staging with ASan that code review had missed for months.

Analysis tools are leverage: they extend review and testing by surfacing hard-to-see correctness issues early.
⚠️ Common Mistake

A common mistake is enabling tools once and ignoring the output. They only help when warnings are triaged and integrated into normal workflows.

🔁 Follow-Up Question

What kinds of bugs will AddressSanitizer catch that unit tests may not reveal reliably?

36 What is cache-friendly code and data-oriented design? performance

Cache-friendly code arranges data and access patterns so the CPU can fetch and reuse nearby memory efficiently. Data-oriented design focuses on how data is laid out and traversed rather than only on object hierarchy.

In practice, contiguous storage, tight loops, smaller working sets, and structure-of-arrays layouts often outperform pointer-heavy object graphs. Modern C++ performance work is frequently limited more by memory access than by arithmetic.

#include <vector>

struct ParticleData {
    std::vector<float> x;
    std::vector<float> y;
    std::vector<float> velocity;
};

void update(ParticleData& data, float dt) {
    for (std::size_t i = 0; i < data.x.size(); ++i) {
        data.x[i] += data.velocity[i] * dt;
        data.y[i] += data.velocity[i] * dt;
    }
}

A simulation engine sped up updates by replacing scattered heap objects with contiguous arrays for hot fields.

Performance often comes from better memory layout and traversal order, not from clever syntax tricks.
⚠️ Common Mistake

Many candidates optimize instructions first and ignore cache misses, even though memory stalls often dominate runtime.

🔁 Follow-Up Question

Why can a vector of structs be slower than a structure of arrays for some workloads?

37 Explain branch prediction and how to write branch-friendly code. performance

Modern CPUs predict branch outcomes to keep instruction pipelines full. When the prediction is wrong, the pipeline is flushed, which can make unpredictable branches expensive in tight loops.

Branch-friendly code reduces unpredictable control flow by keeping common paths dominant, simplifying conditions, batching rare cases, or using data layouts that improve predictability. The goal is not to remove every branch but to reduce costly mispredictions in hot code.

#include <vector>

int countValid(const std::vector<int>& values) {
    int count = 0;
    for (int value : values) {
        if (value >= 0) {
            ++count;
        }
    }
    return count;
}

A parser improved throughput after separating rare malformed records from the common fast path to reduce branch misses.

Unpredictable branches hurt hot loops, so shape data and control flow around common cases.
⚠️ Common Mistake

A common mistake is trying to make code branchless everywhere without measuring whether misprediction is actually the bottleneck.

🔁 Follow-Up Question

What kind of input pattern usually makes branch prediction perform poorly?

38 What are SIMD intrinsics and when should you use them? performance

SIMD intrinsics expose CPU vector instructions that operate on multiple data elements in parallel, such as processing four or eight integers in one instruction group. They are architecture-specific wrappers around low-level vector operations.

You should use them only in proven hotspots with regular data patterns and a clear fallback or abstraction strategy. They can deliver major speedups, but they reduce portability and increase maintenance cost.

#include <immintrin.h>

int main() {
    __m128 a = _mm_set_ps(1.f, 2.f, 3.f, 4.f);
    __m128 b = _mm_set_ps(5.f, 6.f, 7.f, 8.f);
    __m128 c = _mm_add_ps(a, b);
    (void)c;
}

An image-processing path used SIMD intrinsics only in its pixel blend kernel after profiling showed it dominated frame time.

SIMD is a targeted optimization for data-parallel hotspots, not a blanket coding style.
⚠️ Common Mistake

Many candidates jump to intrinsics before checking whether the compiler already auto-vectorizes the loop well enough.

🔁 Follow-Up Question

What tradeoffs do intrinsics introduce compared with relying on compiler auto-vectorization?

39 How do you profile C++ code (Valgrind, perf, VTune)? performance

Profiling means measuring where time, memory, cache misses, allocations, or stalls are actually spent before optimizing. Tools such as Valgrind, perf, and VTune show different views of runtime behavior, from function hotspots to microarchitectural bottlenecks.

The right workflow is to reproduce a representative workload, capture measurements, optimize the dominant issue, and re-measure. Good performance work is evidence-driven, not intuition-driven.

# Linux perf
perf record ./pricing_service
perf report

# Valgrind callgrind
valgrind --tool=callgrind ./pricing_service

A service team discovered that a suspected math bottleneck was actually allocator overhead after sampling the real production workload.

Always profile first, because the obvious bottleneck is often not the real one.
⚠️ Common Mistake

A common mistake is timing debug builds or synthetic microbenchmarks that do not match the production execution path.

🔁 Follow-Up Question

Why is representative workload selection critical when profiling?

40 What are custom memory allocators (pool, arena) and when do they help? performance

Custom allocators such as pools and arenas specialize memory allocation for known object lifetimes or fixed-size patterns. They can reduce fragmentation, improve locality, and lower allocator overhead in allocation-heavy systems.

They help when profiling shows general-purpose allocation is a bottleneck or when many objects share simple lifetime rules. They are not free: they add complexity, can waste memory if sized poorly, and must integrate cleanly with ownership rules.

#include <cstddef>
#include <vector>

class Arena {
public:
    explicit Arena(std::size_t size) : buffer_(size), offset_(0) {}

    void* allocate(std::size_t bytes) {
        void* ptr = buffer_.data() + offset_;
        offset_ += bytes;
        return ptr;
    }

private:
    std::vector<std::byte> buffer_;
    std::size_t offset_;
};

int main() {
    Arena arena(1024);
    void* block = arena.allocate(128);
    (void)block;
}

A parser used an arena for per-request temporary objects and freed the whole batch at once after each request completed.

Custom allocators help when lifetime patterns are predictable and allocation cost is measurable.
⚠️ Common Mistake

Many candidates suggest custom allocators too early. They should come after profiling, not before it.

🔁 Follow-Up Question

Why are arena allocators especially effective for batch-style request lifetimes?

Frequently Asked Questions

Written and reviewed by the FreeBytes Editorial Team · Last updated: June 2026