Open In App

C++ Core Concepts & Memory Handling Interview Questions

Last Updated : 26 Aug, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

C++ is a mid-level language combining high-level abstractions (classes, inheritance, templates) with low-level memory control (pointers, dynamic allocation). Core concepts include variables, functions, references, operators, and object-oriented features like encapsulation and polymorphism. Memory handling covers stack vs heap allocation, pointers, dynamic memory (new/delete), deep vs shallow copies, and smart pointers.

1. How do you free memory allocated with new?

Use delete for single values, and delete[] for arrays:

C++
#include <iostream>
using namespace std;

int main() {

    int* p = new int;
    delete p;
    
    int* arr = new int[10];
    delete[] arr;

    return 0;
}

2. What is the difference between new and malloc()?

Following are the major difference between new and malloc()

new

malloc()

An operator which performs an operation   A function that returns and accepts values
Calls the constructorsCannot call a constructor
Returns the exact data typeReturns void*

3. What happens if we change pointer ?

If you pass a pointer by value but forget to use dereferencing, changes won’t reflect.

Code:

C++
#include <iostream>
using namespace std;

void wrongUpdate(int *p) {
    int x = 100;
    p = &x; // Changing the pointer locally, not the original address
}

int main() {
    int a = 10;
    int *ptr = &a;
    wrongUpdate(ptr);
    cout << "Value: " << *ptr << endl; // Will still print 10
    return 0;
}

4. How internally delete and delete[] works in cpp?

delete and delete[] deallocate memory obtained from new and new[].

  • For single objects, delete calls the destructor (if any) and then releases memory using operator delete.
  • For arrays, delete[] calls the destructor for each element and then uses operator delete[] to free the memory block.

The compiler typically keeps track of the number of elements internally (implementation-defined).

Note: The actual destructor calls and memory release are handled automatically by the compiler/runtime; programmers should never explicitly invoke ptr->~T() before delete.

Example of delete:

C++
ptr->~T();            // Call destructor
operator delete(ptr); // Free memory

Example of delete[]:

C++
for (int i = size - 1; i >= 0; --i)
    arr[i].~T();             // Call destructor on each
operator delete[](arr);     // Free memory

Note: If the object is a primitive type (int, char), destructors aren't needed.

5. How we can make custom delete?

You can overload operator delete or operator delete[] in your class:

C++
void operator delete(void* ptr) {
    std::cout << "Custom delete\n";
    ::operator delete(ptr);
}

6. What happens if you return a pointer to a local variable from a function?

The pointer becomes dangling because the variable’s lifetime ends when the function exits. Accessing it causes undefined behavior.

C++
int* foo() {
    int x = 10;
    return &x;   
}

7. Demonstrate shallow vs deep copy in C++ with memory implications.

C++
#include <iostream>
using namespace std;

int main() {
    
    // Shallow Copy
    int *a = new int(10);
    int *b = a;   // shallow copy: both point to same memory

    *b = 20;      // changing b also changes a
    cout << "Shallow Copy:" << endl;
    cout << "a = " << *a << ", b = " << *b << endl;  // both 20

    // Deep Copy 
    int *x = new int(10);
    int *y = new int(*x);  // deep copy: new memory with same value

    *y = 30;      // changing y does not affect x
    cout << "\nDeep Copy:" << endl;
    cout << "x = " << *x << ", y = " << *y << endl;  // x=10, y=30

    delete a; // free memory
    // delete b;  // dangerous in shallow copy (same memory as a)
    delete x;
    delete y;

    return 0;
}

Output
Shallow Copy:
a = 20, b = 20

Deep Copy:
x = 10, y = 30
  • Shallow Copy: Both pointers (a and b) refer to the same memory. Updating one updates the other.
  • Deep Copy: A new memory block is allocated (y), so changes don’t affect the original (x).

8. Explain why delete cannot be used for arrays with demonstration?

  • If memory is allocated using new[], it must be freed using delete[].
  • Using plain delete on an array leads to undefined behavior because the compiler will not correctly destroy all elements.
  • For simple data types (like int), it may look like it works, but for objects with destructors, only the first element may be destroyed, leaving the rest as a memory leak.
  • Rule of thumb: 1) new → delete, 2)new[] → delete[]

Example:

C++
#include <iostream>
using namespace std;

int main() {
    int* arr = new int[3]{1, 2, 3};

    delete arr;       // Wrong: only first element destructor called
    // delete[] arr;  // Correct: destructors for all elements

    return 0;
}

9. Show why base class destructors should be virtual.?

  • If the base class destructor is not virtual, deleting a derived object through a base pointer calls only the base destructor.
  • This skips cleanup of the derived class → can cause memory leaks or incomplete destruction.
  • Making the destructor virtual ensures the derived destructor is called first, then the base destructor → proper cleanup.

Example:

C++
#include <iostream>
using namespace std;

class Base {
public:
    Base() { cout << "Base constructor\n"; }
    ~Base() { cout << "Base destructor\n"; } // Non-virtual
};

class Derived : public Base {
public:
    Derived() { cout << "Derived constructor\n"; }
    ~Derived() { cout << "Derived destructor\n"; }
};

int main() {
    Base* obj = new Derived();
    delete obj;  // Derived destructor not called: memory leak
    return 0;
}

Output
Base constructor
Derived constructor
Base destructor

10. How do auto and decltype improve type safety? Show with example.

  • auto deduces the type from the initializer, ensuring the variable always matches the assigned expression and avoiding mismatched type declarations.
  • decltype deduces the type from an expression or variable, ensuring consistency in return types and variable declarations.
  • Together, they improve type safety by letting the compiler handle type correctness, especially in complex or generic code.

Example:

C++
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3};

    // auto deduces type
    auto it = v.begin();  // deduced as vector<int>::iterator
    cout << *it << endl;

    // decltype deduces type of expression
    decltype(v)::iterator it2 = v.end();
    cout << *(--it2) << endl;

    return 0;
}

Output
1
3

12. Show how mutable allows changing internal state even in a const object?

  • Normally, a const object in C++ cannot modify its data members.
  • If a data member is marked mutable, it can still be changed even inside a const object.
  • This is useful when some internal details (like caching, counters, or logs) need to change without affecting the logical state of the object.

Example:

C++
#include <iostream>
using namespace std;

class Example {
    mutable int accessCount; // can be modified even in const object
public:
    Example() : accessCount(0) {}
    void show() const {
        accessCount++;  // allowed because 'accessCount' is mutable
        cout << "Access count = " << accessCount << endl;
    }
};

int main() {
    const Example obj;  // const object
    obj.show();         // modifies mutable member
    obj.show();
}

Output
Access count = 1
Access count = 2

Even though obj is const, the mutable member (accessCount) can still be changed.

13. How does pointer arithmetic lead to undefined behavior if misused?

  • Pointer arithmetic allows direct memory access, but accessing out-of-bounds memory is undefined.
  • Compiler may not throw errors, but program may crash or show garbage.

Example cases:

  1. Incrementing past the end of an array.
  2. Decrementing before the start of an array.
  3. Adding large offsets that go far beyond allocated memory.

Code Example:

C++
#include <iostream>
using namespace std;

int main() {
    int arr[3] = {1, 2, 3};
    int* ptr = arr;

    cout << *(ptr + 2) << endl;   // valid, outputs 3
    cout << *(ptr + 5) << endl;   // Undefined behavior

    return 0;
}

Output
3
1587343946

14. Demonstrate difference in memory layout and access.

  • int* arrOfPtrs[3]: An array of 3 pointers to int. Each pointer can point to separate memory locations (may be on stack or heap).
  • int (*ptrToArr)[3]: A single pointer to an array of 3 ints. In the example, new int[1][3] allocates a block of 3 contiguous ints on the heap, and ptrToArr points to it.

So the difference is in layout:

  • Array of pointers: multiple independent ints, not guaranteed contiguous.
  • Pointer to array: one block of contiguous memory.

Example:

C++
#include <iostream>
using namespace std;

int main() {
    int a = 1, b = 2, c = 3;

    int* arrOfPtrs[3] = {&a, &b, &c};   // Array of pointers
    int (*ptrToArr)[3] = new int[1][3]; // Pointer to array
    (*ptrToArr)[0] = 10; (*ptrToArr)[1] = 20; (*ptrToArr)[2] = 30;

    cout << *arrOfPtrs[1] << endl;       // Output: 2
    cout << (*ptrToArr)[1] << endl;      // Output: 20

    delete[] ptrToArr;
}

Output
2
20

Article Tags :