C++ Interview Questions

Get ready for your C++ programming interview

C++ coding interviews can be tricky. Don't get caught off guard. These are some of the most common C++ interview questions. Learn these and you'll be more prepared for your C++ programming interview.

And once you're done with these, we have a bunch of general data structures and algorithms questions in C++.

Interview Cake is not just another question database—we walk you through the question step-by-step, giving hints and explanations as you need them, just like a real interviewer.

What is the difference between a shallow and a deep copy of a class?

Shallow copying is faster, but it's "lazy" in how it handles pointers and references. Instead of making a fresh copy of the actual data the pointer points to, it just copies over the pointer value. So, both the original and the copy will have pointers that reference the same underlying data. (Ditto for references.)

Deep copying actually clones the underlying data; it's not shared between the original and the copy.

Let's get specific. Here's a class representing a bus:

class Bus { private: int yearBuilt_; string name_; vector<string>* routeList_; public: Bus(int yearBuilt, const string& name, vector<string>& routeList) : yearBuilt_(yearBuilt), name_(name), routeList_(&inputRouteList) { } void setName(const string& name); void addStop(const string& stopName); void removeStop(const string& stopName); void busDetails() const; };

Pay close attention to our routeList_ variable. We've made it a pointer so that it can be updated remotely (in case there's a detour). We've also included methods to manipulate the list of stops and print out the bus information.

What do you think happens when we run this code?

void test() { vector<string>* route = new vector<string>(); // Add one bus to our fleet Bus local(2015, "LOCAL", *route); local.addStop("Main St."); local.addStop("First Ave."); local.addStop("Second Ave."); // Add another bus that skips First Ave. Bus express = local; express.setName("EXPRESS"); express.removeStop("First Ave."); // Let's see what we've got local.busDetails(); express.busDetails(); }

Okay, so what happens?

Is the express bus a shallow copy of the local bus? Or a deep copy?

Since we haven't specified otherwise, C++ will make a shallow copy of the local bus when creating the express bus. That means that both buses reference the same route list, like this:

A route list composed of three streets. Both the local bus and a shallow copy of the local bus point to this list.

Since they share the same route list, our route change for the express bus shows up in the local bus too. Oops.

The LOCAL bus was built in 2015 and its current route is: Main St., Second Ave. The EXPRESS bus was built in 2015 and its current route is: Main St., Second Ave.

We can avoid this if we use deep copying to make sure that the express and local buses each have their own copy of the route list.

Okay, so how can we get deep copies?

There are two ways an object can be copied: the assignment operator (Bus express = local) and the copy constructor (Bus express(local)). If a class doesn't implement methods specifying how these copy cases should be handled, then the C++ compiler fills them in with methods that make shallow copies.

Since we want deep copies, we need to implement a copy constructor and assignment operator for our Bus class. Here's what our copy constructor could look like:

Bus::Bus(const Bus& src) : yearBuilt_(src.yearBuilt_), name_(src.name_), routeList_(new vector<string&gt;(*src.routeList_)) { }

The line of code: routeList_(new vector<string>(*src.routeList_)) creates new space so that the new Bus object can have its own route list.

Now, if we do something like this:

// Create and initialize the local bus Bus local(2015, "LOCAL", route); // And, make a deep copy for the express bus Bus express(local); // Also invokes copy constructor Bus anotherExpress = local;

Then, our buses will look like this:

Two route lists composed of three streets each. The local bus points to one list and a deep copy of the local bus points to the other.

Notice how, in that code, we used the Bus constructor to make our copy—Bus express(local). But, in our original code, we used the assignment operator:

Bus express = local;

These are different! Even though we've created a copy constructor, it won't get called when we use the assignment operator like this. So, express is still a shallow copy local, since we haven't said otherwise.

To fix this, let's override the assignment operator by adding our own definition to the class. We'll just have our assignment operator call the copy constructor we've already made:

Bus& Bus::operator=(const Bus& src) { if (this != &amp;src) { yearBuilt_ = src.yearBuilt_; name_ = src.name_; delete routeList; routeList = new vector<string&gt;(*src.routeList_); } return *this; }

One last thing to note: copy constructors don't always create deep copies. For instance, if we add information about the bus supervisor, we'll probably want the same supervisor for all buses. So, we can let them share a reference to the supervisor name, while creating a separate copy of the route list:

Bus::Bus(int yearBuilt, const string& name, vector<string&gt;&amp; routeList, const string&amp; supervisor) : yearBuilt_(yearBuilt), name_(name), routeList_(&amp;routeList), supervisor_(&amp;suprvisor) { } Bus::Bus(const Bus&amp; src) : yearBuilt_(src.yearBuilt_), name_(src.name_), routeList_(new vector<string&gt;(*src.routeList_)), // New bus gets its own copy of the route list supervisor_(src.suprvisor_) // But shares the same supervisor { }

Technically, this isn't a deep copy since the two buses will still share a reference.

What is a template function?

C++ makes us specify the types for function arguments and return values. Templates let us write functions that take in any type. This helps us keep our code DRY.

For example, let’s look at this swap function. Sometimes, we need to swap two integers:

void mySwap(int& x, int& y) { int tmp = x; x = y; y = tmp; }

But what if we wanted to swap two chars?

void mySwap(char& x, char& y) { char tmp = x; x = y; y = tmp; }

Or two strings?

void mySwap(string& x, string& y) { string tmp = x; x = y; y = tmp; }

Aside from the types, these functions are exactly the same! That's annoying—it means that our code is harder to read and there are more places for us to introduce bugs. It would be nice if we could write one function to swap two things of the same type that we could use instead of these three versions.

Template functions to the rescue!

Using templates, we can combine all three swap functions above, like this:

template <typename T> void mySwap(T& x, T& y) { T tmp = x; x = y; y = tmp; };

So now we can write code like this:

int a = 1, b = 2; mySwap(a, b); cout << "a=" << a << ", b=" << b << endl; // "a=2, b=1" string c = "one", d = "two"; swap(c, d); cout << "c=" << c << ", d=" << d << endl; // "c=two, d=one"

Careful: C++ templates can make programs "bloated" by increasing the amount of code that needs to be compiled. Remember the three different swap functions we created earlier? Behind the scenes, the C++ compiler is generating all three of those functions: one for ints, one for strings, and one for characters. Using templates saves us time and makes our code shorter, but we're definitely not saving any space.

What is the Diamond problem? How can we get around it?

The diamond problem is a common question used to test your understanding of multiple inheritance.

The diamond problem refers to an issue when a base class is inherited by two subclasses, and both subclasses are inherited by a fourth class. When this happens, we need to give the compiler a bit of guidance about the exact structure of inheritance we want.

Let’s use the example of lions, tigers, and ... ligers:

We'll start with a base class, Mammal, with the method eat:

class Mammal { protected: const string name_; public: Mammal(const string& name); void eat() { cout << "This Mammal " << name_ << " is eating" << endl; } };

Lion and Tiger both inherit from Mammal:

class Tiger : public Mammal { public: void groom(); }; class Lion : public Mammal { public: void groom(); };

Finally, Liger inherits from both Lion and Tiger:

class Liger : public Tiger, public Lion { public: void nap(); };

This is the inheritance structure we're going for. (See how it looks like a diamond!)

A liger class pointing up to a Tiger class on the left and a Lion class on the right. The Tiger and Lion classes both point up to a Mammal class. These connections form the shape of a diamond.

Satisfied with our structure so far, we decide to write some code using the Liger class:

Liger Lyle("Lyle"); Lyle.groom(); Lyle.eat();

But, when we compile it, we get this error message:

$ g++ ligers.cpp error: non-static member 'eat' found in multiple base-class sub-objects of type 'Mammal': class Liger -> class Tiger -> class Mammal class Liger -> class Lion -> class Mammal Lyle.eat();

What's happening here?

The compiler is complaining that the Liger class has two versions of the eat method—one from the Tiger class (which inherited it from the Mammal class) and one from the Lion class (which also inherited it from the Mammal class). So, the compiler sees two different eat methods, and it doesn't know which version it should use for Ligers.

This happens because C++ doesn't recognize that the Lion and Tiger classes are inheriting from the same Mammal class. What it sees instead is something like this:

A liger class pointing up to a Tiger class on the left and a Lion class on the right. The Tiger and Lion classes both point up to separate copies of the Mammal class, breaking the diamond shape.

We can ensure that the compiler inherits the same Mammal class into the Lion and Tiger classes with the virtual keyword, like this:

class Tiger : virtual public Mammal { public: void groom(); }; class Lion : virtual public Mammal { public: void groom(); };

When a base class is virtually inherited, the C++ compiler makes sure that this class is created only once. That means that all subclasses, like Lion and Tiger, will lead back to the same Mammal base class, giving us the diamond structure we've wanted all along.

Note that we have to put the virtual keyword in the definition of both Tiger and Lion. If we don't then we'll get the incorrect structure we had before.

The virtual keyword can also be used with class methods. When a class method is declared virtual, it can be overridden by inheriting classes.

Going back to our example with animals, we might want to let different mammals define their own eat method. Since we'll be overriding the eat method in inheriting classes, we need to declare it as virtual, like this:

class Mammal { protected: const string name_; public: Mammal(const string& name); virtual void eat() { cout << "This Mammal " << name_ << " is eating" << endl; } };

When developing classes, most public or protected methods will be declared virtual so that inheriting classes can override them as needed. In fact, the only method that can't be declared virtual is the class constructor. (Clearly, we don't want others to override that!)

Why are destructors important?

Destructors are important because they give us a chance to free up an object's allocated resources before the object goes out of scope. Since C++ doesn't have a garbage collector, resources that we don't free ourselves will never be released back to the system, eventually making things grind to a halt.

A resource leak is when a program finishes using some resource (allocated memory, open files, network sockets) but doesn’t properly release it, hoarding resources it's not using any more. Mozilla was famous for having memory leaks with Firefox: the browser would cache recently visited sites so that users could quickly go back to them, but it wouldn't delete saved sites. Over time, the amount of data saved would get pretty large, slowing down the browser.

We're focusing on C++98. We should note, though, that newer versions of C++ have introduced "smart pointers," which are automatically freed when they go out of scope. If you can use smart pointers, they're generally preferable since you can avoid some of the headaches of manual memory management. That said, many projects still use manual memory management, so it's important to understand destructors.

To make this concrete, let's look at an example: shopping carts. Suppose our shopping cart class keeps track of its items in a dynamically allocated vector, like this:

class ShoppingCart { private: vector<string>* items_; public: ShoppingCart() : items_(new vector<string>()) { } ~ShoppingCart() { // Need to free up items here } void addItem(const string& item) { items_->push_back(item); } };

A destructor method is declared with the same name as the constructor, with a ~ in front. For our ShoppingCart class, that's ~ShoppingCart. The C++ compiler exclusively reserves this syntax for destructors, so it's important to not use this name for anything else.

The items vector is dynamically allocated and could take up a lot of space, so we need to free it when we're done. How do we do that?

We can just call the vector's destructor!

Careful, though! We don't call destructors directly.

  • If we allocated an object dynamically (using new), then we call the object's destructor using the delete keyword.
  • For objects on the stack (not allocated using new), the destructor will be called automatically once it is out of scope.

Let's see how this works if we have a few shopping carts.

{ ShoppingCart cart; cart.addItem("eggs"); cart.addItem("cheese"); } // When cart goes out of scope here, // destructor ShoppingCart::~ShoppingCart() is called automatically

This shopping cart is allocated on the stack, so its destructor gets called automatically once it goes out of scope.

ShoppingCart* cart = new ShoppingCart(); cart->addItem("milk"); cart->addItem("bread"); delete cart; // calls destructor ShoppingCart::~ShoppingCart() for cart

This shopping cart is dynamically allocated, so we have to manually call its destructor when we're done with it.

Okay, but what should actually go in the destructor for ShoppingCart? Since we dynamically allocated the items vector, we need to use the delete keyword to call its destructor. We'd do that in ~ShoppingCart, like this:

ShoppingCart::~ShoppingCart() { delete items_; }

The nice thing about destructors is they centralize object cleanup in one place. As we make the ShoppingCart class more complex, we can always make sure we're not leaking resources by updating our destructor to do any necessary cleanup. That's much easier to maintain than having cleanup code scattered around our code base.

Reverse String in Place »

Write a function to reverse a string in-place. keep reading »

Reverse Words »

Write a function to reverse the word order of a string, in-place. It's to decipher a supersecret message and win the war. keep reading »

Inflight Entertainment »

Writing a simple recommendation algorithm that helps people choose which movies to watch during flights keep reading »

Permutation Palindrome »

Check if any permutation of an input string is a palindrome. keep reading »

Reverse A Linked List »

Write a function to reverse a linked list in-place. keep reading »

Kth to Last Node in a Singly-Linked List »

Find the kth to last node in a singly-linked list. We'll start with a simple solution and move on to some clever tricks. keep reading »

Recursive String Permutations »

Write a recursive function of generating all permutations of an input string. keep reading »

In-Place Shuffle »

Do an in-place shuffle on an array of numbers. It's trickier than you might think! keep reading »

Single Riffle Shuffle »

Write a function to tell us if a deck of cards is a single riffle of two other halves. keep reading »

Simulate 5-sided die »

Given a 7-sided die, make a 5-sided die. keep reading »

Simulate 7-sided die »

Given a 5-sided die, make a 7-sided die. keep reading »

Word Cloud Data »

You're building a word cloud. Write a function to figure out how many times each word appears so we know how big to make each word in the cloud. keep reading »

Two Egg Problem »

A building has 100 floors. Figure out the highest floor an egg can be dropped from without breaking. keep reading »

Find Repeat, Space Edition »

Figure out which number is repeated. But here's the catch: optimize for space. keep reading »

Find Repeat, Space Edition BEAST MODE »

Figure out which number is repeated. But here's the catch: do it in linear time and constant space! keep reading »

Find Duplicate Files »

Your friend copied a bunch of your files and put them in random places around your hard drive. Write a function to undo the damage. keep reading »

Apple Stocks »

Figure out the optimal buy and sell time for a given stock, given its prices yesterday. keep reading »

Binary Search Tree Checker »

Write a function to check that a binary tree is a valid binary search tree. keep reading »

Parenthesis Matching »

Write a function that finds the corresponding closing parenthesis given the position of an opening parenthesis in a string. keep reading »

MillionGazillion »

I'm making a new search engine called MillionGazillion(tm), and I need help figuring out what data structures to use. keep reading »

The Cake Thief »

You've hit the motherload: the cake vault of the Queen of England. Figure out how much of each cake to carry out to maximize profit. keep reading »

Making Change »

Write a function that will replace your role as a cashier and make everyone rich or something. keep reading »

Implement A Queue With Two Stacks »

Implement a queue with two stacks. Assume you already have a stack implementation. keep reading »

Compute nth Fibonacci Number »

Computer the nth Fibonacci number. Careful--the recursion can quickly spin out of control! keep reading »

Rectangular Love »

Find the area of overlap between two rectangles. In the name of love. keep reading »

Balanced Binary Tree »

Write a function to see if a binary tree is 'superbalanced'--a new tree property we just made up. keep reading »

Bracket Validator »

Write a super-simple JavaScript parser that can find bugs in your intern's code. keep reading »

2nd Largest Item in a Binary Search Tree »

Find the second largest element in a binary search tree. keep reading »

Largest Stack »

You've implemented a Stack class, but you want to access the largest element in your stack from time to time. Write an augmented LargestStack class. keep reading »

The Stolen Breakfast Drone »

In a beautiful Amazon utopia where breakfast is delivered by drones, one drone has gone missing. Write a function to figure out which one is missing. keep reading »

Delete Node »

Write a function to delete a node from a linked list. Turns out you can do it in constant time! keep reading »

Top Scores »

Efficiently sort numbers in an array, where each number is below a certain maximum. keep reading »

Which Appears Twice »

Find the repeat number in an array of numbers. Optimize for runtime. keep reading »

Find in Ordered Set »

Given an array of numbers in sorted order, how quickly could we check if a given number is present in the array? keep reading »

Find Rotation Point »

I wanted to learn some big words to make people think I'm smart, but I messed up. Write a function to help untangle the mess I made. keep reading »

Product of All Other Numbers »

For each number in an array, find the product of all the other numbers. You can do it faster than you'd think! keep reading »

Highest Product of 3 »

Find the highest possible product that you can get by multiplying any 3 numbers from an input array. keep reading »

Merging Meeting Times »

Write a function for merging meeting times given everyone's schedules. It's an enterprise end-to-end scheduling solution, dog. keep reading »

Temperature Tracker »

Write code to continually track the max, min, mean, and mode as new numbers are inserted into a tracker class. keep reading »

Ticket Sales Site »

Design a ticket sales site, like Ticketmaster keep reading »

Sign up for updates. Free practice problems every week!

Psst. Pass it on.

. . .