Skip to content

C++ Interview Questions for 3 Years Experience

40 questions with answers for candidates with 3 years of experience — 2 easy, 34 medium, 4 hard.

What interviewers expect: 3 years experience

Three-year candidates are evaluated as independent engineers. Expect questions on concurrency primitives, deadlocks, memory layout, performance profiling and modern C++17 or C++20 features, plus one round of low-level or component design. You should explain trade-offs clearly, such as map versus unordered_map or templates versus virtual functions, and describe measurable impact from your work.

This set is weighted towards Standard Template Library (STL) (7), Performance, Debugging and Build Tools (6), Pointers and Memory Management (6), Modern C++ (C++11 to C++23) (6).

Concurrency and synchronisation Profiling and optimisation Component design Modern C++17 and C++20
Solved at this levelStart practicing
3 QUESTIONS FREESign up to unlock the full question bank and track your progress.Unlock every question and answer, then continue practising company-wise and package-wise.
3 questions free Unlock the rest in seconds
01

What is cache locality and why does it matter for performance?

Save to ▾
Show answer
CPUs load memory in cache lines, usually 64 bytes, and access to L1 cache takes a few cycles while main memory takes hundreds. Code that walks memory sequentially, like iterating a std::vector, uses every byte loaded and lets the prefetcher help. Pointer-heavy structures like linked lists jump around memory and cause cache misses. That is why a vector often beats a list even for insert-heavy workloads.
02

What is RAII and why is it central to C++?

Save to ▾
Show answer
Resource Acquisition Is Initialization ties a resource's lifetime to an object's lifetime. The constructor acquires the resource, such as memory, a file, a socket or a mutex, and the destructor releases it. Because destructors run automatically when a scope ends, even when an exception is thrown, resources are never leaked. std::unique_ptr, std::lock_guard and std::fstream are standard RAII types.
03

A binary runs on your laptop but fails on the server with error while loading shared libraries. What is wrong?

Save to ▾
Show answer
The program depends on a shared library that is missing or in a different version on the server, or the loader cannot find it. I would run ldd on the binary to see unresolved libraries, then install the right package, set the rpath at link time, or ship the library alongside. A newer glibc or libstdc++ on the build machine is a common cause; build inside a container matching production, or link statically where allowed.
04

Your program crashes with the message double free or corruption. What could cause it?

Save to ▾
Show answer
The same block was freed twice or heap metadata was overwritten. Typical causes are a class with a raw owning pointer and the default copy constructor, deleting the same pointer in two places, mixing delete and delete[], or a buffer overflow writing past an allocation. AddressSanitizer shows the first invalid free and where the memory was allocated. The lasting fix is clear ownership with smart pointers or containers.
05

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

Save to ▾
Show answer
Undefined behaviour means the standard places no requirement on the result, so the program may crash, print garbage or appear to work. Examples are signed integer overflow, reading an uninitialised variable, out-of-bounds array access, dereferencing a null or dangling pointer and data races. Optimisers assume UB never happens and remove checks based on that, so such bugs often appear only in optimised release builds.
06

What do compiler optimisation levels like -O0, -O2, -O3 and -Os mean?

Save to ▾
Show answer
-O0 disables optimisation and is best for debugging. -O1 and -O2 enable progressively more optimisations such as inlining, constant propagation and loop optimisations; -O2 is the common release default. -O3 adds aggressive vectorisation and inlining, which may increase code size. -Os optimises for size. Link-time optimisation with -flto optimises across translation units, and -march=native tunes for the current CPU.
07

Use std::variant and std::visit to handle different message types.

Save to ▾
Show answer
#include <iostream> #include <string> #include <variant> struct Login { std::string user; }; struct Logout { int sessionId; }; using Message = std::variant<Login, Logout>; template <class... Ts> struct Overloaded : Ts... { using Ts::operator()...; }; void handle(const Message& m) { std::visit(Overloaded{ [](const Login& l) { std::cout << "login " << l.user << '\n'; }, [](const Logout& o) { std::cout << "logout " << o.sessionId << '\n'; }}, m); } // Adding a new type without a handler becomes a compile error.
08

What is object slicing?

Save to ▾
Show answer
Slicing happens when a derived object is copied into a base object by value. Only the base part is copied, the derived data is lost and virtual calls use the base version. Example: void draw(Shape s) called with a Circle loses Circle's radius and behaviour. Avoid it by passing polymorphic objects by reference or pointer, and consider deleting copy operations in polymorphic base classes.
09

Write a lookup function that uses std::optional instead of returning -1 or a null pointer.

Save to ▾
Show answer
#include <optional> #include <string> #include <unordered_map> std::optional<int> findAge(const std::unordered_map<std::string, int>& db, const std::string& name) { if (auto it = db.find(name); it != db.end()) return it->second; return std::nullopt; } // caller if (auto age = findAge(db, "Meera")) { use(*age); } int a = findAge(db, "Unknown").value_or(0);
10

Remove duplicates from a vector of integers.

Save to ▾
Show answer
#include <algorithm> #include <vector> void dedupSorted(std::vector<int>& v) { std::sort(v.begin(), v.end()); v.erase(std::unique(v.begin(), v.end()), v.end()); } // O(n log n). std::unique only removes adjacent duplicates, so sort first. // To keep the original order, track seen values in an unordered_set instead.
11

How do you prioritise when a critical bug and a feature deadline arrive at the same time?

Save to ▾
Show answer
Situation: a customer-facing crash was reported two days before a feature demo. Task: handle both without dropping quality. Action: I informed my lead immediately, estimated both tasks, and we agreed the crash came first because it affected users. I fixed and released it in half a day, then cut one optional part of the feature after agreeing with the product owner. Result: the crash was resolved and the demo went ahead on time.
12

Is it expensive to return a large std::vector from a function by value?

Save to ▾
Show answer
Usually no. Return value optimisation constructs the result directly in the caller's storage, and since C++17 this copy elision is guaranteed when returning a prvalue. For a named local, compilers apply named RVO, and if they cannot, the vector is moved, which only copies three pointers. So return by value is clean and fast. Do not write return std::move(local); because it can block elision.
13

Tell me about a time you found a difficult bug in your C++ code.

Save to ▾
Show answer
Situation: a service crashed randomly about once a day in production. Task: find the root cause without blocking releases. Action: I enabled core dumps, analysed them in gdb and saw a corrupted vector inside a callback. A staging run with AddressSanitizer showed a use-after-free because a lambda captured a raw pointer to a destroyed object. I changed it to capture a weak_ptr. Result: no crashes for months, and sanitizer runs were added to CI.
14

Describe a time you improved code quality by changing how memory was managed.

Save to ▾
Show answer
Situation: our module used raw new and delete everywhere and QA reported frequent leaks. Task: make it safe without a full rewrite. Action: I proposed a step-by-step migration to unique_ptr and std::vector, wrote a one-page ownership guideline and enabled LeakSanitizer in CI so new leaks failed the build. Result: leak reports dropped to zero within two sprints and reviews became faster because ownership was obvious.
15

Use a C++20 ranges pipeline to get squares of the first three even numbers.

Save to ▾
Show answer
#include <iostream> #include <ranges> #include <vector> int main() { std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8}; auto result = v | std::views::filter([](int x) { return x % 2 == 0; }) | std::views::transform([](int x) { return x * x; }) | std::views::take(3); for (int x : result) std::cout << x << ' '; // 4 16 36 } // Views are lazy: elements are processed only while iterating.
16

You are building a particle simulation with millions of particles. Would you store them as an array of structs or a struct of arrays?

Save to ▾
Show answer
If each update loop touches only a few fields, such as position and velocity, a struct of arrays keeps those fields contiguous, so every cache line is fully used and the compiler can vectorise with SIMD. An array of structs is simpler and better when most fields of one particle are used together. I would benchmark both, and often a hybrid that groups hot fields together works best.
17

How do virtual functions work internally?

Save to ▾
Show answer
Most compilers give each polymorphic class a vtable, a static table of function pointers for its virtual functions. Every object stores a hidden vptr pointing to its class's vtable. A call through a base pointer loads the vptr, looks up the slot and jumps to the right override. The cost is one pointer per object, an indirect call and usually no inlining, which matters only in very hot loops.
18

Two objects hold shared_ptr references to each other and their memory is never released. Why, and how do you fix it?

Save to ▾
Show answer
This is a reference cycle. Each object keeps the other's reference count above zero, so neither destructor runs even after all outside pointers are gone. Fix it by making one direction non-owning, usually the back pointer, with weak_ptr. For example, a parent holds shared_ptr to its children, while each child holds a weak_ptr to the parent and calls lock() when it needs access.
19

What is a deadlock and how do you prevent it?

Save to ▾
Show answer
A deadlock happens when threads wait forever for each other's locks, for example thread A holds m1 and waits for m2 while thread B holds m2 and waits for m1. It needs mutual exclusion, hold and wait, no preemption and circular wait. Prevent it by always locking in a fixed global order, locking several mutexes together with std::scoped_lock, keeping critical sections small and never calling unknown code while holding a lock.
20

Which profiling tools do you use for C++ and what does each show?

Save to ▾
Show answer
On Linux, perf samples the CPU and shows hot functions and call graphs, often visualised as flame graphs. Valgrind callgrind counts instructions precisely but runs slowly, and massif or heaptrack profile memory. gprof is older and needs instrumented builds. Intel VTune and AMD uProf show cache misses and pipeline stalls. On Windows, the Visual Studio profiler covers CPU and memory.
21

Write a program where two threads print odd and even numbers alternately up to 10.

Save to ▾
Show answer
#include <condition_variable> #include <iostream> #include <mutex> #include <thread> std::mutex m; std::condition_variable cv; int n = 1; const int kMax = 10; void printer(int parity) { std::unique_lock<std::mutex> lk(m); while (true) { cv.wait(lk, [&] { return n > kMax || n % 2 == parity; }); if (n > kMax) break; std::cout << n++ << '\n'; cv.notify_all(); } cv.notify_all(); } int main() { std::thread odd(printer, 1), even(printer, 0); odd.join(); even.join(); }
22

Design a class structure for UPI, card and net banking payments in a checkout system.

Save to ▾
Show answer
I would define an abstract PaymentMethod interface with virtual functions such as authorize, capture, refund and a virtual destructor. UpiPayment, CardPayment and NetBankingPayment implement it with their own validation. Checkout depends only on PaymentMethod, and a factory creates the right object from user choice. New methods such as wallets can be added without touching checkout code, which follows the open-closed principle.
23

Write a custom hash so a struct can be used as a key in std::unordered_map.

Save to ▾
Show answer
#include <functional> #include <string> #include <unordered_map> struct Point { int x, y; bool operator==(const Point&) const = default; }; struct PointHash { std::size_t operator()(const Point& p) const noexcept { std::size_t h1 = std::hash<int>{}(p.x); std::size_t h2 = std::hash<int>{}(p.y); return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)); } }; std::unordered_map<Point, std::string, PointHash> grid;
24

What is the erase-remove idiom and what replaced it in C++20?

Save to ▾
Show answer
std::remove does not delete elements; it shifts the kept elements forward and returns an iterator to the new logical end. You then call erase to shrink the container: v.erase(std::remove(v.begin(), v.end(), 0), v.end());. remove_if works the same with a predicate. C++20 adds std::erase(v, value) and std::erase_if(v, pred), which do both steps in one call.
25

Tell me about a time when choosing a better data structure improved performance.

Save to ▾
Show answer
Situation: a pricing job took 40 minutes because it searched a vector of 2 lakh products linearly for every order line. Task: bring it under 5 minutes without changing output. Action: I profiled with perf, confirmed the hot loop, replaced the search with an unordered_map keyed by product ID built once with reserve, and added a regression test. Result: the job finished in about 3 minutes and the team adopted profiling before optimising.
26

What is the difference between full and partial template specialization?

Save to ▾
Show answer
Full specialization provides a separate implementation for one exact set of template arguments, such as template <> class Hash<std::string>. Partial specialization, allowed only for class and variable templates, covers a family of arguments, such as template <typename T> class Hash<T*> for all pointer types. Function templates cannot be partially specialised; overloading is used instead.
27

A program crashes with terminate called without an active exception when a function returns. What is the cause?

Save to ▾
Show answer
A std::thread object that is still joinable was destroyed, often because the function returned early or threw before reaching join. The std::thread destructor calls std::terminate in that case. Fix it by making sure every thread is joined on all paths, using an RAII guard, or switching to std::jthread, which joins automatically. Be careful with detach, since detached threads may access destroyed data.
28

What does C++20 std::jthread add over std::thread?

Save to ▾
Show answer
std::jthread joins automatically in its destructor, so forgetting join no longer calls std::terminate. It also supports cooperative cancellation: the thread function can take a std::stop_token, check stop_requested() in its loop, and the owner calls request_stop(), which the destructor also does before joining. std::stop_callback lets you register cleanup actions. This makes long-running worker threads safer to write.
29

What are placement new and memory pools?

Save to ▾
Show answer
Placement new constructs an object in memory you already own, written new (buffer) T(args). It does not allocate, so you must call the destructor yourself and release the buffer separately. A memory pool pre-allocates large blocks and hands out fixed-size slots, avoiding slow general-purpose allocation and fragmentation. Game engines, low-latency trading systems and std::pmr allocators use these for predictable performance.
30

What are C++20 concepts?

Save to ▾
Show answer
Concepts are named compile-time predicates on template parameters. You can write template <std::integral T> T gcd(T a, T b) or define your own with a requires expression. If a type does not satisfy the concept, the compiler reports a short, readable error at the call site instead of pages of instantiation errors. Concepts also allow overloading by constraint and document the template's expectations.
31

Write a deadlock-free money transfer between two bank accounts.

Save to ▾
Show answer
#include <mutex> #include <stdexcept> struct Account { std::mutex m; long long balance = 0; }; void transfer(Account& from, Account& to, long long amount) { if (&from == &to) return; std::scoped_lock lock(from.m, to.m); // locks both without deadlock if (from.balance < amount) throw std::runtime_error("insufficient funds"); from.balance -= amount; to.balance += amount; }
32

A function taking a Money object is silently called with an int and gives wrong results. What is happening?

Save to ▾
Show answer
Money probably has a single-argument constructor such as Money(int paise) that is not marked explicit, so the compiler performs an implicit conversion from int. A call like pay(500) compiles even though the unit is unclear. Mark single-argument constructors explicit, so callers must write pay(Money(500)). Also consider strong types or named factory functions like Money::fromRupees to make units obvious.
33

What is std::string_view and what are its pitfalls?

Save to ▾
Show answer
string_view is a non-owning pointer and length pair that refers to existing characters, so passing it avoids copies and accepts both std::string and literals. The main pitfall is lifetime: it must not outlive the data it views, so returning a string_view to a local string or a temporary dangles. It is also not guaranteed to be null-terminated, so do not pass data() to C functions expecting C strings.
34

Implement an LRU cache with get and put in O(1).

Save to ▾
Show answer
#include <list> #include <unordered_map> class LRUCache { int cap_; std::list<std::pair<int, int>> items_; // front = most recent std::unordered_map<int, std::list<std::pair<int, int>>::iterator> pos_; public: explicit LRUCache(int capacity) : cap_(capacity) {} int get(int key) { auto it = pos_.find(key); if (it == pos_.end()) return -1; items_.splice(items_.begin(), items_, it->second); return it->second->second; } void put(int key, int value) { if (auto it = pos_.find(key); it != pos_.end()) { it->second->second = value; items_.splice(items_.begin(), items_, it->second); return; } if (static_cast<int>(items_.size()) == cap_) { pos_.erase(items_.back().first); items_.pop_back(); } items_.emplace_front(key, value); pos_[key] = items_.begin(); } };
35

Using a custom struct as the key of an unordered_map gives a compile error. Why, and how do you fix it?

Save to ▾
Show answer
unordered_map needs a hash function and equality comparison for the key type, and std::hash is not defined for user structs. Fix it by defining operator== (or defaulting it in C++20) and providing a hash functor that combines member hashes, passed as the third template argument, or by specialising std::hash for the type. std::map instead needs operator< or a comparator.
36

Write a C++20 concept and use it to constrain a function.

Save to ▾
Show answer
#include <concepts> #include <string> template <typename T> concept Printable = requires(T t) { { t.toString() } -> std::convertible_to<std::string>; }; template <Printable T> std::string describe(const T& obj) { return "Item: " + obj.toString(); } struct User { std::string name; std::string toString() const { return name; } }; // describe(User{"Asha"}) compiles; describe(42) fails with a clear message.
37

Why should you prefer make_unique and make_shared over calling new directly?

Save to ▾
Show answer
They avoid writing raw new, so no allocated object can leak if another expression in the same call throws. make_shared also allocates the object and the control block together in one allocation, which is faster and more cache friendly. One trade-off: with make_shared the memory block stays allocated until every weak_ptr is gone, which matters for very large objects.
38

Write a C++ program that counts how often each word appears in the input.

Save to ▾
Show answer
#include <iostream> #include <map> #include <string> int main() { std::map<std::string, int> freq; std::string word; while (std::cin >> word) { ++freq[word]; } for (const auto& [w, c] : freq) { std::cout << w << ' ' << c << '\n'; } } // Use std::unordered_map for O(1) average lookups if sorted output is not needed.
39

How would you choose between exceptions, error codes and std::expected for error handling?

Save to ▾
Show answer
Exceptions suit rare, truly exceptional failures, keep the happy path clean and work well with RAII, but have unpredictable cost when thrown and are often disabled in embedded or game code. Error codes are explicit and cheap but easy to ignore. C++23 std::expected<T, E> returns either a value or an error, combining explicit handling with clean types. I follow the project convention and use expected for expected failures like parsing.
40

A program does millions of lookups in a std::map with string keys and is slow. How would you speed it up?

Save to ▾
Show answer
If ordering is not needed, switch to std::unordered_map and call reserve with the expected size to avoid rehashing. For string keys, use heterogeneous lookup with string_view to avoid building temporary strings. If keys are fixed after loading, a sorted vector with binary search is very cache friendly. I would profile before and after, because hashing long strings has its own cost.

Frequently asked questions

What is asked in C++ interviews for 3 years of experience?

Three-year candidates are evaluated as independent engineers. Expect questions on concurrency primitives, deadlocks, memory layout, performance profiling and modern C++17 or C++20 features, plus one round of low-level or component design. You should explain trade-offs clearly, such as map versus unordered_map or templates versus virtual functions, and describe measurable impact from your work.

How many questions should I practise before a C++ interview?

Work through all 40 questions here, then the company-wise sets for the employers you are targeting. Quality matters more than count: be able to explain why each answer is right.

Which topics matter most for 3 years of experience?

Focus first on Concurrency and synchronisation, Profiling and optimisation, Component design, Modern C++17 and C++20.