40 questions with answers for candidates with 7 years of experience — 18 medium, 22 hard.
What interviewers expect: 7 years experience
Seven-year candidates are assessed for senior or lead roles. Interviews focus on architecture of large C++ systems, scalability, reliability, build and dependency strategy, and cross-team collaboration. You will discuss how you led migrations, handled critical incidents and balanced delivery pressure with technical debt. Coding rounds still happen, but design and judgement carry more weight.
This set is weighted towards Multithreading and Concurrency (8), Object-Oriented Programming in C++ (8), Standard Template Library (STL) (7), Pointers and Memory Management (5).
Architecture and scalability Technical decision making Incident and risk management Team leadership
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
Using a custom struct as the key of an unordered_map gives a compile error. Why, and how do you fix it?
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.
02
What does C++20 std::jthread add over std::thread?
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.
03
Design a producer-consumer pipeline where one thread reads log lines and several workers parse them.
I would use a bounded thread-safe queue built with a mutex and two condition variables, one for not empty and one for not full, so a fast reader cannot exhaust memory. The reader pushes lines and workers pop and parse them. For shutdown, the reader pushes a sentinel per worker or sets a closed flag and notifies all. Batching lines per push reduces locking overhead, and results go to a second queue.
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.
05
Explain the rule of three, rule of five and rule of zero.
Rule of three: if a class needs a custom destructor, copy constructor or copy assignment, it usually needs all three because it manages a resource. Rule of five adds the move constructor and move assignment from C++11. Rule of zero says the best design avoids writing any of them by holding resources in members such as std::vector, std::string and unique_ptr, which already manage themselves.
06
Which profiling tools do you use for C++ and what does each show?
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.
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.
08
How do you prioritise when a critical bug and a feature deadline arrive at the same time?
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.
Situation: a senior wanted a deep inheritance hierarchy for device drivers. Task: I believed composition would be simpler to test. Action: instead of arguing in the meeting, I built a small prototype of both designs, measured test setup effort and shared the results calmly, while accepting that the final call was theirs. Result: we chose a hybrid with a thin interface plus composition, and the senior later asked me to document it for the team.
11
A low-latency system needs a queue between two threads with no locks. How would you approach it?
For one producer and one consumer, a single-producer single-consumer ring buffer is the standard answer: a fixed-size array with atomic head and tail indices, using release on publish and acquire on read, and padding so the indices sit on different cache lines. It avoids allocation and syscalls. For multiple producers I would use a proven library rather than writing my own, and verify with stress tests and TSan.
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.
13
Two third-party libraries both define a global function called init and your build fails with duplicate symbols. How do you fix it?
The clean fix is to place each library's code in its own namespace so calls become liba::init and libb::init. If the source cannot change, I would check whether one function is only used internally and give it internal linkage, or wrap one library behind a shared library that hides its symbols using visibility settings. I avoid macro renaming hacks because they break silently on upgrades.
Combine a doubly linked list with a hash map. std::list stores key-value pairs in usage order, with the most recent at the front. std::unordered_map maps each key to its list iterator. On get, splice the node to the front in O(1). On put, update or insert at the front and, if over capacity, remove the back node and its map entry. list iterators stay valid after splice, which makes this work.
16
Implement a basic Observer pattern for an event publisher.
std::vector<bool> is a special case that packs bits, so elements are not real bool objects and operator[] returns a proxy object, not bool&. You cannot take a reference or pointer to a single element, and it is not thread-safe to write neighbouring elements from different threads. If you need real bools use std::vector<char>, std::deque<bool>, or std::bitset when the size is fixed.
18
Design a class structure for UPI, card and net banking payments in a checkout system.
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.
19
When would you use std::set versus std::priority_queue?
priority_queue is a binary heap that gives O(1) access to the largest element and O(log n) push and pop, but you cannot search or erase arbitrary elements. std::set keeps all elements sorted with O(log n) insert, erase and find, and gives both minimum and maximum. Use priority_queue for schedulers and Dijkstra, and set or multiset when you must update or remove arbitrary items.
20
A production service's memory usage grows steadily over several days. How do you find the cause?
First confirm it is a real leak and not a cache or allocator fragmentation by tracking RSS and allocator statistics. Then reproduce in staging with a heap profiler such as heaptrack or jemalloc profiling and compare snapshots to see which call stacks keep growing. LeakSanitizer catches lost blocks. Common culprits are unbounded maps, shared_ptr cycles and listeners never removed. After the fix, add a long soak test.
21
You parallelised a loop across eight threads but it became slower than the single-threaded version. What could be wrong?
Likely causes are lock contention when all threads share one mutex, false sharing when per-thread counters sit on the same 64-byte cache line, too little work per task so thread creation dominates, or memory bandwidth limits. I would profile with perf, pad per-thread data with alignas(64) or std::hardware_destructive_interference_size, use thread-local accumulation and a thread pool, and make chunks larger.
22
The release binary has grown very large and analysis shows many template instantiations. What can you do?
Each distinct set of template arguments generates separate code, so heavy generic code bloats binaries. I would find the biggest symbols with tools like bloaty or nm, move type-independent logic into a non-template base or helper, use type erasure for rarely hot paths, limit instantiations with extern template, and enable link-time optimisation plus identical code folding to merge duplicate functions.
std::async runs a function, possibly on another thread, and returns a std::future that will hold its result or exception; calling get() waits for it. Use std::launch::async to force a new thread. std::promise is the writing end: one thread calls set_value or set_exception, and the paired future receives it. They make simple fork-join tasks easier than manual threads, but are not a full thread pool.
24
You are building a particle simulation with millions of particles. Would you store them as an array of structs or a struct of arrays?
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.
25
Should a function take a shared_ptr by value, by const reference, or take a raw pointer or reference?
It depends on ownership. If the function only uses the object, take T& or T* and let the caller manage lifetime. Take shared_ptr by value only when the function stores or shares ownership, and then move it into place. Passing shared_ptr by value everywhere adds atomic reference count updates that slow down hot paths and hide who really owns the object.
26
What is the difference between full and partial template specialization?
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
Many threads read a shared configuration object constantly, but it changes only once an hour. How do you synchronise it efficiently?
A plain mutex would serialise all readers. Option one is std::shared_mutex: readers take shared_lock and run in parallel, the updater takes unique_lock. Option two, often faster, is to keep the config in a std::shared_ptr<const Config> and swap in a new immutable object atomically with std::atomic<std::shared_ptr> in C++20; readers grab a copy of the pointer and never block the writer.
28
Tell me about a code review where you pointed out an important issue in a colleague's code.
Situation: a colleague's change stored pointers to vector elements in a map and then appended to the vector. Task: flag the risk without discouraging them. Action: I commented with a short example showing reallocation invalidating the pointers, suggested storing indices or using std::deque, and offered to pair on the fix. Result: the bug was fixed before release, and we added a checklist item on iterator invalidation for reviews.
29
You inherit a 3000-line class that handles parsing, validation, database and logging. How would you refactor it?
It breaks the single responsibility principle. First I add characterisation tests around current behaviour. Then I extract cohesive pieces into separate classes such as Parser, Validator and Repository, injected through interfaces so they can be mocked. I move step by step, keeping the old public API as a facade, and merge small refactors frequently instead of one big rewrite, so risk stays low and reviews stay easy.
Options include static polymorphism with CRTP or templates so calls can be inlined, marking classes or functions final so the compiler can devirtualise, and batching work by type so each loop handles one concrete class. Another option is replacing the hierarchy with std::variant and std::visit. I would confirm with a benchmark first, because often memory layout matters more than the virtual call itself.
32
Build times have become very slow because heavy templates are included everywhere. How do you speed them up?
I would measure first with -ftime-trace or build analyzers to find the costly headers. Then reduce includes with forward declarations, move template-heavy implementation details behind non-template interfaces, use extern template with explicit instantiation for common types, enable precompiled headers and a compiler cache such as ccache, and evaluate C++20 modules where the toolchain supports them well.
33
You must find the top 100 most frequent items in a stream of 10 million events. How would you do it?
Count frequencies with an unordered_map reserved to the expected key count. Then keep a min-heap, a priority_queue with greater comparator, of size 100: push each entry and pop when size exceeds 100, which costs O(n log k). Alternatively copy counts into a vector and use std::nth_element for O(n) average. If memory is limited and approximate results are fine, a Count-Min sketch helps.
34
Tell me about a time when choosing a better data structure improved performance.
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.
35
Explain move semantics and what std::move actually does.
Move semantics let an object transfer its resources, such as a heap buffer, to another object instead of copying them. A move constructor takes T&& and usually steals the pointer and leaves the source empty but valid. std::move does not move anything; it is just a cast to an rvalue reference that allows a move overload to be chosen. After moving, only assign to or destroy the source.
36
You are asked to modernise a large C++98 codebase to C++17. How would you plan it?
First switch the compiler standard flag and fix build errors, with CI running the full test suite. Then add clang-tidy modernize checks to convert NULL to nullptr, add override and use auto where it helps, applied module by module in small reviewed batches. Next replace raw owning pointers with unique_ptr in new and touched code. I avoid a big-bang rewrite, track progress with metrics and train the team as we go.
37
Your application is slow and you are asked to optimise it. Where do you start?
I start by measuring, not guessing. I define a target, such as p99 under 50 ms, create a repeatable benchmark, and profile with perf to find where time actually goes. Then I fix the biggest hotspot first, usually algorithmic complexity, unnecessary copies, allocations or I/O, and re-measure after each change. I keep correctness tests running and stop when the target is met.
38
A function taking a Money object is silently called with an int and gives wrong results. What is happening?
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.
39
Write a move constructor and move assignment operator for a class that owns a buffer.
What is asked in C++ interviews for 7 years of experience?
Seven-year candidates are assessed for senior or lead roles. Interviews focus on architecture of large C++ systems, scalability, reliability, build and dependency strategy, and cross-team collaboration. You will discuss how you led migrations, handled critical incidents and balanced delivery pressure with technical debt. Coding rounds still happen, but design and judgement carry more weight.
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 7 years of experience?
Focus first on Architecture and scalability, Technical decision making, Incident and risk management, Team leadership.