C++ is a powerful, general-purpose programming language used for everything from back ends to embedded development, to desktop apps, to the very operating systems on which they run. It’s one of the very few languages to have stood the test of time, hence its well-deserved popularity and respect. But despite the efforts of the ISO C++ Standards Committee and the community to make it more programmer-friendly, it’s still arguably one of the hardest languages to master.
What’s Special About C++?
A great C++ developer is primarily a great software developer: someone with a strong problem-solving skillset and abstract thinking, the ability to find the right tools and frameworks to work with, and a passion for computer science.
There are plenty of interview questions that are language independent and designed to check the engineering prowess of the candidate; on a more basic level, the FizzBuzz question is famously effective at filtering for general coding ability. But our current focus will be very specific to C++.
If the candidate is also familiar with application development in other languages, there’s an opportunity to make the interview process even more fruitful. In that case, these first three questions will kick off an interesting discussion about the philosophies of different languages, their fundamental structures, and the advantages and disadvantages deriving from them.
Q: What is RAII and how does it relate to the fact that there is no finally
keyword in C++ exception handling?
RAII stands for “resource acquisition is initialization” and is a C++-specific resource management technique. It’s based on the fact that C++ has destructors and a guarantee that they’ll be called for an object when it’s going out of scope, even in exception handling.
We don’t need finally
to deal with our resources because we can wrap them up with RAII and be sure that the destructor will be called, thanks to stack unwinding.
Q: What is const-correctness? What’s its purpose (value)?
Const-correctness is the practice of specifying variables and member functions as const
if they are not going to be modified or modify the state of the object, respectively. The best practice is to use const
whenever possible.
const
is a contract between the programmer and the compiler—it doesn’t manifest itself in the generated machine code in any way. It’s an elegant way of documenting code, making it more readable and less error-prone. Consider the following code snippet:
void PaySalary(const EmployeeId employee_id)
{
Employee& employee = company.FindEmployee(employee_id);
const auto base_salary = employee.GetBaseSalary();
const auto performance_rating = employee.MonthlyPerformance();
const auto debt = company.GetDebt(employee);
const auto total_pay = CalculateSalary(base_salary, performance_rating, debt);
company.PaySalary(employee, total_pay);
}
In this function, we acquire a reference to an object and do some further operations. One might wonder what happens with this object—in particular, when might it be modified?
The fact that GetBaseSalary
and MonthlyPerformance
are const
member functions tells us that employee
will not change its state after these calls. GetDebt
takes a const Employee&
, which, again, means that it hasn’t been modified. The PaySalary
function, however, takes an Employee&
, so that’s where we would dig in.
One should always pay extra attention to const correctness when designing a class, as it contains a message to the developers who will use it.
The Newer C++ Standards
C++ has picked up an unprecedented pace of development during the last decade. We’ve had a new standard every three years since 2011: C++11, C++14, C++17, and C++20. C++14 was a minor update over C++11, but all the others came with significant new features. The knowledge of these features is obviously necessary, but it’s also important to know their alternatives for the older standards.
Even though the vast majority of the changes in the newer standards are additions—deprecations and removals are very rare—switching a codebase to a new standard is far from easy. A proper switch, making good use of the new features, will require a lot of refactoring.
And there is still a huge amount of C++98 and C++03 code out there requiring constant care and attention. The same goes for C++11 and C++14. Besides knowing about the constexpr if
(which came with C++17), a great C++ programmer should also know how to achieve the same results when stuck with an older standard.
An interesting approach to start a discussion is to ask the candidate about their favorite feature(s). A strong candidate should be able to list the most important features of a given standard, choose a favorite one, and give reasons for their choice. Of course, there is no right or wrong answer, but it quickly reveals the candidate’s feel for the language.
Q: C++11/14 introduced several significant features. Which do you find brought the biggest improvement, and why?
The auto
Keyword and Range-based for
Loops
The auto
keyword frees us from having to explicitly specify the type of a variable when the compiler is able to deduce it. This results in cleaner, more readable, and more generic code. The range-based for
loop is syntactic sugar for the most common use case.
// before C++11
for (std::vector<int>::const_iterator it = vec.begin(); it != vec.end(); ++it) {
// after C++11
for (const auto& val : vec) {
Lambda Functions
This is a new syntax allowing developers to define function objects in-place.
std::count_if(vec.begin(), vec.end(), [](const int value) { return value < 10; });
Move Semantics
This allows us to explicitly define what it means for an object to take ownership of another object. As a result, we get to have move-only types (std::unique_ptr
, std::thread
), efficient shallow copy from temporaries, etc. This is a pretty big topic nicely covered by a chapter of Scott Meyers’ Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14.
Smart Pointers
C++11 introduced new smart pointers: unique_ptr
, shared_ptr
, and weak_ptr
. unique_ptr
effectively made auto_ptr
obsolete.
Built-in Support for Threads
No need to struggle with OS-native and C-style libraries.
Q: Which feature of C++17 do you find most useful, and why?
Structured Bindings
This feature increases readability.
// before C++17
for (const auto& name_and_id : name_to_id) {
const auto& name = name_and_id.first;
const auto& id = name_and_id.second;
// after C++17
for (const auto& [name, id] : name_to_id) {
Compile-time if
With if constexpr
, we can have different parts of code enabled depending on a compile-time condition. This makes the template metaprogramming (TMP) enable_if
magic easier and nicer to achieve.
Built-in Filesystem Library
Just like with threads support in C++11, this built-in library continues to reduce the need for C++ developers to write OS-specific code. It provides an interface to work with files as such (not their content) and directories, letting developers copy them, remove them, iterate over them recursively, and so on.
Parallel STL
Parallel alternatives of much-used STL algorithms are included with C++17—an important feature ever since multi-core processors have become commonplace even on the desktop.
Q: Which feature of C++20 do you think is a nice addition, and why?
There are at least two promising C++20 features for developers.
Constraints and Concepts
This feature provides a rich new syntax to make TMP more structured, and its constructs reusable. If used properly, it can make code more readable and better-documented.
Ranges
The C++ programmers behind the ranges-v3 library advocated for its inclusion in C++20. With Unix pipe-like syntax, composable constructs, lazy range combinators, and other features, this library aims to give C++ a modern, functional look.
std::vector<int> numbers = …;
for (auto number : numbers
| std::views::transform(abs)
| std::views::filter(is_prime)) {
…
}
// the alternative without ranges
for (auto orig_number : numbers) {
auto number = abs(orig_number);
if (!is_prime(number)) {
continue;
}
…
}
In the example above, the transform
and filter
operations are performed on the run, without affecting the content of the original container.
Initially, Bjarne Stroustrup, the creator of C++, had named it “C with classes,” with the motivation of supporting object-oriented programming (OOP). A general understanding of the OOP concepts and design patterns is outside the language, so checking the OOP knowledge of a C++ programmer doesn’t need to be C++-specific in most ways. However, there are some minor specificities. A very common interview question is:
Q: How does dynamic polymorphism (virtual functions) work in C++?
In C++, dynamic polymorphism is achieved through virtual functions.
class Musician
{
public:
virtual void Play() = 0;
};
class Pianist : public Musician
{
public:
virtual void Play() override {
// piano-playing logic
}
};
class Guitarist : public Musician
{
public:
void Play() override {
// guitar-playing logic
}
};
void AskToPlay(Musician* musician)
{
musician->Play();
}
The polymorphism (from the Greek poly, meaning “many,” and morph, meaning “shape”) here is that AskToPlay
behaves in different ways depending on the type of the object musician
points to (it can be either a Pianist
or a Guitarist
). The problem is that C++ code has to compile into a machine code that has a fixed set of instructions for each, including and especially the AskToPlay
function. Those instructions have to choose where to jump (call) at run-time, to Pianist::Play
or Guitarist::Play
.
The most common and efficient approach for the compiler is to use virtual tables (or vtables). A virtual table is an array of addresses to virtual functions. Each class has its own vtable, and each instance of that class has a pointer to it. In our example, the call to Play
actually becomes a call to a function that resides at the address written in the first entry of the vtable that *musician
points to. If it had been instantiated as a Pianist
, its pointer to vtable would have been set to Pianist
’s vtable, and we’d end up calling the right function.
Another specificity comes from the fact that C++ supports multiple inheritance and there is no formal distinction between an interface and a class. The classic question of the diamond problem is a good starter to discuss that.
Being a multiparadigm programming language, C++ also supports functional programming (FP), which has become an even bigger deal after the ranges were introduced in the C++20 standard. It has brought C++ closer to the other, more FP-friendly (or FP-focused) languages from the point of view of the expressiveness and readability of the functional code. An example of a starter question would be:
Q: How do lambda functions work?
The compiler generates a functor class with an operator()
with the body of the lambda, and with members to store the copies of or references to the captured variables of the lambda. Here’s an example.
It’s the same as with the other question regarding the virtual functions: Either the candidate has looked under the hood and knows exactly how it works, or it’s a good place to start a general discussion with some follow-up questions like Where do they mostly come in handy? and What was C++ programming like before lambda functions?.
Template meta-programming is yet another paradigm supported by C++. It encompasses a number of useful features, which have been accompanied by better alternatives introduced in newer standards. New features like constexpr
and concept
s make TMP less puzzling, but then again, there is still a lot of production code with puzzles. A famous TMP puzzle question is:
Q: How can you calculate _n_th Fibonacci number at compile-time?
With the newer standards, it’s as trivial as having a constexpr
specifier in the beginning of the recursive implementation.
constexpr int fibo(int n)
{
if (n <= 1) {
return n;
}
return fibo(n - 1) + fibo(n - 2);
}
Without that, it will be:
template <int N>
struct Fibo
{
static const int value = Fibo<N - 1>::value + Fibo<N - 2>::value;
};
template <>
struct Fibo<1>
{
static const int value = 1;
};
template <>
struct Fibo<0>
{
static const int value = 0;
};
STL, Data Structures, and Algorithms
The Standard Template Library (STL) is a library built into C++. It’s been with C++ for so long now that it’s hard to segregate it from the core language. STL brings C++ developers four kinds of features:
- Containers—implementations of some fundamental data structures, like
vector
, list
, map
, and unordered_map
- Algorithms—implementations of some fundamental algorithms, like
sort
, binary_search
, transform
, and partition
- Iterators—an abstraction of containers for algorithms to work with
- Functors—a means to customize algorithms even more
STL’s design and philosophy are unique and quite extensive. Any decent C++ programmer must have strong knowledge of STL, but the question is, to which extent? The first level would be the so-called user level knowledge. That’s when the candidate knows at least the most popular containers and algorithms, how and when to use them. The next three are some classic questions to check this:
Q: What are the differences between list
and vector
? How should C++ developers choose among them?
Both list
and vector
are sequential containers but based on different data structures. list
is based on a doubly linked list, whereas vector
contains a raw array.
The advantages are split between them. vector
has the advantages of storing the data consecutively, with no memory overhead and constant-time indexed access. In contrast, list
has constant-time insertion and removal at any position, and supports features like splice
, merge
, and in-place sort
and reverse
.
As a result, vector
is more popular and most of the time is the right choice. list
will be the better choice in some corner cases, like when dealing with heavy-to-copy objects, so that developers can keep them in order, or move from one container to another by just manipulating their node connections.
Q: How can you remove all _42_s from a vector
? What about a list
?
The trivial approach would be to iterate over the container and erase each occurrence with the erase
member function, which both containers have. This is perfectly valid in the case of a list
; in fact, its remove
member function does pretty much the same.
But there’s a reason why vector
doesn’t have such a member function. This approach is quite inefficient because of vector
’s underlying data structure. The elements of vector
are consecutive in the memory, so erasing an element means shifting all subsequent elements back by one position. That will result in a lot of extra copying.
The best approach is the erase-remove idiom:
vec.erase(std::remove(vec.begin(), vec.end(), 42), vec.end());
// initially - { 1, 2, 42, 3, 4, 42, 5, 6 }
// after the remove - { 1, 2, 3, 4, 5, 6, ?, ?, }
// after the erase - { 1, 2, 3, 4, 5, 6 }
First, we apply remove
on the whole range, which doesn’t remove 42
s from it—it just moves the others to the beginning, and it does that in the most efficient way. Instead of moving 6
to the left twice by one position (which would happen if we individually erased 42
s), it only moves 6
once, by two positions. With erase
then, we erase the “waste” at the end.
This idiom is nicely covered in Item 32 of Scott Meyers’ legendary Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library.
Q: What are the different types of iterators in C++? Which ones work with std::sort
?
There are six categories of STL iterators: input, output, forward, bidirectional, random access, and contiguous.
The category of an iterator denotes its functionality, and those categories are inclusive. This means, saying ‘the iterator of set
is bidirectional’ effectively means it’s also forward
but neither random access nor contiguous.

Differences of the categories are due to the data structures of the containers. A vector
iterator is a random access one, which means, unlike a set
iterator, it can jump to any point in constant time. std::sort
requires its parameters to be random access iterators.
These are arguably the most popular parts of STL, so any C++ programmer with at least a year of experience has met and worked with them. But that’s not enough. A high-quality C++ developer has to know not only all the STL containers and how to use them but also the abstract data structures behind them, advantages and drawbacks of each.
Also, not only should they know most of the algorithms but also their asymptotic complexities. This means C++ developers should understand, in big-O terms, the performance of standard algorithms and the data structures they use, and also the theory behind both. Questions can be very specific:
STL Container Fundamentals
Rather than test exhaustively for this knowledge, an audit-style selection of a few facts—more if needed—should make it clear enough whether the candidate is well-versed enough in these commonly-needed details.
- How is [a given container, e.g.,
vector
] implemented?
- Of which category are the iterators of [a given container]?
- What’s the asymptotic complexity of [a given operation, e.g.,
remove
] on [a given container]?
vector
stores the elements in an underlying array, with a size marker. When it’s full, a new, bigger storage is allocated, the elements are copied from the old storage, which then is released. Its iterators are of the random access category. The asymptotic complexity of insert (push_back
) is amortized constant time, indexed access is constant time, remove is linear.
list
implements a doubly linked list, storing pointers to the head and tail nodes. Its iterators are bidirectional. The complexity of insert (push_back
and push_front
) and remove (pop_back
and pop_front
) are constant time, whereas indexed access is linear.
set
and map
are implemented on a balanced binary search tree, more specifically a red-black tree, thus keeping the elements sorted. Their iterators are bidirectional as well. The complexities of insert, find (lookup), and remove are logarithmic. Removing an element with an iterator (erase
member function) has amortized constant time complexity.
unordered_set
and unordered_map
are the implementations of the hash table data structure. Usually, it’s an array of so-called buckets, which are simply linked lists. A bucket is chosen for an element (during the insert or lookup) depending on its hash value and the total number of buckets. When there are so many elements in the container that the average bucket size is greater than an implementation-defined threshold (usually 1.0), the number of buckets is increased and the elements are moved to the right buckets. This process is called rehashing, and it makes the complexity of insertion amortized constant time. Find and remove have constant time complexities.
These are the most basic containers—knowledge of the above details is pretty much a must, but whether every last detail is tested for is up to the interviewer’s intuition.
Q: What’s the asymptotic complexity of [a given algorithm]?
The asymptotic complexities of some famous STL algorithms are:
Algorithm |
Complexity |
std::sort |
O(N log(N)) |
std::nth_element |
O(N) |
std::advance |
O(1) for random access iterators, and O(N) otherwise |
std::binary_search |
O(log(N)) for random access iterators, and O(N) otherwise |
std::set_intersection |
O(N) |
Or, a single question to cover it all:
Q: For which containers can the insertion operation invalidate the iterators?
The elegance of this question is that it’s very hard to just remember, and the right answer requires extensive knowledge of underlying data structures of the containers with some logic.
The insertion operation might invalidate the iterators on vector
, string
, deque
, and unordered_set/map
(in case of expanding/rehashing). For list
, set
, and map
, this is not the case thanks to their node-based data structures.
It’s standard in many developer interviews to provide a computational problem and expect the candidate to develop an algorithm to solve it. There are a bunch of great platforms for competitive C++ programming like LeetCode, HackerRank, etc. that are full of such problems.
These questions are valuable in that they check a lot of things at once. Most interviewers will start with an intentionally underspecified problem statement to test the candidate’s problem-tackling skills and ability to ask the right questions. Then, the candidate has to solve the problem, write the code, and, most importantly, do a complexity analysis.
Being good at the latter and having strong knowledge of data structures is quite enough to make wise decisions when using STL containers and algorithms, as well as writing new ones. Some might go even further and look at the actual implementation. Everyone ends up in STL code at some point, either by accident, while debugging, or willingly. If the candidate went there and managed to make sense of that gibberish, that says something about their experience, curiosity, and persistence. For example:
Q: Which sorting algorithm does std::sort
implement?
The ISO C++ standard doesn’t specify the algorithm; it only sets the requirement of asymptotic complexities. The choice then depends on the implementation (i.e., that of Microsoft vs. GNU.)
Most of them don’t just settle with a single algorithm, like mergesort or quicksort. A common approach is to have a hybrid of those or choose one depending on the size. GCC, for example, implements introsort, which is a hybrid of quicksort and heapsort. It starts with the first and switches to heapsort when the length is less than the implementation-defined threshold.
C++: Time-tested, But Development Teams Must Be Skilled at Avoiding Its Pitfalls
The questions outlined in this guide cover some basic and some tricky aspects of C++ programming. But just like its possibilities, the language’s hidden surprises are limitless, and that makes it difficult to cover everything about C++ in interviews. Therefore, it is essential to evaluate a candidate’s competency, skill set, and deep understanding of C++ through their ability to convey their ideas clearly and vividly.
We hope these questions will help you as a guide in your search for a true high-quality C++ expert in either a full-time or part-time role. These rare elites may be hard to come by, but they will clearly stand out from the rest of the pack for your C++ programming team.