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 is the C language most widely used for object-oriented programming, providing advanced features such as classes, inheritance, and polymorphism, all while retaining compatibility with C-style procedural programming. It plays a significant role in mobile app development for both Android and iOS and is a primary language for game development. One of the very few languages to have stood the test of time, it has earned its understandable popularity and respect. But despite the efforts of the ISO C++ Standards Committee and the community to boost usability and make it more programmer-friendly, it’s still arguably one of the hardest languages to master.
What’s Special About C++?
C++ has a number of key strengths. It offers low-level control through pointers, enabling optimized code and fine-grained resource management. As a compiled language, it ensures fast execution by converting code directly to machine code. Developers also have explicit control over memory management, improving efficiency but requiring careful handling to avoid errors like memory leaks.
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. The best C++ developers, like any good programmers, will also have the soft skills and project management chops to function as part of a high-performing development team, are comfortable with Agile methodologies, and are well-versed in best practices like version control with Git.
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 identifying skilled C++ developers.
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