C++ Technical Interview | 30 Questions — 'Pointer vs Reference?' and Model Answers
이 글의 핵심
C++ technical interviews expect you to explain pointers, RAII, virtual functions, STL, and concurrency verbally. This article groups 30 common questions with model answer flows by topic.
Introduction: Common Questions in C++ Technical Interviews
Unlike coding tests, C++ technical interviews assess conceptual understanding and practical experience. You hear questions like “What is the difference between a pointer and a reference?”, “How do virtual functions work?”, and “How do you prevent race conditions (when multiple threads access the same resource and the outcome depends on scheduling)?” This article summarizes 30 questions that often appear and model answer patterns.
What this article covers:
- Pointers and memory management (10 questions)
- OOP and virtual functions (7 questions)
- STL and templates (6 questions)
- Multithreading and concurrency (5 questions)
- Modern C++ (after C++11) (2 questions)
Table of Contents
- Pointers and memory management (10)
- OOP and virtual functions (7)
- STL and templates (6)
- Multithreading and concurrency (5)
- Modern C++ (after C++11) (2)
1. Pointers and memory management (10 questions)
Q1. What is the difference between a pointer and a reference?
Answer:
| Aspect | Pointer | Reference |
|---|---|---|
| Rebinding | Allowed (ptr = &other;) | Not allowed (fixed after initialization) |
| nullptr | Allowed | Not allowed (must refer to a valid object) |
| Syntax | *ptr, ptr-> | Used like a normal variable name |
| Size | Size of the pointer (8 bytes on 64-bit) | Alias; no extra storage for the reference itself |
Example:
// Copy-paste then: g++ -std=c++17 -o ptr_ref ptr_ref.cpp && ./ptr_ref
#include <iostream>
int main() {
int x = 10;
int* ptr = &x;
ptr = nullptr; // OK
int& ref = x;
std::cout << ref << "\n"; // 10
return 0;
}
Output: A single line printing 10.
When to use which?
- Pointer: dynamic allocation, optional values (nullptr), arrays
- Reference: function parameters (avoid copies), return values (lvalue returns)
Practical guidelines:
- When passing an object into a function: prefer const reference (save copy cost)
- When the function must modify the object: non-const reference
- Optional value (may be absent): pointer or
std::optional - Arrays and low-level structures: pointers
Example:
// Good patterns
void print(const std::string& str) { // no copy, no modification
std::cout << str << '\n';
}
void modify(std::string& str) { // no copy, may modify
str += " modified";
}
std::string* findUser(int id) { // may not exist
if (id == 0) return nullptr;
return new std::string("User");
}
Q2. What is the difference between the stack and the heap?
Answer:
| Aspect | Stack | Heap |
|---|---|---|
| Allocation | Automatic (local variables) | Manual (new, malloc) |
| Deallocation | Automatic (end of scope) | Manual (delete, free) |
| Speed | Fast (mostly pointer bump) | Slower (allocator overhead) |
| Size | Limited (often ~8 MB default) | Large (up to system RAM) |
| Lifetime | Until function returns | Until explicitly freed |
Example:
void func() {
int a = 10; // stack
int* p = new int(20); // heap
delete p; // must free explicitly
} // a destroyed automatically
Why the stack is fast:
Stack allocation is essentially moving the stack pointer (SP)—often one or two CPU instructions. Heap allocation requires the allocator to find free space, which can cost tens or hundreds of instructions.
When to use the heap:
- Large data (stack size limits)
- Lifetime beyond a single function
- Size known only at runtime (e.g., array size from user input)
Q3. What is a memory leak? How do you prevent it?
Answer:
A memory leak is when memory allocated with new is never deleted, so it stays allocated until process exit.
Example:
void leak() {
int* p = new int(42);
// missing delete p; ❌
} // p goes away but heap block remains
Prevention:
1. Smart pointers (recommended):
// Runnable example
void noLeak() {
std::unique_ptr<int> p = std::make_unique<int>(42);
// automatic cleanup
}
2. RAII (Resource Acquisition Is Initialization):
class FileHandle {
FILE* file;
public:
FileHandle(const char* path) : file(fopen(path, "r")) {}
~FileHandle() { if (file) fclose(file); } // release in destructor
};
3. Detect with Valgrind (Linux):
valgrind --leak-check=full ./myapp
Q4. Shallow copy vs deep copy?
Answer:
Shallow copy: copies the pointer address only → two objects share the same memory.
Deep copy: copies the pointed-to data → independent memory.
Example:
class MyClass {
int* data;
public:
MyClass(int val) : data(new int(val)) {}
// default copy ctor would be shallow — bad
// MyClass(const MyClass& other) : data(other.data) {}
// deep copy
MyClass(const MyClass& other) : data(new int(*other.data)) {}
~MyClass() { delete data; }
};
Problem (shallow copy):
MyClass a(10);
MyClass b = a; // shallow → a.data and b.data same address
// double free on destruction
Fix: define copy constructor and copy assignment explicitly, or use std::unique_ptr (non-copyable, move-only).
Q5. Smart pointer types and differences?
Answer:
| Kind | Ownership | Copy | Typical use |
|---|---|---|---|
| unique_ptr | exclusive | no (move only) | single owner, RAII |
| shared_ptr | shared (refcount) | yes | shared ownership |
| weak_ptr | non-owning observe | — | break cycles in shared_ptr graphs |
Example:
std::unique_ptr<int> p1 = std::make_unique<int>(42);
// std::unique_ptr<int> p2 = p1; // error: not copyable
std::unique_ptr<int> p2 = std::move(p1); // move (p1 becomes nullptr)
std::shared_ptr<int> s1 = std::make_shared<int>(42);
std::shared_ptr<int> s2 = s1; // copy (refcount 2)
Q6. What is a dangling pointer?
Answer:
A pointer that still refers to memory that has already been freed.
Example:
int* ptr = new int(42);
delete ptr;
*ptr = 10; // dangling use — Undefined Behavior
Prevention:
- After
delete, setptr = nullptr; - Prefer smart pointers
Q7. new vs malloc?
Answer:
| Aspect | new | malloc |
|---|---|---|
| Language | C++ | C |
| Type safety | yes | no (void*) |
| Constructors | called | not called |
| On failure | throws std::bad_alloc (unless nothrow) | returns nullptr |
| Release | delete | free |
Example:
// new
MyClass* obj = new MyClass(10); // ctor runs
delete obj;
// malloc
MyClass* obj2 = (MyClass*)malloc(sizeof(MyClass)); // no ctor
free(obj2);
Recommendation: In C++, use new/delete or smart pointers.
Q8. What is RAII?
Answer:
RAII: bind resources (memory, files, locks) to object lifetime. Acquire in constructor, release in destructor—so cleanup runs even when exceptions propagate.
Example:
class FileLock {
std::mutex& mtx;
public:
FileLock(std::mutex& m) : mtx(m) { mtx.lock(); }
~FileLock() { mtx.unlock(); }
};
void process() {
std::mutex mtx;
FileLock lock(mtx); // lock on construction
// unlock in destructor even if an exception is thrown
}
STL examples: std::unique_ptr, std::lock_guard, std::ifstream (closes file)
Q9. Why do we need a virtual destructor?
Answer:
With polymorphism, deleting through a base class pointer without a virtual destructor calls only the base destructor. Derived resources may leak.
Problematic code:
class Base {
public:
~Base() { std::cout << "~Base\n"; } // not virtual
};
class Derived : public Base {
int* data;
public:
Derived() : data(new int[100]) {}
~Derived() { delete[] data; std::cout << "~Derived\n"; }
};
int main() {
Base* ptr = new Derived();
delete ptr; // only ~Base — ~Derived not run → leak
return 0;
}
Fix:
class Base {
public:
virtual ~Base() { std::cout << "~Base\n"; }
};
Now delete ptr; runs ~Derived then ~Base.
Q10. When does stack overflow happen?
Answer:
When stack space is exhausted. Common causes:
1. Infinite recursion:
void recursive() {
recursive(); // no base case
}
2. Huge arrays on the stack:
// Variable declaration and initialization
int main() {
int arr[10000000]; // ~40 MB — may exceed default stack (~8 MB)
return 0;
}
Fix:
- Add a termination condition to recursion
- Put large buffers on the heap (
std::vectorornew)
Everyday analogy: Think of memory as a building. The stack is like an elevator—fast but limited space. The heap is like a warehouse—large but lookup takes longer. A pointer is a note with an address such as “3rd floor, room 302.”
2. OOP and virtual functions (7 questions)
Q11. How do virtual functions work?
Answer:
Through a virtual function table (vtable) the actual function is chosen at runtime.
Mechanism:
- Classes with virtual functions have a vtable (array of function pointers).
- Each object has a vptr pointing at its vtable.
- A virtual call goes vptr → vtable → final function (indirect call).
Concrete layout sketch:
// Runnable example mental model
Base object:
[vptr: 8 bytes][member data...]
Base vtable:
[0] → address of Base::show
[1] → address of Base::other
Derived vtable:
[0] → address of Derived::show (overridden)
[1] → address of Base::other (not overridden)
Call sequence:
Base* ptr = new Derived();
ptr->show();
- Call
ptr->show() - Read vptr from
*ptr→ Derived’s vtable - Read slot 0 → address of
Derived::show - Jump to that address
Example:
class Base {
public:
virtual void show() { std::cout << "Base\n"; }
};
class Derived : public Base {
public:
void show() override { std::cout << "Derived\n"; }
};
int main() {
Base* ptr = new Derived();
ptr->show(); // prints "Derived" — resolved at runtime
delete ptr;
return 0;
}
Overhead: one indirect call—usually negligible.
Q12. What is a pure virtual function?
Answer:
A virtual function without an implementation, marked = 0. A class containing one becomes an abstract class and cannot be instantiated.
Example:
class Shape {
public:
virtual double area() const = 0; // pure virtual
virtual ~Shape() = default;
};
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() const override { return 3.14 * radius * radius; }
};
int main() {
// Shape s; // error: abstract
Circle c(5.0);
std::cout << c.area() << '\n';
return 0;
}
Use: define interfaces (similar to Java interface).
Q13. Why use override?
Answer:
override (C++11) catches cases where you thought you overrode a virtual function but signature mismatch means you defined a new function instead.
Bug pattern:
class Base {
public:
virtual void show() const {}
};
class Derived : public Base {
public:
void show() {} // not override — missing const → new overload
};
With override:
class Derived : public Base {
public:
void show() override {} // compile error: does not override
};
The compiler errors if it is not a true override.
Q14. What is wrong with multiple inheritance?
Answer:
Diamond problem: if two bases share a grandparent, the grandparent’s members appear twice unless you use virtual inheritance.
Example:
class A { public: int value; };
class B : public A {};
class C : public A {};
class D : public B, public C {}; // A appears twice
int main() {
D d;
// d.value = 10; // ambiguous
d.B::value = 10; // qualify
return 0;
}
Fix: virtual inheritance:
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {}; // one A subobject
int main() {
D d;
d.value = 10;
return 0;
}
Practice: multiple inheritance is rare; interface-like bases (pure virtual only) are more common.
Q15. When do you use static member variables?
Answer:
Shared across all instances of the class—one storage per class.
Example:
class Counter {
static int count; // declaration
public:
Counter() { count++; }
static int getCount() { return count; }
};
int Counter::count = 0; // definition
int main() {
Counter c1, c2, c3;
std::cout << Counter::getCount() << '\n'; // 3
return 0;
}
Uses: singletons, instance counts, shared configuration.
Q16. What are const member functions?
Answer:
Functions that do not modify the object’s observable state and can be called on const objects.
Example:
class Point {
int x, y;
public:
Point(int x, int y) : x(x), y(y) {}
int getX() const { return x; }
void setX(int val) { x = val; }
};
int main() {
const Point p(10, 20);
std::cout << p.getX() << '\n';
// p.setX(30); // error on const object
return 0;
}
Q17. When do you use friend?
Answer:
Lets non-members or other classes access private members.
Example:
class Box {
int width;
friend void printWidth(const Box& b);
public:
Box(int w) : width(w) {}
};
void printWidth(const Box& b) {
std::cout << b.width << '\n';
}
Uses: operator overloads like operator<<, test helpers.
Caution: breaks encapsulation—use sparingly.
3. STL and templates (6 questions)
Q18. vector vs list?
Answer:
| Aspect | vector | list |
|---|---|---|
| Structure | dynamic array | doubly linked list |
| Random access | O(1) | O(N) |
| Insert/erase (middle) | O(N) | O(1) with iterator |
| Memory | contiguous | node per element |
| Cache locality | good | poor |
When:
- vector: default choice (fast iteration, indexing)
- list: frequent inserts/erases in the middle
Q19. map vs unordered_map?
Answer:
| Aspect | map | unordered_map |
|---|---|---|
| Structure | red-black tree | hash table |
| Sorted keys | yes | no |
| Access | O(log N) | average O(1), worst O(N) |
| Memory | often lower | higher (buckets) |
When:
- map: need ordered iteration by key
- unordered_map: fast lookup (common in contests)
Q20. What is template specialization?
Answer:
Providing a different implementation for a specific type.
Example:
template <typename T>
T add(T a, T b) {
return a + b;
}
// full specialization for std::string
template <>
std::string add<std::string>(std::string a, std::string b) {
return a + " " + b;
}
int main() {
std::cout << add(3, 5) << '\n';
std::cout << add<std::string>("Hello", "World") << '\n';
return 0;
}
Q21. When are iterators invalidated?
Answer:
vector:
push_back,insert,erasemay reallocate → all iterators invalid.eraseinvalidates iterators at and after the erased position.
Example:
std::vector<int> v = {1, 2, 3, 4, 5};
for (auto it = v.begin(); it != v.end(); ++it) {
if (*it == 3) {
v.erase(it); // it is invalid — ++it is UB
}
}
Fix:
for (auto it = v.begin(); it != v.end(); ) {
if (*it == 3) {
it = v.erase(it); // erase returns next valid iterator
} else {
++it;
}
}
Q22. emplace_back vs push_back?
Answer:
push_back: construct a temporary, then copy/move into the container.
emplace_back: construct in place inside the container.
Example:
std::vector<std::pair<int, std::string>> v;
v.push_back({1, "apple"}); // temporary then move
v.emplace_back(2, "banana"); // construct directly — often fewer temps
Effect: fewer copy/move operations for heavy types.
Q23. What is template metaprogramming?
Answer:
Doing computation at compile time with templates—no runtime cost for that computation.
Example (compile-time factorial):
template <int N>
struct Factorial {
static const int value = N * Factorial<N-1>::value;
};
template <>
struct Factorial<0> {
static const int value = 1;
};
int main() {
std::cout << Factorial<5>::value << '\n'; // 120 at compile time
return 0;
}
Since C++11: often simpler with constexpr:
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int result = factorial(5);
return 0;
}
4. Multithreading and concurrency (5 questions)
Everyday analogy: Concurrency is like one cook switching between tasks (stir soup, chop vegetables). Parallelism is multiple cooks working on different dishes at the same time.
Q24. What is a race condition? How to prevent it?
Answer:
When multiple threads read/write the same memory without synchronization, results depend on scheduling.
Why counter++ is unsafe: it is not atomic—typically load, increment, store.
Buggy code:
int counter = 0;
void increment() {
for (int i = 0; i < 100000; ++i) {
counter++; // data race
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join(); t2.join();
std::cout << counter << '\n'; // may not be 200000
return 0;
}
Fix: protect with a mutex:
std::mutex mtx;
int counter = 0;
void increment() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(mtx);
counter++;
}
}
Q25. When does deadlock happen?
Answer:
Two or more threads each hold a lock the other needs—circular wait.
Example:
std::mutex mtx1, mtx2;
void thread1() {
std::lock_guard<std::mutex> lock1(mtx1);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::lock_guard<std::mutex> lock2(mtx2); // may block forever
}
void thread2() {
std::lock_guard<std::mutex> lock2(mtx2);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
std::lock_guard<std::mutex> lock1(mtx1);
}
Fix: consistent lock ordering or std::lock:
void thread1() {
std::lock(mtx1, mtx2);
std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
}
Q26. What is std::atomic?
Answer:
Types that support lock-free atomic read-modify-write for simple operations.
Example:
std::atomic<int> counter(0);
void increment() {
for (int i = 0; i < 100000; ++i) {
counter++; // atomic increment
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join(); t2.join();
std::cout << counter << '\n'; // 200000
return 0;
}
Note: complex invariants across multiple variables still need mutexes.
Q27. When do you use condition_variable?
Answer:
To block a thread until a condition is true—common in producer–consumer queues.
Example:
std::mutex mtx;
std::condition_variable cv;
std::queue<int> q;
void producer() {
for (int i = 0; i < 10; ++i) {
{
std::lock_guard<std::mutex> lock(mtx);
q.push(i);
}
cv.notify_one();
}
}
void consumer() {
for (int i = 0; i < 10; ++i) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return !q.empty(); });
int val = q.front();
q.pop();
std::cout << val << '\n';
}
}
Q28. What is thread_local?
Answer:
Each thread gets its own instance of the variable.
Example:
thread_local int counter = 0;
void increment() {
for (int i = 0; i < 100; ++i) {
counter++;
}
std::cout << std::this_thread::get_id() << ": " << counter << '\n';
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join(); t2.join();
return 0;
}
5. Modern C++ (after C++11) (2 questions)
Q29. What is move semantics?
Answer:
Transferring resources instead of copying to improve performance, using rvalue references (&&).
Why: a large temporary (e.g. std::vector with millions of elements) does not need a deep copy—you can steal its internal pointer.
Analogy:
- Copy: photocopy a heavy book (slow)
- Move: hand over the book itself (fast)
lvalue vs rvalue (simplified):
- lvalue: has a name (e.g.
x,arr[0]) - rvalue: temporary (e.g.
42,x + y, result ofstd::move(x))
Example:
class MyString {
char* data;
public:
MyString(const char* str) {
data = new char[strlen(str) + 1];
strcpy(data, str);
}
MyString(const MyString& other) {
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
}
MyString(MyString&& other) noexcept : data(other.data) {
other.data = nullptr;
}
~MyString() { delete[] data; }
};
Effect: std::vector<MyString> can move on growth/reallocation instead of copying everything.
Q30. Lambda capture modes?
Answer:
Captures bring outer variables into the lambda body.
| Capture | Meaning |
|---|---|
[] | capture nothing |
[x] | x by value (copy) |
[&x] | x by reference |
[=] | all by value |
[&] | all by reference |
[this] | capture this for member access |
Example:
int x = 10, y = 20;
auto lambda1 = [x]() { return x + 1; };
auto lambda2 = [&x]() { x++; };
auto lambda3 = [=]() { return x + y; };
auto lambda4 = [&]() { x++; y++; };
Interview answer tips
1. Structured answers
Weak:
“Pointers… point to addresses… references are aliases…”
Strong:
“There are three main differences. First, pointers can be rebound; references cannot after initialization. Second, pointers may be null; references must bind to an object. Third, syntax: pointers use
*and->, references look like ordinary names.”
2. Short code after concepts
A tiny example after the explanation signals depth.
3. Tie to experience
“In production we default to
unique_ptrand useshared_ptronly when sharing is required.”
4. If unsure, say so
“I’m not certain, but my reasoning is…—does that sound right?”
Showing reasoning matters.
One-line summary: thirty questions covering pointers, memory, virtual functions, STL, and multithreading—the core of many C++ technical interviews. Next, consider Junior developer interview prep or Coding test prep.
Frequently asked questions (FAQ)
Q. Do I need to memorize code perfectly?
A: No. Understand concepts and be able to describe structure. Interviewers may invite pseudocode.
Q. What if I am a junior and get threading questions?
A: Basics (race, mutex) suffice. Be honest about limited production experience. Seniors rarely expect advanced patterns from juniors.
Q. Is modern C++ mandatory?
A: Almost. Know auto, lambdas, smart pointers, and move semantics at minimum.
Q. “What is wrong with this code?”
A:
- Leaks (
newwithoutdelete) - Exception safety
- Efficiency (copies, bad complexity)
- Edge cases (empty, null, overflow)
Related posts
- C++ memory leaks: memory management basics
- C++ smart pointers:
unique_ptr,shared_ptr - C++ virtual functions and polymorphism: vtable,
override - C++ multithreading: thread, mutex, condition_variable
- C++ move semantics: rvalue references,
std::move
C++ technical interviews reward understanding and application over rote memorization. These thirty topics cover most of what you will see. Prepare short code sketches and one sentence of real-world usage per topic. If you get an unfamiliar question, stay calm and reason aloud from what you know.
Search keywords: C++ technical interview, pointer vs reference, virtual function vtable, smart pointers, race condition, move semantics
Architecture diagram
graph TD
A[Start] --> B{Check condition}
B -->|Yes| C[Process 1]
B -->|No| D[Process 2]
C --> E[Done]
D --> E
The diagram illustrates a simple control-flow pattern.
More to read (internal links)
- C++ memory leaks | real outages and five Valgrind patterns
- C++ junior developer interview | portfolio and answers without project experience
- C++ coding test | Baekjoon·Programmers STL patterns by problem type
Keywords (related searches)
C++, technical interview, interview questions, pointers, memory management, STL, multithreading, virtual functions, and similar terms will lead readers to this material.