All posts

C and C++ Interview Questions: 27 Questions on Pointers, Memory and Modern C++

C and C++ Interview Questions: 27 Questions on Pointers, Memory and Modern C++

The interviewer writes char *s = "hello"; s[0] = 'H'; and asks what happens. You say the string becomes "Hello". They ask, "Where does "hello" live in memory?" The question was never about strings. It was about whether you know what your program looks like once it is running.

That is how C and C++ interviews work. The languages are small enough that one question can be pushed down to the memory, the compiler and the standard, and it usually is.

The short answer

C interview questions come from a predictable pool: compilation, storage classes, pointers, arrays, dynamic memory, process memory layout, structures and unions, const and volatile, macros and undefined behaviour. C++ interview questions add classes and RAII, copy and move semantics, virtual functions, smart pointers, templates, the STL and exceptions. C coding interview questions are short programs where the edge cases matter more than the loop. Each answer below explains why, followed by the question you are likely to hear next.

For the wider placement plan, see our interview preparation guide for freshers. The first half of this list doubles as a set of C interview questions for freshers.

C interview questions: compilation, storage and pointers

1. What happens when you compile a C program?

Four stages. The preprocessor expands #include, #define and conditionals (gcc -E). The compiler turns that into assembly (gcc -S). The assembler produces an object file with unresolved symbols (gcc -c). The linker combines object files and libraries, resolves the symbols and writes the executable.

Likely follow-up: "You get undefined reference to foo. Which stage failed?" The linker. A declaration of foo was visible, so it compiled, but nothing supplied the definition.

2. Explain the storage classes in C.

auto is the default for locals: automatic lifetime, garbage value if uninitialised. register is a speed hint compilers mostly ignore, but you still cannot take its address. static on a local gives it whole-program lifetime, so it keeps its value between calls, is initialised once, and starts at zero by default. static on a global or function gives internal linkage, hiding it from other files. extern declares something defined elsewhere.

3. How does pointer arithmetic work?

Adding 1 to a pointer moves it by the size of the pointed-to type, not one byte, which is why a[i] is defined as *(a + i). Subtracting two pointers into the same array gives the element count between them, as a ptrdiff_t. Arithmetic is defined only within an array and one past its end; going further is undefined behaviour even without a dereference.

Likely follow-up: "Arithmetic on a void *?" Not in standard C, since void has no size. GCC allows it as an extension, treating the size as 1.

4. Are arrays and pointers the same thing?

No. An array decays to a pointer to its first element in most expressions, and the differences show where it does not. sizeof arr is the whole array; sizeof ptr is a pointer's size. You cannot assign to an array. arr and &arr share an address but differ in type, so &arr + 1 skips the whole array. And a parameter written int a[] is really a pointer, which is why sizeof inside the function cannot give you the length.

5. What are dangling, wild and NULL pointers?

A NULL pointer deliberately points to nothing; dereferencing it is undefined behaviour. A wild pointer was never initialised. A dangling pointer pointed to memory that has gone: after free(p), or after returning the address of a local variable.

Likely follow-up: "Does free(p) set p to NULL?" No, it gets a copy of the pointer. Set it yourself; free(NULL) is a harmless no-op, so a second free stops being a double free.

Dynamic memory and the process layout

6. malloc, calloc, realloc and free?

malloc(n) allocates n uninitialised bytes. calloc(count, size) allocates and zeroes the bytes, and good implementations check that count * size does not overflow. realloc(p, n) resizes a block, possibly moving it. free returns it. The allocators return NULL on failure.

Likely follow-up: "What is wrong with p = realloc(p, n)?" On failure realloc returns NULL but keeps the old block, and you have just overwritten your only pointer to it. Assign to a temporary and check it first.

7. Describe the memory layout of a C program.

From low addresses up, roughly: the text segment holds machine code and is read-only; read-only data holds string literals; the data segment holds globals and statics initialised to non-zero values; BSS holds zero or uninitialised ones, takes no space in the file and is zeroed at load. The heap grows upward and serves malloc. The stack sits high and grows down, holding each call's locals and return address.

Likely follow-up: so what about s[0] = 'H' from the opening? Writing a string literal is undefined behaviour, and because compilers usually place it in read-only memory, it is usually a segfault. char s[] = "hello"; copies it into a writable array.

8. Structure vs union, and what is padding?

A structure gives each member its own storage. A union overlays them all, so its size is the largest member rounded up for alignment. Padding is the bytes the compiler inserts so each member is suitably aligned. With a 4-byte int, struct { char a; int b; char c; } is typically 12 bytes, not 6; reorder it as int, char, char and it is 8.

Likely follow-up: "Write one union member and read another?" C allows it and reinterprets the bytes. In C++ it is undefined behaviour; use memcpy or C++20's std::bit_cast.

Qualifiers, macros and calling conventions

9. What do const and volatile mean?

const is a promise not to modify through this name. Read declarations right to left: const int *p is a pointer to a constant int (you can move p, not change *p); int *const p is a constant pointer (the reverse). volatile says the value can change outside the program's control, so every access must really happen and cannot be cached or optimised away. It is for memory-mapped hardware registers and signal handlers.

Likely follow-up: "Is volatile enough for threads?" No. It gives no atomicity or inter-thread ordering; use C11 atomics or a mutex. And yes, const volatile makes sense: a read-only hardware status register.

10. Macros or inline functions?

A macro is text substitution before type checking. #define SQUARE(x) x * x turns SQUARE(a + 1) into a + 1 * a + 1, and even a parenthesised version evaluates its argument twice, so SQUARE(i++) is undefined behaviour. An inline function type-checks its arguments, evaluates each once and respects scope; inline itself is only a hint. Macros remain right for include guards, conditional compilation, and stringising with #x.

11. Call by value vs call by reference in C?

C only has call by value, which is why swap(int a, int b) changes nothing in the caller. You get the effect of reference by passing pointers, swap(&x, &y); the pointers themselves are still copies. To change a caller's pointer, such as a linked list's head, pass a pointer to that pointer, such as struct node **.

Undefined behaviour, strings and crashes

12. What is undefined behaviour? What does i = i++ give?

Undefined behaviour means the standard places no requirement on the result, and the optimiser is allowed to assume it never happens. In C, i = i++ modifies i twice without sequencing, so there is no correct answer to what it prints. Signed overflow, using an uninitialised local's value (in most cases), out-of-bounds access and NULL dereference are UB too.

Likely follow-up: "And in C++?" Since C++17 the right side of an assignment is sequenced first, so i = i++ is defined there and leaves i unchanged; i++ + i++ is still undefined. Unsigned overflow is never UB in either language: it wraps.

13. What are the common string pitfalls in C?

Almost all come from the '\0' terminator. "hello" needs 6 bytes. strcpy and strcat do not know the destination size, so they overflow. strncpy adds no terminator when the source fills the buffer. gets cannot be used safely and was removed in C11; use fgets. sizeof gives the array size, strlen the length. == compares addresses; use strcmp.

14. What causes a segmentation fault, and how do you find one?

The OS stops a process that touches memory it may not: NULL or wild pointer dereference, writing a string literal, running far off an array, or stack overflow from runaway recursion. Use-after-free often does not crash at once, which is worse. Compile with -g and get a backtrace in gdb, or build with -fsanitize=address to get the exact bad access. Why the OS can catch it at all is virtual memory, covered in our OS interview questions.

C++ interview questions: classes and object lifetime

15. Class vs struct in C++?

Only the defaults differ: members and base classes are private in a class, public in a struct. By convention, struct is for plain data and class for types with invariants.

16. Constructors, destructors and RAII?

A constructor establishes an object's invariants; the destructor runs when its lifetime ends. For a local object that is guaranteed on every exit from the scope, including an exception that is caught somewhere, in reverse order of construction. RAII (Resource Acquisition Is Initialization) ties a resource to that lifetime: std::lock_guard unlocks a mutex, std::fstream closes a file, std::vector frees memory, with no cleanup code on any path.

Likely follow-up: "Why a virtual destructor?" Deleting a derived object through a base pointer without one is undefined behaviour; in practice the derived part is never destroyed. Any class meant for polymorphic deletion needs it.

17. Copy vs move semantics?

Copying makes an independent duplicate, which for a std::string means a new allocation. Moving, added in C++11, transfers the resources: the new string takes the old buffer and the old one is typically left empty. A move constructor takes an rvalue reference, T&&. std::move moves nothing; it is a cast that lets an lvalue bind to T&&. A moved-from object is valid but unspecified: assign to it or destroy it, do not rely on its value.

Likely follow-up: "Why mark a move constructor noexcept?" std::vector moves elements during reallocation only if the move cannot throw (or the type cannot be copied); otherwise it copies them to keep its exception guarantee.

18. Rule of three, five and zero?

Three: a class that needs a custom destructor, copy constructor or copy assignment needs all three, because it manages a resource by hand. With only a destructor, the default copy duplicates a raw pointer and both objects free it. Five (C++11): add move constructor and move assignment, since declaring a destructor or copy operation suppresses the implicit moves. Zero: hold resources in std::vector, std::string or std::unique_ptr and write none of them.

19. References vs pointers?

A reference is an alias: it must be initialised, cannot be reseated and cannot legitimately be null. A pointer holds an address: it can be null, reassigned and used in arithmetic. Use a reference when the object must exist, a pointer when "nothing" is a valid answer.

Polymorphism

20. Overloading vs overriding?

Overloading is same name, different parameter lists, in one scope, resolved at compile time; return type alone cannot overload. Overriding is a derived class redefining a base virtual function with the same signature, resolved at runtime from the object's actual type.

Likely follow-up: "What does override do?" Since C++11 it makes the compiler confirm you are overriding something. Without it, a signature typo silently creates a new function.

21. How do virtual functions work? What is a vtable?

The standard specifies only the behaviour, but mainstream compilers implement it the same way: each polymorphic class has a table of function pointers, the vtable, and each object carries a hidden vptr to its class's table. A virtual call loads the vptr, indexes the slot and calls through it, costing a pointer per object and an indirect call that is hard to inline.

Likely follow-up: "A virtual call inside a constructor?" It runs the version of the class being constructed, because the derived part does not exist yet.

22. Pure virtual functions and abstract classes?

virtual double area() const = 0; is pure virtual. A class with one is abstract: it cannot be instantiated, only derived from, and derived classes must override every pure virtual function to become concrete. The design side of this is in our OOPs interview questions.

Likely follow-up: "Can a pure virtual function have a body?" Yes, defined outside the class. A pure virtual destructor must have one, since every derived destructor calls it.

Memory in modern C++

23. new and delete vs malloc and free?

new allocates then constructs; delete destroys then frees. malloc and free deal only in raw bytes and never run constructors or destructors. new returns a typed pointer and throws std::bad_alloc instead of returning NULL. Memory from new[] needs delete[], and mixing any of the pairs is undefined behaviour.

24. unique_ptr, shared_ptr and weak_ptr?

All three came in C++11. std::unique_ptr is sole ownership: move-only, with no overhead over a raw pointer when using the default deleter. std::shared_ptr shares ownership via a reference count in a control block and destroys the object when the last owner goes. std::weak_ptr observes without owning; lock() gives you a shared_ptr if the object still exists. Prefer std::make_unique (C++14) and std::make_shared, which allocates object and control block together.

Likely follow-up: "When do you need weak_ptr?" To break cycles. A parent and child holding shared_ptrs to each other never reach zero and both leak; make the back-pointer weak.

Templates, the STL and exceptions

25. What are templates, and why are they in headers?

A template is a blueprint from which the compiler generates code per type at compile time, so std::max<int> and std::max<double> are separate functions with full type checking and no runtime cost. The compiler needs the whole definition wherever a template is used, so it lives in a header unless you explicitly instantiate the types you need.

26. vector vs list, map vs unordered_map, and iterator invalidation?

std::vector is contiguous: O(1) indexing, amortised O(1) push_back, cache-friendly iteration. std::list gives O(1) insert and erase at a known position but no indexing and poor locality, so vector usually wins. std::map is a balanced tree (red-black in practice), sorted, O(log n). std::unordered_map is a hash table, average O(1), worst O(n), unordered. Invalidation: a vector reallocation invalidates every iterator, pointer and reference; erasing invalidates from the erased element on. Erasing from a list or map invalidates only the erased element.

Likely follow-up: "Erase while looping over a vector?" Write it = v.erase(it) and increment only when you did not erase, or use std::erase_if (C++20). The underlying structures are in the data structures you must know.

27. How do exceptions work in C++?

throw unwinds the stack to the nearest matching catch, destroying every fully constructed local on the way, which is why RAII and exceptions fit together. Throw by value, catch by const reference; catching by value slices off the derived part.

Likely follow-up: "Can a destructor throw?" It should not. Destructors are implicitly noexcept since C++11 (unless declared noexcept(false) or a member's or base's destructor is), so an exception escaping one calls std::terminate.

C coding interview questions

These programs come up in nearly every fresher C round. Interviewers listen for the key idea and the edge cases, not the loop.

Reverse a string in place. Put one index at the start and one at strlen(s) - 1, swap, and move both inward until they meet. Use strlen, not sizeof. Check for an empty string before subtracting 1, because strlen returns an unsigned size_t, and remember a string literal cannot be modified at all. O(n) time, O(1) space.

Check for a palindrome. The same two-index walk, comparing instead of swapping. Ask whether case and punctuation count; if not, skip with isalnum and compare with tolower, passing each an unsigned char because a negative char is undefined behaviour for them.

Swap two numbers without a temporary. XOR (a ^= b; b ^= a; a ^= b;) or add and subtract. The pitfalls are the real answer: if both arguments are the same object, swap(&x, &x), XOR zeroes it (so does add and subtract), and the arithmetic version can overflow, which is UB for signed integers. Say that in real code a temporary is clearer and compiles at least as well.

Count set bits. Checking n & 1 and shifting takes one pass per bit. Brian Kernighan's trick is better: n & (n - 1) clears the lowest set bit, so loop until zero and count, one pass per set bit. Use an unsigned type, since right-shifting a negative value is implementation-defined. Mention __builtin_popcount and C++20's std::popcount.

Reverse a singly linked list. Use prev (starting NULL), curr and next: save curr->next, point curr->next at prev, then advance both. When curr is NULL, prev is the new head. Written this way, an empty list and a single node need no special case. The recursive version uses O(n) stack and can overflow on a long list.

Explaining these out loud, without typing them, is exactly what a round on our DSA interview practice page asks for.

How to practise these

Read each answer, close the page, and explain it out loud, then ask yourself the follow-up. In C and C++ it is usually one level closer to the memory: where does this live, who frees it, what does the standard say. When you hit one you cannot answer, see how to handle a question you don't know.

To rehearse under pressure, a round with Sia, the AI interviewer, asks one question at a time against a clock, takes spoken or typed answers, and scores each with a written write-up. There is no code editor, so it trains what these interviews test: explaining your approach, steps, complexity and edge cases. When you want a person, a live interview with a working engineer over Zoom rates you on technical depth, problem solving, communication, behaviour and code quality, with written feedback.