Skip to content
Laviyx Learn

Try

C++ Interview Questions

Frequently asked C++ interview questions on memory, pointers, OOP, the STL and modern C++, with hints, full answers and the reasoning interviewers look for.

18 questions · Updated

  1. 1.What is the difference between a pointer and a reference?

    BeginnerConceptMemory
    Show hint

    Can either of them be null? Can either be changed to refer to something else?

    Show solution
    • A reference is another name for an existing object. It must be initialized, cannot be null, and cannot be re-seated.
    • A pointer stores an address. It can be nullptr, can be changed to point elsewhere, and needs * or -> to reach the object.
    C++
    int x = 10;
    int& r = x;    // r IS x
    int* p = &x;   // p holds x's address
    r += 1;        // x == 11
    *p += 1;       // x == 12

    Prefer references for parameters that must refer to a valid object, and pointers when "no object" (nullptr) is a valid value.

  2. 2.What is the difference between stack and heap memory?

    BeginnerConceptMemory
    Show hint

    Who frees each kind of memory, and when?

    Show solution
    • The stack holds local variables and function call frames. Allocation is extremely fast, memory is freed automatically when a scope ends, and its size is limited (often a few MB).
    • The heap (free store) holds memory requested at run time (new, or inside containers like std::vector). It is large, but allocation is slower, and the memory must be released, ideally automatically by an owner object (RAII, smart pointers).

    Deep recursion or huge local arrays can overflow the stack; large or long-lived data belongs on the heap, owned by a container or smart pointer.

  3. 3.What does a const member function mean?

    BeginnerConceptOOP
    Show hint

    What is the type of this inside such a function?

    Show solution

    A member function declared const (for example double area() const;) promises not to modify the object. Inside it, this is a pointer to const, so changing data members is a compile error. Only const member functions can be called on const objects or through const& parameters, which is why every read-only method should be marked const. (Members declared mutable, such as a cache, are the one exception.)

  4. 4.Why should a base class have a virtual destructor?

    IntermediateConceptOOP
    Show hint

    What happens when you delete a derived object through a base pointer?

    Show solution

    When a derived object is deleted through a pointer to its base class, the destructor call is resolved like any other: if the base destructor is not virtual, only the base destructor runs. That is undefined behaviour and typically leaks the derived part's resources.

    C++
    struct Base {
        virtual ~Base() = default;   // required for polymorphic deletion
    };
    struct Derived : Base {
        std::vector<int> data;
    };
    std::unique_ptr<Base> p = std::make_unique<Derived>();   // destroys Derived correctly

    Rule of thumb: any class with at least one virtual function should have a virtual destructor.

  5. 5.How are virtual functions implemented?

    IntermediateConceptOOP
    Show hint

    Think of a hidden table per class and a hidden pointer per object.

    Show solution

    Compilers typically give each polymorphic class a vtable, a table of pointers to its virtual function implementations, and each object a hidden vptr pointing to its class's vtable. A virtual call loads the vptr, looks up the function's slot and calls through it. The costs are one pointer per object, one indirection per call, and fewer inlining opportunities. Constructors set the vptr, which is why calling a virtual function inside a base constructor calls the base version.

  6. 6.Explain the Rule of Three, Five and Zero.

    IntermediateConceptObject lifetime
    Show hint

    What do the destructor, copy operations and move operations have in common?

    Show solution
    • Three: if you write a destructor, copy constructor or copy assignment operator, you almost certainly need all three, because the class manages a resource.
    • Five: add the move constructor and move assignment operator for efficient transfers.
    • Zero: best of all, let members that manage themselves (std::vector, std::string, std::unique_ptr) own every resource, so you write none of the five and the compiler-generated versions are correct.
  7. 7.When would you use unique_ptr, shared_ptr and weak_ptr?

    IntermediateConceptSmart pointers
    Show hint

    How many owners does the object have?

    Show solution
    • std::unique_ptr: exactly one owner. Zero overhead, movable, not copyable. The default choice for heap objects.
    • std::shared_ptr: several owners; a reference count destroys the object when the last owner goes away. It costs a control block and atomic count updates.
    • std::weak_ptr: observes a shared object without owning it; used to break reference cycles (for example a child pointing back to its parent) and for caches. Call lock() to get a temporary shared_ptr.

    Create them with std::make_unique and std::make_shared, never with a raw new.

  8. 8.What is the difference between std::map and std::unordered_map?

    IntermediateConceptSTL
    Show hint

    Compare the data structure, ordering, and lookup complexity.

    Show solution
    std::mapstd::unordered_map
    StructureBalanced binary search treeHash table
    Iteration orderSorted by keyUnspecified
    Lookup / insert / eraseO(log n)O(1) average, O(n) worst case
    Key requirementsoperator<std::hash and operator==
    Range querieslower_bound, upper_boundNot available

    Choose unordered_map for fast lookups when order does not matter, and map when you need sorted keys or range queries.

  9. 9.Why is std::vector::push_back amortized O(1)?

    IntermediateConceptSTL
    Show hint

    What happens when the vector runs out of capacity, and how often does that happen?

    Show solution

    A vector keeps spare capacity. Most push_back calls just construct the element in that space, O(1). When capacity runs out, the vector allocates a larger block (growing geometrically, typically 1.5× or 2×), moves the elements over and frees the old block, which is O(n). Because the capacity grows geometrically, these expensive steps become rarer as the vector grows, and the total cost of n pushes is O(n), so each push is O(1) amortized. Calling reserve(n) up front avoids reallocations entirely.

  10. 10.What does this program print?

    BeginnerOutput predictionFundamentals
    Show hint

    Look at the types of the operands in each division.

    Show solution
    C++
    #include <iostream>
    
    int main() {
        int a = 7;
        int b = 2;
        double c = a / b;
        double d = static_cast<double>(a) / b;
        std::cout << c << ' ' << d << ' ' << a % b << '\n';
    }

    It prints 3 3.5 1. a / b divides two ints, so the result is the integer 3 before it is stored in the double. Casting one operand first makes the division floating-point.

  11. 11.What does this program print, and why?

    IntermediateOutput predictionOOP
    Show hint

    Look at how each function receives its argument.

    Show solution
    C++
    #include <iostream>
    
    struct Animal {
        virtual const char* sound() const { return "..."; }
        virtual ~Animal() = default;
    };
    struct Dog : Animal {
        const char* sound() const override { return "Woof"; }
    };
    
    void by_value(Animal a) { std::cout << a.sound() << '\n'; }
    void by_ref(const Animal& a) { std::cout << a.sound() << '\n'; }
    
    int main() {
        Dog d;
        by_value(d);
        by_ref(d);
    }

    It prints ... and then Woof. Passing a Dog by value as an Animal copies only the Animal part (object slicing), so the copy is a plain Animal. Passing by reference keeps the real Dog, and the virtual call dispatches to Dog::sound.

  12. 12.What is wrong with this function?

    IntermediateDebuggingMemory
    Show hint

    How long does the variable named result live?

    Show solution
    C++
    const std::string& make_greeting(const std::string& name) {
        std::string result = "Hello, " + name;
        return result;   // returns a reference to a local variable
    }

    result is destroyed when the function returns, so the caller receives a dangling reference; using it is undefined behaviour (it may print garbage, crash, or appear to work). GCC and Clang warn about this. The fix is to return by value: std::string make_greeting(...). Returning by value is efficient because of copy elision and move semantics.

  13. 13.Find the bug in this loop that removes even numbers.

    IntermediateDebuggingSTL
    Show hint

    What happens to the iterator after erase?

    Show solution
    C++
    for (auto it = v.begin(); it != v.end(); ++it) {
        if (*it % 2 == 0) {
            v.erase(it);   // bug: 'it' is invalid after erase, then ++it uses it
        }
    }

    erase invalidates the erased iterator (and all after it, for a vector), so ++it is undefined behaviour; it can also skip elements. Use the iterator erase returns:

    C++
    for (auto it = v.begin(); it != v.end(); ) {
        if (*it % 2 == 0) it = v.erase(it);
        else ++it;
    }

    Or, in C++20, simply std::erase_if(v, [](int x) { return x % 2 == 0; });.

  14. 14.Reverse the order of words in a sentence.

    BeginnerCodingStrings
    Show hint

    Reading with >> from a std::istringstream splits on spaces and skips extra ones.

    Show solution
    C++
    #include <iostream>
    #include <sstream>
    #include <string>
    #include <vector>
    
    std::string reverse_words(const std::string& s) {
        std::istringstream in(s);
        std::vector<std::string> words;
        std::string w;
        while (in >> w) words.push_back(w);
    
        std::string out;
        for (auto it = words.rbegin(); it != words.rend(); ++it) {
            if (!out.empty()) out += ' ';
            out += *it;
        }
        return out;
    }
    
    int main() {
        std::cout << reverse_words("  practice   makes progress ") << '\n';   // progress makes practice
    }

    O(n) time and space. Mention that extra spaces are collapsed, and ask the interviewer whether that is acceptable.

  15. 15.Find the first non-repeating character in a string.

    IntermediateCodingHashing
    Show hint

    Count every character first, then scan the string again in its original order.

    Show solution
    C++
    #include <array>
    #include <iostream>
    #include <string>
    
    char first_unique(const std::string& s) {
        std::array<int, 256> count{};
        for (unsigned char c : s) ++count[c];
        for (unsigned char c : s) {
            if (count[c] == 1) return static_cast<char>(c);
        }
        return '\0';
    }
    
    int main() {
        std::cout << first_unique("swiss") << '\n';   // w
    }

    Two passes give O(n) time and O(1) extra space (a fixed table of 256 counters). Calling s.find or counting inside the loop would make it O(n²).

  16. 16.How would you implement an LRU cache with O(1) get and put?

    AdvancedDesignSTL
    Show hint

    Combine a structure that keeps usage order with one that finds keys instantly.

    Show solution

    Use a doubly linked list (std::list) holding (key, value) pairs in order of use, most recent at the front, plus an unordered_map from key to the list iterator.

    • get(key): look up the iterator in the map (O(1)); if found, move that node to the front with list.splice (O(1), no reallocation) and return the value.
    • put(key, value): if the key exists, update it and move it to the front; otherwise insert at the front and record the iterator. If the size exceeds capacity, remove the back node (least recently used) and erase its key from the map.
    C++
    #include <list>
    #include <unordered_map>
    #include <utility>
    
    class LRUCache {
    public:
        explicit LRUCache(std::size_t capacity) : capacity_(capacity) {}
    
        int get(int key) {
            auto it = index_.find(key);
            if (it == index_.end()) return -1;
            items_.splice(items_.begin(), items_, it->second);   // mark as most recent
            return it->second->second;
        }
    
        void put(int key, int value) {
            if (auto it = index_.find(key); it != index_.end()) {
                it->second->second = value;
                items_.splice(items_.begin(), items_, it->second);
                return;
            }
            items_.emplace_front(key, value);
            index_[key] = items_.begin();
            if (items_.size() > capacity_) {
                index_.erase(items_.back().first);
                items_.pop_back();
            }
        }
    
    private:
        std::size_t capacity_;
        std::list<std::pair<int, int>> items_;
        std::unordered_map<int, std::list<std::pair<int, int>>::iterator> index_;
    };

    std::list iterators stay valid when other nodes are inserted, removed or spliced, which is what makes storing them in the map safe.

  17. 17.What does std::move do?

    AdvancedConceptMove semantics
    Show hint

    Does it generate any machine code that moves data?

    Show solution

    std::move is only a cast to an rvalue reference. It moves nothing itself; it allows overload resolution to pick a move constructor or move assignment operator, which then transfers the resources. After a move, the source object is in a valid but unspecified state: you may assign to it or destroy it, but should not rely on its value. Two traps worth mentioning: std::move on a const object silently copies, and return std::move(local); can prevent copy elision.

  18. 18.What is a data race, and how do you fix one?

    AdvancedConceptConcurrency
    Show hint

    Two threads, the same memory, and at least one write.

    Show solution

    A data race occurs when two threads access the same memory concurrently, at least one access is a write, and nothing synchronizes them. It is undefined behaviour; a typical symptom is a shared counter that ends up lower than expected because increments interleave.

    Fixes:

    • Protect the shared data with a std::mutex and an RAII lock (std::lock_guard, std::scoped_lock).
    • Use std::atomic for single variables such as counters and flags.
    • Avoid sharing: give each thread its own data and combine the results after join() (or use std::async and futures).

    ThreadSanitizer (-fsanitize=thread) detects races during testing.

Review the lessons