0

Key C++ practice problems (and solutions) from beginner to senior level

Share

#Key #practice #problems #solutions #beginner #senior #level

Not sure what to practice?

In this guide, we delve into a wide array of C++ problems, ranging from fundamental syntax and basic data structures for beginners, to advanced concepts like object-oriented programming (OOP) and efficient memory management for experienced developers. Each problem is accompanied by a detailed solution and an explanation to aid in understanding the solution and to deepen your grasp of the underlying concepts. Whether you’re starting your journey in C++ or looking to refine your expertise, this guide serves as a valuable resource to navigate through the intricacies of one of the most powerful programming languages.

Evaluating your current skill level in C++ is crucial for effective learning. We recommend starting with basic problems and gradually progressing to more complex ones, allowing you to accurately gauge your understanding and identify areas needing improvement. Setting clear goals is essential, whether it’s mastering specific features of C++ or preparing for a technical interview. Your goals should align with your professional development objectives and the role you’re interviewing for—whether that means becoming proficient in writing efficient code for large-scale applications, or gaining a deep understanding of lower-level C++ for system-level programming. 

To create a structured practice plan, start by allocating regular, focused practice sessions, prioritize topics based on your goals, and use a variety of resources like coding challenges, open-source projects, and community forums to enhance your learning experience. The key to mastering C++ is consistent practice and a willingness to continually challenge yourself.

Jump to a section

What you will need to get started

Before diving into practice problems, you’ll need to configure your coding environment if you haven’t already. You have various options, each with its own merits and drawbacks. Trying out multiple setups can improve your adaptability, so take the time to discover which one suits your preferences and workflow best. We’ll discuss some environment options below.

IDEs

Using fully-featured IDEs for C++ interview preparation provides a comprehensive environment, integrating editing, building, and debugging seamlessly. The GUI-based project configuration simplifies the setup, making it easier to manage larger codebases efficiently. IDEs come with advanced features like code refactoring, enhancing productivity during interview simulations. However, be mindful of the potentially steep learning curve associated with complex UIs, and the resource-intensive nature of IDEs if you haven’t used one before. 

Conversely, for more senior engineers and those very comfortable with their IDE, pay attention to your debugging methods. Many interview platforms don’t have rich environments for watching variables, compilation warnings, and code completion, so consider practicing interview questions in a different environment than what you’re used to so that you’re able to adapt to a few methods of debugging.

Code editor and command line

For interview practice in C++, opting for a code editor like Sublime Text or VS Code with command-line tools provides flexibility and control. This setup allows developers to delve into the intricacies of makefiles and the build process, which can be helpful for beginners learning how compilation works in C++ and how files interact. While this setup is lightweight and fast, offering a consistent experience across platforms, be aware that configuring the toolchain and compiler may require extra effort. The lack of built-in debugging may make it difficult for beginners that are still learning C++ debugging. Nevertheless, this approach can be beneficial for honing manual coding and build skills, which are often tested in interviews.

Online IDEs

For quick, instant coding practice, online IDEs offer immediate accessibility without the need for setup. They are suitable for short, focused tests and demonstrations, making them convenient for interview preparation. This helps emulate many interview situations where you’re coding online with a less-customized environment. However, be aware of the limitations, such as restricted features compared to desktop IDEs. The inability to save work locally might be a drawback if you want to review your code later in another IDE for practice, and requiring internet access might limit when you can practice. 

Libraries and dependencies

When preparing for C++ interviews, having the right libraries accessible simplifies implementation and allows you to concentrate on core programming concepts.

Fortunately, most standard facilities needed for interview practice come built-in:

  • Headers like <algorithm>, <vector>, <string> provide collections, utilities
  • I/O streams using <iostream> and <fstream>
  • Containers/iterators in <array>, <list>, <map>
  • Multithreading primitives via <thread>, <mutex>
  • Built-in smart pointers like std::unique_ptr
  • These and more ship standard with any C++ compiler so are always available.

While the STL suffices for most fundamental coding challenges, some additional libraries useful to have handy include:

  • Boost: For added containers, algorithms, strings, testing
  • Qt: GUI development and platform abstractions
  • OpenSSL” Cryptography and secure communication
  • Libcurl: HTTP and network transfers

Ideally, have these pre-installed and integrated into the dev environment instead of figuring out during interviews. This removes distractions to stay focused. Most build systems or Linux package managers make acquiring these straightforward.

How to solve our C++ practice problems for maximum benefit

Beginning C++ Interview Techniques

In preparation for C++ interviews, some principles apply regardless of your seniority.  Study essentials like OOP concepts of inheritance and polymorphism, handling exceptions properly with try/catch blocks, leveraging C++11 smart pointers for automatic memory management, and applying move semantics for performance. Additionally, become fluent with the Standard Template Library (STL), including the central vectors, maps, sets, strings, iterators, algorithms for sorting/searching, and templates for generic programming. Knowing these building blocks allows for efficiently solving most problems without reinventing built-in capabilities. Additionally, practice analyzing algorithmic complexity using big-O notation, as interviewers will often ask for the theoretical efficiency of code implementations.  A solid grasp of big-O allows you to compare the scalability of different solutions critically to select the most optimal approach.

Beyond honing your proficiency in the language’s fundamentals, familiarize yourself with the compiler and build process, incorporating tools like Makefiles, as this knowledge proves invaluable for troubleshooting and optimizing code. The extent to which you’ll need to master these areas will depend on your experience and goals.

Advanced C++ Interviewing Techniques

In addition to the general advice outlined above, more senior developers will have to prepare to effectively use advanced C++ features in their interviews. When solving these more difficult problems, prioritizing optimizing speed and memory usage becomes more important as solutions can quickly become complex. As with more beginner questions, leverage existing STL containers and algorithms before opting for custom implementations, always considering space-time tradeoffs. Effective memory management is crucial—manually handle cleanup and freeing resources when working with custom allocators, and use smart pointers for automatic management. Ensure constructors and destructors are carefully managed, emphasizing clear organization for memory lifetimes and ownership intent.

When encountering questions regarding concurrency, minimize shared mutable state between threads, use mutex locks judiciously, and prefer condition variables over busy wait loops for efficiency. Make concurrent code exception-safe when needed, and stress-test to identify and address race conditions. Additionally, explore template metaprogramming (TMP) techniques, such as SFINAE (Substitution Failure Is Not An Error) and type traits, as they can empower you with tools to create flexible and efficient code. These practices contribute to a comprehensive understanding of advanced C++ concepts, enhancing your problem-solving skills and proficiency in the language.

Essential C++ practice problems (and solutions) for beginners

For introductory C++ interview questions, interview questions often prioritize evaluating problem decomposition skills, data structure comprehension, and general coding aptitude over specialized library or language syntax familiarity. That said, C++ is a difficult language, so mastering the essential language fundamentals and syntax is the initial key to success. Familiarize yourself with prevalent libraries and patterns, allowing you to concentrate on solving the interview problem without struggling with C++ details. 

With any language, you’ll need to build a solid foundation in implementing basic data structures, such as dynamic arrays and stacks, as well as common algorithm patterns and object-oriented programming principles.  This set of beginner-level and essential questions aims to evaluate your grasp on foundational concepts, setting the stage for more advanced assessments. 

Language fundamentals and syntax

Question 1: Print a custom message

Prompt: Write a C++ program that prompts the user to enter their name and then prints a greeting message, “Hello, [name]!”.

What skills this question evaluates: This question tests basic input/output operations and string manipulation in C++. It evaluates the ability to use cin for input and cout for output.

Solution:

cpp
Copy code
#include <iostream>
#include <string>
using namespace std;
int main() {
    string name;
    cout << "Enter your name: ";
    cin >> name;
    cout << "Hello, " << name << "!" << endl;
    return 0;
}

Explanation of the solution: This solution involves using the iostream library for input and output operations. The program first declares a string variable name, then uses cin to read the user’s name and cout to print the greeting. The usage of << operator for output and >> for input is fundamental in C++.

Question 2: Sum of two numbers

Prompt: Enhance your program to prompt the user to enter two integers. The program should then print the sum of these two integers.

What skills this question evaluates:  This question builds on the first by adding arithmetic operations and basic data type handling. It assesses the candidate’s ability to perform calculations and handle user input.

Solution:

cpp
Copy code
#include <iostream>
using namespace std;
int main() {
    int num1, num2;
    cout << "Enter two numbers: ";
    cin >> num1 >> num2;
    cout << "The sum is: " << num1 + num2 << endl;
    return 0;
}

Explanation of the solution: This solution introduces integer variables num1 and num2. The program uses cin to read two integers inputted by the user and calculates their sum using the + operator. This introduces the concept of arithmetic operations and multiple data input in C++. The use of cin >> num1 >> num2 demonstrates how to read multiple inputs in a single line.

C++ control structures

Question 1: Check for an even or odd number

Prompt: Write a C++ program that asks the user to input an integer and then prints whether the number is even or odd.

What skills this question evaluates: This question tests the understanding of conditional statements (if-else). It evaluates the ability to use logical expressions to make decisions in C++.

Solution:

cpp
Copy code
#include <iostream>
using namespace std;
int main() {
    int number;
    cout << "Enter an integer: ";
    cin >> number;
    if (number % 2 == 0) {
        cout << number << " is even." << endl;
    } else {
        cout << number << " is odd." << endl;
    }
    return 0;
}

Explanation of the solution: This solution uses an if-else statement to determine if a number is even or odd. The % operator is used to find the remainder when the number is divided by 2. If the remainder is 0, the number is even; otherwise, it’s odd. This demonstrates basic conditional logic in C++.

Question 2: Counting positive and negative numbers

Prompt: Modify your program to allow the user to enter multiple numbers until a zero is entered. After zero is entered, the program should print the count of positive and negative numbers entered.

What skills this question evaluates: This question builds on the previous one by adding a while loop for repeated input. It assesses the understanding of loops and conditional statements combined.

Solution:

cpp
Copy code
#include <iostream>
using namespace std;
int main() {
    int number, positiveCount = 0, negativeCount = 0;
    cout << "Enter numbers (0 to stop): ";
    while (cin >> number && number != 0) {
        if (number > 0) {
            positiveCount++;
        } else {
            negativeCount++;
        }
    }
    cout << "Positive numbers: " << positiveCount << endl;
    cout << "Negative numbers: " << negativeCount << endl;
    return 0;
}

Explanation of the solution: In this solution, a while loop is used to continuously read numbers from the user until zero is entered. Within the loop, an if-else statement increments either positiveCount or negativeCount based on whether the number is positive or negative. This question demonstrates the use of loops for repetitive tasks and conditional statements for decision-making in C++.

Basic I/O operations with C++

Question 1: User-inputted string reversal

Prompt: Write a C++ program that prompts the user to enter a string, and then prints the reverse of the string.

What skills this question evaluates: This question tests the candidate’s ability to handle input/output operations and basic string manipulation. It assesses how to use standard input to receive a string and then process it to produce the reversed version.

Solution:

cpp
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
    string input;
    cout << "Enter a string: ";
    getline(cin, input);
    reverse(input.begin(), input.end());
    cout << "Reversed string: " << input << endl;
    return 0;
}

Explanation of the solution: The program reads a string from the user using `getline` to handle potentially spaced input. It then utilizes the `reverse` function from the standard `<algorithm>` library to reverse the string in-place. This solution demonstrates basic string handling and the use of the standard library for string manipulation.

Question 2: Writing and reading a string to/from a file

Prompt: Modify your program to write the reversed string to a file named “output.txt”. Then, read the string back from the file and print it to the console.

What skills this question evaluates: This question adds basic file handling to the previous skills of string manipulation and I/O operations. It evaluates the ability to write to and read from a file in C++.

Solution:

cpp
#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
using namespace std;
int main() {
    string input;
    cout << "Enter a string: ";
    getline(cin, input);
    reverse(input.begin(), input.end());
    // Write to file
    ofstream outFile("output.txt");
    outFile << input;
    outFile.close();
    // Read from file and print
    string fileContent;
    ifstream inFile("output.txt");
    getline(inFile, fileContent);
    cout << "String from file: " << fileContent << endl;
    inFile.close();
    return 0;
}

Explanation of the solution: This solution extends the previous string reversal program to include file I/O operations. The reversed string is written to “output.txt” using an `ofstream` object. The program then creates an `ifstream` object to read the content back from the file and prints it to the console. This demonstrates how to handle basic file operations in C++, including writing to and reading from files.

Object-oriented programming

Question 1: Creating a basic class

Prompt: Define a C++ class named `Car` with private member variables for `brand` (string), `model` (string), and `year` (integer). Include public member functions to set and get the values of these variables.

What skills this question evaluates: This question assesses understanding of basic class creation, encapsulation, and the use of public member functions for accessing private data. It focuses on the fundamental principles of object-oriented programming in C++.

Solution:

cpp
#include <iostream>
#include <string>
using namespace std;
class Car {
private:
    string brand;
    string model;
    int year;
public:
    void setBrand(const string &b) {
        brand = b;
    }
    void setModel(const string &m) {
        model = m;
    }
    void setYear(int y) {
        year = y;
    }
    string getBrand() const {
        return brand;
    }
    string getModel() const {
        return model;
    }
    int getYear() const {
        return year;
    }
};
int main() {
    Car myCar;
    myCar.setBrand("Toyota");
    myCar.setModel("Corolla");
    myCar.setYear(2020);
    cout << "Car Details: " << myCar.getBrand() << " " << myCar.getModel() << " " << myCar.getYear() << endl;
    return 0;
}

Explanation of the solution: The `Car` class is defined with private member variables (`brand`, `model`, `year`) and public methods (`setBrand`, `setModel`, `setYear`, `getBrand`, `getModel`, `getYear`) for setting and getting these variables. This encapsulates the data and provides controlled access to it. The `main` function demonstrates creating an instance of `Car` and using its methods.

Question 2: Inheritance and polymorphism

Prompt: Extend the `Car` class by creating a subclass named `ElectricCar` that includes an additional private member variable for `batteryRange` (integer) and corresponding public functions. Demonstrate polymorphism by creating a function that takes a `Car` object and prints its details.

This question tests understanding of inheritance and polymorphism. It evaluates the ability to extend a base class and use polymorphic behavior to work with objects of both the base and derived classes.

Solution:

cpp
#include <iostream>
#include <string>
using namespace std;
class Car {
    // ... (same as above)
};
class ElectricCar : public Car {
private:
    int batteryRange;
public:
    void setBatteryRange(int range) {
        batteryRange = range;
    }
    int getBatteryRange() const {
        return batteryRange;
    }
};
void printCarDetails(const Car &car) {
    cout << "Car Details: " << car.getBrand() << " " << car.getModel() << " " << car.getYear() << endl;
}
int main() {
    ElectricCar myElectricCar;
    myElectricCar.setBrand("Tesla");
    myElectricCar.setModel("Model S");
    myElectricCar.setYear(2022);
    myElectricCar.setBatteryRange(400);
    printCarDetails(myElectricCar);
    return 0;
}

Explanation of the solution: The `ElectricCar` class is a subclass of `Car` and includes an additional property `batteryRange`. The `printCarDetails` function takes a `Car` object as a parameter, demonstrating polymorphism by also accepting objects of the `ElectricCar` subclass. In `main`, an `ElectricCar` object is created, its properties are set, and it is passed to `printCarDetails`. This showcases inheritance by extending the `Car` class and polymorphism through the use of a function accepting objects of both the base and derived class.

Basic data structures

Question 1: Implementing a dynamic array

Prompt: Write a C++ class `DynamicArray` that mimics the behavior of a dynamically resizable array. It should start with a capacity of 1 and double in size when needed. Implement methods `add(int item)` to add an item to the array and `get(int index)` to retrieve an item by index.

What skills this question evaluates: This question assesses understanding of dynamic memory allocation and array resizing, fundamental concepts in managing data structures. It tests the candidate’s ability to implement basic operations like adding items and accessing them.

Solution:

cpp
#include <iostream>
#include <cassert>
using namespace std;
class DynamicArray {
private:
    int* data;
    int size;
    int capacity;
    void resize() {
        capacity *= 2;
        int* newData = new int[capacity];
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        delete[] data;
        data = newData;
    }
public:
    DynamicArray() : size(0), capacity(1), data(new int[capacity]) {}
    ~DynamicArray() {
        delete[] data;
    }
    void add(int item) {
        if (size == capacity) {
            resize();
        }
        data[size++] = item;
    }
    int get(int index) {
        assert(index >= 0 && index < size);
        return data[index];
    }
};
int main() {
    DynamicArray arr;
    arr.add(5);
    arr.add(10);
    cout << "Element at index 0: " << arr.get(0) << endl;
    cout << "Element at index 1: " << arr.get(1) << endl;
    return 0;
}

Explanation of the solution: The `DynamicArray` class uses a dynamic array `data` to store integers. It automatically resizes (doubles its capacity) when needed. The `add` method inserts an item, and `get` retrieves an item at a given index, with an `assert` statement ensuring the index is valid. The constructor initializes the array, and the destructor releases the allocated memory.

Question 2: Implementing a stack

Prompt: Based on your `DynamicArray` class, implement a class `Stack` that supports push, pop, and top operations. Ensure that `pop` and `top` handle the case when the stack is empty.

What skills this question evaluates: This question tests the ability to use existing data structures (like the dynamic array) to implement another data structure (stack). It evaluates understanding of stack operations and error handling in C++.

Solution:

cpp
#include <iostream>
#include <cassert>
using namespace std;
class Stack {
private:
    DynamicArray arr;
public:
    void push(int item) {
        arr.add(item);
    }
    int pop() {
        assert(!isEmpty());
        return arr.get(--arr.size);
    }
    int top() {
        assert(!isEmpty());
        return arr.get(arr.size - 1);
    }
    bool isEmpty() {
        return arr.size == 0;
    }
};
int main() {
    Stack stack;
    stack.push(10);
    stack.push(20);
    cout << "Top element: " << stack.top() << endl;
    cout << "Popped element: " << stack.pop() << endl;
    cout << "New top element: " << stack.top() << endl;
    return 0;
}

Explanation of the solution: The `Stack` class uses an instance of `DynamicArray` to manage its elements. The `push` operation adds an element to the stack, while `pop` removes and returns the top element. The `top` operation returns the top element without removing it. Both `pop` and `top` include assertions to check that the stack is not empty before attempting to access elements. This solution demonstrates how to build a stack using an underlying dynamic array structure and includes basic error handling.

Advanced C++ practice problems (and solutions)

C++ is a complex language, and it requires time and effort to master. While you may be well-versed in some advanced concepts as a senior programmer, others may be less familiar to you. Strengthen your expertise in the areas you know thoroughly to demonstrate your skills and proficiency. Additionally, build a foundation in other concepts to highlight your versatility and adaptability. 

As a senior C++ developer, the expectations go beyond writing functional code; comprehensive knowledge of software design principles becomes crucial. Senior-level interviews delve into more complex paradigms like memory management, multithreading, and optimization strategies, emphasizing scalable and maintainable solutions. Use the following practice problems to recognize your areas of strength. After completing them, work on improving your code and exploring alternate solutions to problems so that you are prepared for a variety of challenges.

Advanced data structures and algorithms

Question 1: Implementing a Binary Search Tree (BST)

Prompt: Write a C++ class `BinarySearchTree` that implements a basic binary search tree. The class should have methods to insert a new key, `insert(int key)`, and to check if a key exists in the tree, `search(int key)`.

What skills this question evaluates: This question assesses understanding of binary search trees, a fundamental data structure in computer science. It evaluates the ability to implement key operations like insertion and search, which require recursive thinking and an understanding of tree traversal.

Solution:

cpp
#include <iostream>
using namespace std;
class Node {
public:
    int key;
    Node *left, *right;
    Node(int item) : key(item), left(nullptr), right(nullptr) {}
};
class BinarySearchTree {
private:
    Node* root;
    Node* insertRec(Node* root, int key) {
        if (root == nullptr) {
            return new Node(key);
        }
        if (key < root->key) {
            root->left = insertRec(root->left, key);
        } else {
            root->right = insertRec(root->right, key);
        }
        return root;
    }
    bool searchRec(Node* root, int key) {
        if (root == nullptr) {
            return false;
        }
        if (root->key == key) {
            return true;
        }
        return key < root->key ? searchRec(root->left, key) : searchRec(root->right, key);
    }
public:
    BinarySearchTree() : root(nullptr) {}
    void insert(int key) {
        root = insertRec(root, key);
    }
    bool search(int key) {
        return searchRec(root, key);
    }
};
int main() {
    BinarySearchTree bst;
    bst.insert(10);
    bst.insert(5);
    bst.insert(15);
    cout << "Is 10 in BST? " << (bst.search(10) ? "Yes" : "No") << endl;
    cout << "Is 20 in BST? " << (bst.search(20) ? "Yes" : "No") << endl;
    return 0;
}

Explanation of the solution: The `BinarySearchTree` class uses a nested `Node` class to represent each node in the tree. The `insert` method adds a new key to the tree, while `search` checks for the existence of a key. Both methods use private recursive helper functions (`insertRec`, `searchRec`) to traverse the tree.

Question 2: Depth-First Search (DFS) in a graph

Prompt: Implement a graph represented as an adjacency list and perform a depth-first search (DFS) from a given starting node. Write a function `DFS(int startNode)` that prints the nodes visited during the search.

What skills this question evaluates: This question tests understanding of graph data structures and depth-first search, an important algorithm in graph theory. It assesses the ability to implement graphs and traverse them using recursive or stack-based approaches.

Solution:

cpp
#include <iostream>
#include <list>
#include <vector>
using namespace std;
class Graph {
private:
    int numVertices;
    list<int> *adjLists;
    vector<bool> visited;
    void DFSUtil(int vertex) {
        visited[vertex] = true;
        cout << vertex << " ";
        for (int adj : adjLists[vertex]) {
            if (!visited[adj]) {
                DFSUtil(adj);
            }
        }
    }
public:
    Graph(int vertices) : numVertices(vertices), adjLists(new list<int>[vertices]), visited(vertices, false) {}
    void addEdge(int src, int dest) {
        adjLists[src].push_back(dest);
    }
    void DFS(int startNode) {
        fill(visited.begin(), visited.end(), false);
        DFSUtil(startNode);
    }
};
int main() {
    Graph g(4);
    g.addEdge(0, 1);
    g.addEdge(0, 2);
    g.addEdge(1, 2);
    g.addEdge(2, 0);
    g.addEdge(2, 3);
    g.addEdge(3, 3);
    cout << "Depth First Traversal starting from vertex 2:" << endl;
    g.DFS(2);
    return 0;
}

Explanation of the solution: The `Graph` class uses an adjacency list for representing the graph and a `visited` vector to track visited nodes. The `addEdge` method adds edges to the graph. The `DFS` method initializes the `visited` vector and calls `DFSUtil`, a private method that performs recursive depth-first traversal from the specified node, printing each visited node. This solution demonstrates the implementation of a graph and a fundamental graph traversal algorithm.

Memory management

Question 1: Implementing a smart pointer

Prompt: Write a template class `SmartPointer` that mimics the behavior of a smart pointer. The class should be able to hold a pointer of any type and should release the memory when it is no longer in use (i.e., implement basic reference counting).

What skills this question evaluates: This question assesses the understanding of dynamic memory management and the concept of smart pointers in C++, particularly the implementation of reference counting to manage memory automatically.

Solution:

cpp
#include <iostream>
using namespace std;
template <typename T>
class SmartPointer {
private:
    T* ptr;
    unsigned* count;
public:
    SmartPointer(T* p = nullptr) : ptr(p), count(new unsigned(1)) {}
    SmartPointer(const SmartPointer<T>& sp) : ptr(sp.ptr), count(sp.count) {
        (*count)++;
    }
    SmartPointer<T>& operator=(const SmartPointer<T>& sp) {
        if (this != &sp) {
            if (--(*count) == 0) {
                delete ptr;
                delete count;
            }
            ptr = sp.ptr;
            count = sp.count;
            (*count)++;
        }
        return *this;
    }
    ~SmartPointer() {
        if (--(*count) == 0) {
            delete ptr;
            delete count;
        }
    }
    T& operator*() {
        return *ptr;
    }
};
int main() {
    SmartPointer<int> sp1(new int(10));
    SmartPointer<int> sp2 = sp1;
    cout << "Value: " << *sp2 << endl;
    return 0;
}

Explanation of the solution: The `SmartPointer` template class handles a pointer and a reference count. The constructor initializes the pointer and the reference count. The copy constructor and copy assignment operator manage the reference count, increasing it when a new reference is created and decreasing it when a reference is destroyed or changed. If the count reaches zero, the memory is released. This ensures that memory is automatically managed and freed when no longer needed, demonstrating basic principles of smart pointers.

Question 2: Custom memory allocator

Prompt: Create a custom memory allocator class `CustomAllocator` that allocates memory for an array of a specified type but does not construct the objects. Implement methods `allocate(size_t n)` for allocating memory and `deallocate()` for deallocating memory.

What skills this question evaluates: This question tests the candidate’s understanding of lower-level memory management in C++, particularly the concepts of memory allocation and deallocation without object construction and destruction.

Solution:

cpp
#include <iostream>
using namespace std;
template <typename T>
class CustomAllocator {
private:
    T* array;
public:
    CustomAllocator() : array(nullptr) {}
    T* allocate(size_t n) {
        array = static_cast<T*>(operator new[](n * sizeof(T)));
        return array;
    }
    void deallocate() {
        operator delete[](array);
    }
};
int main() {
    CustomAllocator<int> allocator;
    int* arr = allocator.allocate(5);
    // Use the allocated array (construct objects if necessary)
    for (int i = 0; i < 5; ++i) {
        new (&arr[i]) int(i);  // Placement new
    }
    // Manually call destructor for constructed objects
    for (int i = 0; i < 5; ++i) {
        arr[i].~int();
    }
    allocator.deallocate();
    return 0;
}

Explanation of the solution: The `CustomAllocator` template class handles raw memory allocation and deallocation for an array of type `T`. The `allocate` method uses `operator new[]` to allocate unconstructed memory and returns a pointer to this memory. The `deallocate` method frees the memory using `operator delete[]`. In `main`, the allocated memory is used to manually construct and destruct objects using placement new and explicit destructor calls, demonstrating an understanding of memory allocation without automatic construction and destruction. This example showcases a more advanced and controlled approach to memory management in C++.

Concurrency and multithreading

Question 1: Implementing a thread-safe queue

Prompt: Create a C++ class `ThreadSafeQueue` that implements a thread-safe queue using mutexes. The class should provide `enqueue` and `dequeue` methods to add and remove items, ensuring thread safety.

What skills this question evaluates: This question tests the candidate’s understanding of thread synchronization in C++ using mutexes. It evaluates the ability to implement basic thread-safe data structures, crucial in multithreaded applications.

Solution:

cpp
#include <iostream>
#include <queue>
#include <mutex>
#include <condition_variable>
using namespace std;
template <typename T>
class ThreadSafeQueue {
private:
    queue<T> queue;
    mutex mtx;
    condition_variable cv;
public:
    void enqueue(T item) {
        lock_guard<mutex> lock(mtx);
        queue.push(item);
        cv.notify_one();
    }
    T dequeue() {
        unique_lock<mutex> lock(mtx);
        cv.wait(lock, [this] { return !queue.empty(); });
        T item = queue.front();
        queue.pop();
        return item;
    }
};
int main() {
    ThreadSafeQueue<int> tsQueue;
    // Example usage: tsQueue.enqueue(10);
    // Example usage: int item = tsQueue.dequeue();
    return 0;
}

Explanation of the solution: The `ThreadSafeQueue` class uses a standard queue and a mutex for synchronization. The `enqueue` method locks the mutex, adds an item to the queue, and then notifies one waiting thread. The `dequeue` method waits (without busy-waiting) until the queue is not empty and then removes an item from the queue. This implementation ensures thread safety for enqueueing and dequeueing operations, demonstrating key concepts in concurrent C++ programming.

Question 2: Implementing a simple thread pool

Prompt: Create a C++ class `ThreadPool` that manages a fixed number of threads. The class should be able to execute tasks (function objects) added to a queue. Implement methods to add tasks and to shut down the thread pool gracefully.

What skills this question evaluates: This question assesses advanced understanding of multithreading, specifically the management of multiple threads and task execution. It evaluates the ability to implement a thread pool and manage its lifecycle.

Solution:

cpp
#include <iostream>
#include <vector>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>
#include <queue>
using namespace std;
class ThreadPool {
private:
    vector<thread> workers;
    queue<function<void()>> tasks;
    mutex mtx;
    condition_variable cv;
    bool stop;
public:
    ThreadPool(size_t threads) : stop(false) {
        for (size_t i = 0; i < threads; ++i) {
            workers.emplace_back([this] {
                while (true) {
                    function<void()> task;
                    {
                        unique_lock<mutex> lock(this->mtx);
                        this->cv.wait(lock, [this] { return this->stop || !this->tasks.empty(); });
                        if (this->stop && this->tasks.empty())
                            return;
                        task = this->tasks.front();
                        this->tasks.pop();
                    }
                    task();
                }
            });
        }
    }
    void enqueue(function<void()> task) {
        {
            lock_guard<mutex> lock(mtx);
            tasks.push(task);
        }
        cv.notify_one();
    }
    void shutdown() {
        {
            lock_guard<mutex> lock(mtx);
            stop = true;
        }
        cv.notify_all();
        for (thread &worker : workers) {
            worker.join();
        }
    }
    ~ThreadPool() {
        if (!stop) {
            shutdown();
        }
    }
};
int main() {
    ThreadPool pool(4);
    // Example usage: pool.enqueue([]{ cout << "Task executed." << endl; });
    return 0;
}

Explanation of the solution: The `ThreadPool` class manages a fixed number of worker threads and a task queue. Worker threads execute tasks from the queue. Tasks are function objects enqueued using the `enqueue` method. The `shutdown` method stops all threads after completing any remaining tasks. The use of mutexes and condition variables ensures thread-safe access to the task queue and coordinated execution. This solution demonstrates a fundamental pattern in concurrent programming: managing a pool of threads to efficiently execute tasks.

Advanced features of C++

Question 1: Implementing a custom template metaprogram

Prompt: Write a C++ template metaprogram `Factorial` that computes the factorial of a compile-time constant integer. Use template specialization to achieve this.

What skills this question evaluates: : This question assesses advanced knowledge of C++ template metaprogramming, an area that involves using templates to perform computations at compile time. It evaluates the ability to use recursive template instantiations and template specialization.

Solution:

cpp
#include <iostream>
template <unsigned int N>
struct Factorial {
    static const unsigned int value = N * Factorial<N - 1>::value;
};
template <>
struct Factorial<0> {
    static const unsigned int value = 1;
};
int main() {
    std::cout << "Factorial of 5: " << Factorial<5>::value << std::endl;
    return 0;
}

Explanation of the solution: The `Factorial` template struct calculates the factorial of a number at compile time. It uses recursive template instantiation, with each instance calculating part of the factorial. The specialized version of the template for the base case `Factorial<0>` provides the stopping condition for the recursion. This is a classic example of template metaprogramming, leveraging C++ templates’ powerful ability for compile-time computation.

Question 2: Utilizing advanced C++17 features

Prompt: Using C++17 features, write a function `processVariants` that takes a `std::variant` of different types (e.g., `int`, `double`, `std::string`) and uses a `std::visit` to apply a lambda function that prints the value regardless of its type.

What skills this question evaluates:  This question tests understanding of newer C++17 features, particularly `std::variant` and `std::visit`. It assesses the ability to work with type-safe unions and visitation patterns, showcasing proficiency in modern C++ idioms.

Solution:

cpp
#include <iostream>
#include <variant>
#include <string>
using VariantType = std::variant<int, double, std::string>;
void processVariants(const VariantType& var) {
    std::visit([](const auto& value) {
        std::cout << value << std::endl;
    }, var);
}
int main() {
    VariantType var1 = 10;
    VariantType var2 = 3.14;
    VariantType var3 = "Hello C++17";
    processVariants(var1);
    processVariants(var2);
    processVariants(var3);
    return 0;
}

Explanation of the solution: The function `processVariants` takes a `std::variant` and uses `std::visit` along with a generic lambda to process and print the contained value. The lambda uses auto type deduction to work with any type contained in the variant. This example demonstrates how C++17 introduces more flexible and type-safe ways to handle a set of types, streamlining what would otherwise require more complex and less safe approaches.

Next steps & resources

Getting better at C++ interviewing requires patience and analytical thinking more than just coding prowess. Understand each question deeply before starting to code, and think through use cases thoroughly. Consider working out the logic on paper before writing so you have a clear idea of what you’re trying to accomplish with the code. Once the solution is complete, refine it to produce clean, well-commented code, to facilitate ease in future writing. Thoroughly test the output, dedicating time to explore potential edge cases to guarantee the completeness of your solution.

By taking these steps with the practice questions above (and with more specialized applications of C++ for game development, machine learning, and others) you’ll set yourself up to cultivate a robust understanding of the fundamentals through consistent practice, rather than trying to “cram” concepts shortly before an interview. Practicing interview scenarios, either solo or with a peer, can also help you build confidence and improve ability to problem-solve on the spot.

Platforms like CodeSignal allow you to practice coding interview challenges in a realistic IDE built for skills development. Sign up for free to get started.