All C++ Interview Questions

What is the difference between std::string and a char array in C++?

Pointstd::stringchar array
SizeGrows by itselfFixed
Joina + bstrcat (can overflow)
Comparea == bstrcmp
Lengthsize()strlen
SafetySafeBuffer overflow risk
#include <cstring>
#include <iostream>
#include <string>

int main() {
    std::string s = "Hello";
    s += " World";
    std::cout << s << ' ' << s.size() << '\n';

    char c[] = "Hello";
    std::cout << sizeof(c) << ' ' << std::strlen(c) << '\n';      // 6 bytes (with \0), 5 characters
}
Output

Hello World 11
6 5

See C++ Strings.

What is the STL in C++?

The STL has three main parts:

  • Containers: vector, list, map, set, unordered_map, stack, queue.
  • Iterators: objects which point to the items of a container.
  • Algorithms: sort, find, count, transform, accumulate and many more.
#include <algorithm>
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {4, 1, 3};
    std::sort(v.begin(), v.end());
    for (int x : v) std::cout << x << ' ';
    std::cout << '\n';
}
Output

1 3 4

See STL Containers and Algorithms.

What is the auto keyword in C++?

#include <iostream>
#include <map>
#include <string>
#include <vector>

int main() {
    auto n = 5;                                  // int
    auto d = 2.5;                                // double
    auto name = std::string("C++");
    std::vector<std::map<std::string, int>> data = {{{"a", 1}}};
    for (const auto& m : data) {                 // no long type name
        std::cout << m.size() << '\n';
    }
    std::cout << n << ' ' << d << ' ' << name << '\n';
}
Output

1
5 2.5 C++

  • auto needs an initial value.
  • auto removes const and references: use const auto& when you need them.

See C++ Variables.

What is the difference between const and constexpr in C++?

Pointconstconstexpr
MeaningRead only after the startComputed at compile time
Value knownCan be at run timeMust be at compile time
Use for array sizesOnly if the value is a compile time constantYes
#include <array>
#include <iostream>

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

int main() {
    constexpr int size = square(3);               // calculated while compiling
    std::array<int, size> a{};
    const int input = 5;                          // const, set at the start
    std::cout << a.size() << ' ' << input << '\n';
}
Output

9 5

See C++ Variables.

What is the static keyword in C++?

  • Static local variable: keeps its value between calls.
  • Static data member: one copy for the whole class.
  • Static member function: can be called without an object, and has no this.
  • Static at file level: the name is visible only in that file.
#include <iostream>

int next_id() {
    static int id = 0;           // set only once
    return ++id;
}

struct Counter {
    static inline int total = 0;
    Counter() { total++; }
};

int main() {
    std::cout << next_id() << next_id() << next_id() << '\n';
    Counter a, b;
    std::cout << Counter::total << '\n';
}
Output

123
2

See Static Members.

What are access specifiers in C++?

SpecifierWho can use it
publicEveryone
protectedThe class and derived classes
privateOnly the class (and friends)
class A {
private:
    int secret = 1;
public:
    int open = 2;
};

int main() {
    A a;
    int x = a.open;
    int y = a.secret;
}
Output

Private.cpp: In function 'int main()':
Private.cpp:11:15: error: 'int A::secret' is private within this context
11 | int y = a.secret;
| ^~~~~~
Private.cpp:3:9: note: declared private here
3 | int secret = 1;
| ^~~~~~
Private.cpp:10:9: warning: unused variable 'x' [-Wunused-variable]
10 | int x = a.open;
| ^
Private.cpp:11:9: warning: unused variable 'y' [-Wunused-variable]
11 | int y = a.secret;
| ^

See Access Specifiers and Encapsulation.

What is exception handling in C++?

Use throw to report an error and try ... catch to handle it.

#include <iostream>
#include <stdexcept>

int main() {
    try {
        throw std::runtime_error("something went wrong");
    } catch (const std::exception& e) {
        std::cout << "Caught: " << e.what() << '\n';
    }
}
Output

Caught: something went wrong

See C++ Exceptions.

How to compile and run a C++ program?

#include <iostream>

int main() {
    std::cout << "Hello, C++!\n";
}
Terminal

$ g++ -std=c++23 -Wall -Wextra hello.cpp -o hello
$ ./hello
Hello, C++!

The steps inside are: preprocessing, compiling, assembling and linking. See C++ Installation and Setup.

What is RAII in C++?

RAII (Resource Acquisition Is Initialization) ties the life of a resource (memory, file, lock, socket) to the life of an object. The constructor takes the resource, and the destructor releases it. When the object leaves its scope, C++ calls the destructor, even if an exception is thrown.

#include <iostream>
#include <mutex>
#include <stdexcept>

std::mutex m;

void risky() {
    std::lock_guard<std::mutex> lock(m);        // RAII: locks here
    std::cout << "locked\n";
    throw std::runtime_error("oops");           // the lock is still released
}

int main() {
    try {
        risky();
    } catch (const std::exception& e) {
        std::cout << "caught: " << e.what() << '\n';
    }
    std::cout << "can lock again: " << m.try_lock() << '\n';
}
Output

locked
caught: oops
can lock again: 1

Standard RAII types: std::unique_ptr, std::vector, std::string, std::lock_guard and std::ofstream. See Constructors and Destructors.

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

RuleMeaning
Rule of ZeroDo not write special member functions. Let std::string, vector and smart pointers handle the resources
Rule of ThreeIf you need a custom destructor, copy constructor or copy assignment, you probably need all three
Rule of FiveAdd the move constructor and move assignment to the three
#include <iostream>
#include <string>
#include <vector>

struct Person {                                   // Rule of Zero: no special functions written
    std::string name;
    std::vector<int> scores;
};

int main() {
    Person a{"Riya", {80, 90}};
    Person b = a;                                  // a correct copy, made by the compiler
    Person c = std::move(a);                       // a correct move
    std::cout << b.name << ' ' << b.scores.size() << ' ' << c.name << ' ' << a.name.empty() << '\n';
}
Output

Riya 2 Riya 1

See Move Semantics and the Rule of Five.

What does std::move do in C++?

std::move(x) is only a cast to T&&. The real moving is done by the move constructor or move assignment. After a move, the old object is valid but its value is unspecified (usually empty).

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

int main() {
    std::string a(30, 'x');
    std::string b = a;                      // copy: both have the text
    std::string c = std::move(a);           // move: c takes the buffer
    std::cout << a.size() << ' ' << b.size() << ' ' << c.size() << '\n';
}
Output

0 30 30

Do not use std::move on a returned local variable: return x; already moves, and a manual std::move can stop the compiler from skipping the copy completely (copy elision). See Move Semantics.

What is the difference between unique_ptr, shared_ptr and weak_ptr?

TypeOwnersCopyCost
unique_ptrOneNo (move only)None
shared_ptrManyYesReference count (atomic)
weak_ptrNoneYesSmall
#include <iostream>
#include <memory>

int main() {
    auto u = std::make_unique<int>(1);
    auto v = std::move(u);                          // move the ownership

    auto s1 = std::make_shared<int>(2);
    std::weak_ptr<int> w = s1;                      // does not increase the count
    auto s2 = s1;
    std::cout << (u == nullptr) << ' ' << s1.use_count() << ' ' << w.use_count() << '\n';

    s1.reset();
    s2.reset();                                     // the last owner is gone
    std::cout << w.expired() << '\n';
    std::cout << (w.lock() == nullptr) << '\n';
}
Output

1 2 2
1
1

Start with unique_ptr. Use shared_ptr only when ownership is really shared. weak_ptr breaks cycles, where two shared objects point to each other. See Smart Pointers.

Why do we need a virtual destructor in C++?

If you delete a derived object through a base pointer and the base destructor is not virtual, only the base destructor runs. This is undefined behavior, and the resources of the derived class leak.

#include <iostream>
#include <memory>

struct Base {
    virtual ~Base() { std::cout << "~Base\n"; }
};

struct Derived : Base {
    ~Derived() override { std::cout << "~Derived\n"; }
};

int main() {
    std::unique_ptr<Base> p = std::make_unique<Derived>();
}   // both destructors run, because ~Base is virtual
Output

~Derived
~Base

Rule: a class which is used as a base class for polymorphism must have a virtual destructor (or a protected non virtual one). See Polymorphism.

How do virtual functions work in C++ (vtable and vptr)?

The compiler makes one vtable for each class which has virtual functions. It is a table of function pointers. Each object gets a hidden pointer, the vptr, to the vtable of its real class. A virtual call follows the vptr to find the right function at run time.

You can see the cost of the hidden pointer in the size of the objects:

#include <iostream>

struct Plain   { int x; };
struct Virtual { int x; virtual void f() {} };

int main() {
    std::cout << sizeof(Plain) << ' ' << sizeof(Virtual) << '\n';
}
Output

4 16

On a 64 bit system the virtual class is bigger by the size of a pointer (and padded). A virtual call costs a little more than a normal call, and the compiler can often not put it inline. See Virtual Functions.

What is object slicing in C++?

#include <iostream>

struct Base {
    virtual const char* who() const { return "Base"; }
};
struct Derived : Base {
    int extra = 42;
    const char* who() const override { return "Derived"; }
};

int main() {
    Derived d;
    Base sliced = d;                    // copies only the Base part
    const Base& ref = d;                // no copy
    std::cout << sliced.who() << ' ' << ref.who() << '\n';
}
Output

Base Derived

Pass polymorphic objects by reference, pointer or smart pointer, and store pointers in containers (std::vector<std::unique_ptr<Base>>), not objects. See Polymorphism.

What is const correctness in C++?

  • const variables: const int max = 10;
  • const reference parameters: read without a copy.
  • const methods: promise not to change the object (int size() const).
  • const pointers: const int* (value const) and int* const (pointer const).
#include <iostream>
#include <string>

class User {
public:
    explicit User(std::string n) : name(std::move(n)) {}
    const std::string& get_name() const { return name; }     // does not change the object
    void rename(std::string n) { name = std::move(n); }       // changes it: not const
private:
    std::string name;
};

void print(const User& u) {          // can only call const methods
    std::cout << u.get_name() << '\n';
    // u.rename("x");                // error if enabled
}

int main() {
    User u("Riya");
    print(u);
}
Output

Riya

Also the keyword mutable lets a member change inside a const method (used for caches and mutexes). See Classes.

What is undefined behavior in C++? Give examples.

Undefined behavior (UB) is code for which the standard gives no rules. The compiler may assume it never happens, so the result is anything: a crash, a wrong answer, or “it works on my machine”.

  • Reading a variable which is not initialized.
  • An array index out of range, or dereferencing nullptr.
  • Signed integer overflow (unsigned overflow is defined: it wraps).
  • Using an object after it is deleted (dangling pointer).
  • Two threads changing the same data without a lock (data race).
  • Modifying a const object, or a missing return in a function which returns a value.

Tools find UB while testing. We compile this code with the undefined behavior sanitizer:

#include <climits>
#include <iostream>

int main() {
    int x = INT_MAX;
    x = x + 1;                          // signed overflow: undefined behavior
    std::cout << x << '\n';
}
Terminal

$ g++ -std=c++23 -fsanitize=undefined ub.cpp -o ub
$ ./ub 2>&1 | sed 's/^[^ ]*ub.cpp/ub.cpp/'
ub.cpp:6:7: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
-2147483648

Use -fsanitize=address,undefined and -Wall -Wextra in every test build.

What is the difference between std::map and std::unordered_map?

Pointstd::mapstd::unordered_map
InsideBalanced treeHash table
OrderSorted by keyNo order
Find, insertO(log n)O(1) on average
Key needsoperator<hash and ==
IteratorsStay valid after insertCan be invalidated by a rehash
#include <iostream>
#include <map>
#include <string>
#include <unordered_map>

int main() {
    std::map<std::string, int> sorted{{"pear", 3}, {"apple", 1}, {"mango", 2}};
    for (const auto& [k, v] : sorted) std::cout << k << ' ';        // always sorted
    std::cout << '\n';

    std::unordered_map<std::string, int> fast{{"pear", 3}, {"apple", 1}};
    std::cout << fast.at("pear") << ' ' << fast.contains("kiwi") << '\n';
}
Output

apple mango pear
3 0

Choose unordered_map when you only look up by key. Choose map when you need the order or range searches. See STL Containers.

What is the difference between push_back and emplace_back in C++?

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

struct Item {
    Item(std::string n, int q) : name(std::move(n)), qty(q) { std::cout << "construct " << name << '\n'; }
    Item(const Item& o) : name(o.name), qty(o.qty) { std::cout << "copy " << name << '\n'; }
    Item(Item&& o) noexcept : name(std::move(o.name)), qty(o.qty) { std::cout << "move " << name << '\n'; }
    std::string name;
    int qty;
};

int main() {
    std::vector<Item> v;
    v.reserve(4);                       // avoid moves caused by growing
    v.push_back(Item("pen", 1));        // builds a temporary, then moves it
    v.emplace_back("book", 2);          // builds directly in the vector
}
Output

construct pen
move pen
construct book

emplace_back avoids one move. For simple types the difference is small, and push_back is fine. See Vectors.

What is iterator invalidation in C++?

A change to a container can make old iterators, pointers and references invalid. Using them is undefined behavior. For a vector, push_back can move all the items to a bigger memory block when the capacity is full.

#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3};
    const int* before = v.data();
    v.reserve(1000);                          // the data moves to a new place
    const int* after = v.data();
    std::cout << "data moved: " << (before != after) << '\n';
    // an iterator or pointer taken before reserve() must not be used now
}
Output

data moved: 1

ContainerAfter insertAfter erase
vectorAll invalid if it reallocates, else from the insert pointFrom the erased item onward
list, map, setStay validOnly the erased item is invalid
unordered_mapCan all become invalid on a rehashOnly the erased item

Safe habits: use indexes, call reserve early, and do not keep iterators across changes. See Vectors.