25 C Memory Management Interview Questions and Answers (with Code)

Memory management is where C interviews get serious. Pointers test syntax — memory questions test whether you can be trusted with a production codebase. This page collects 25 real C memory management interview questions with clear answers and tested, compilable code: stack vs heap, the malloc family, leaks, dangling pointers, double free, struct padding, and the predict-the-output traps interviewers love.

Jump to a section: Stack vs Heap · malloc, calloc, realloc, free · Memory Bugs · sizeof, Padding, and Copying · Debugging Tools · Predict the Output

Stack vs Heap

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

The stack is managed automatically: every function call pushes a frame holding its local variables, and the frame is destroyed when the function returns. Allocation is nearly free (move the stack pointer), but the memory’s lifetime is tied to the function and the size must be known at compile time. The heap is managed by you: malloc() reserves a block that survives until you call free(), so lifetime and size are under your control — and so are the bugs. Every question on this page is ultimately about the consequences of that trade.

Stack Heap
Allocation Automatic (function call) Manual (malloc/free)
Lifetime Until the function returns Until you free it
Size Fixed at compile time, small (MBs) Decided at runtime, large
Speed Very fast Slower (allocator bookkeeping)
Typical bug Stack overflow, dangling pointer to a dead frame Leak, use-after-free, double free

2. What are the memory segments of a running C program?

Five regions, low to high in the classic layout: the text segment (the compiled machine code, read-only), the initialized data segment (globals and statics with an explicit initializer), the BSS (globals and statics that start as zero — stored as just a size, zero-filled at load time), the heap (grows upward as you malloc), and the stack (grows downward with each call). Knowing where a variable lives tells you its lifetime — a static local variable lives in the data segment, which is exactly why it keeps its value between calls.

3. What storage durations does C define?

Three (in C89/C99 terms): automatic — ordinary locals, alive until the enclosing block exits; static — globals and static variables, alive for the whole program run; and allocatedmalloc‘d memory, alive until freed. Interviewers ask this to see whether you understand that scope (where a name is visible) and lifetime (when the object exists) are different things: a heap block returned from a function is out of scope but very much alive.

4. What is a stack overflow and what causes it?

The stack has a fixed budget (commonly 1–8 MB). Exceed it and the program crashes — usually a segmentation fault. The two classic causes: unbounded recursion (a missing or wrong base case pushes frames until the stack dies) and huge local arrays like int buf[10000000];. The fix for the second is to move the data to the heap: a few MB is nothing to malloc but fatal on the stack.

5. When must you use the heap instead of the stack?

Three situations: the size is only known at runtime (user input, file size); the data must outlive the function that creates it (see Q25); or the data is simply too big for the stack. If none of those apply, prefer the stack — it’s faster, can’t leak, and cleans up after itself.

malloc, calloc, realloc, free

6. What is the difference between malloc() and calloc()?

Two differences, and interviewers want both: calloc zeroes the memory, and it takes count and element size separately, checking the multiplication for overflow (malloc(n * size) can silently wrap and allocate a tiny block):

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int i;
    int *m = malloc(5 * sizeof *m);   /* contents: garbage      */
    int *c = calloc(5, sizeof *c);    /* contents: all zero     */

    if (m == NULL || c == NULL) {
        free(m);
        free(c);
        return 1;
    }
    for (i = 0; i < 5; i++) {
        printf("c[%d] = %d\n", i, c[i]);   /* guaranteed 0 */
    }
    free(m);
    free(c);
    return 0;
}

Reading m[i] before writing it would be undefined behavior — that’s the whole point of the distinction. Note the idiom sizeof *m: it stays correct even if the type of m changes later.

7. How do you use realloc() safely?

realloc(p, n) resizes a block, preserving its contents, and may move it to do so. The classic trap is p = realloc(p, n); — if realloc fails it returns NULL, and you’ve just overwritten your only pointer to the original block. That’s a guaranteed leak. Always go through a temporary:

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    size_t i;
    int *tmp;
    int *arr = malloc(4 * sizeof *arr);

    if (arr == NULL) {
        return 1;
    }
    for (i = 0; i < 4; i++) {
        arr[i] = (int)i;
    }

    tmp = realloc(arr, 8 * sizeof *arr);   /* NEVER assign straight to arr */
    if (tmp == NULL) {
        free(arr);                         /* original block still valid    */
        return 1;
    }
    arr = tmp;

    for (i = 4; i < 8; i++) {
        arr[i] = (int)i;
    }
    for (i = 0; i < 8; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    free(arr);
    return 0;
}

Two follow-ups interviewers like: after a successful realloc, the old pointer is invalid (using it is use-after-free), and the new tail bytes are uninitializedrealloc never zeroes.

8. What happens if malloc() fails, and how should you handle it?

It returns NULL. Every allocation should be checked before use — dereferencing NULL is undefined behavior, and on embedded targets (where allocation failure is routine) an unchecked malloc is an automatic interview fail. The standard pattern is check, clean up whatever you’ve already allocated, and propagate the error upward.

9. Should you cast the return value of malloc() in C?

No. malloc returns void *, which converts to any object pointer type implicitly:

int *p = malloc(10 * sizeof *p);          /* correct C          */
int *q = (int *)malloc(10 * sizeof *q);   /* legal but unneeded */

The cast is required in C++, which is why you see it in old code — but in C it’s noise, and historically it could hide the bug of a missing <stdlib.h> include. Saying “the cast is a C++ habit, not a C requirement” is exactly the answer interviewers are listening for.

10. What does malloc(0) return?

Implementation-defined: either NULL, or a unique non-NULL pointer that you must not dereference but may pass to free(). Portable code never relies on either behavior — the question is really testing whether you know that “implementation-defined” is a category the C standard uses deliberately.

11. How does free() know how many bytes to release?

The allocator stores bookkeeping — typically a small header just before the address it hands you — recording the block size. free(p) reads that hidden header. This is also why free only works with the exact pointer malloc returned: pass a pointer into the middle of a block and the allocator reads garbage where its header should be (see Q16).

12. What is a double free, and is free(NULL) legal?

Calling free() twice on the same pointer is undefined behavior — the allocator’s free-list gets corrupted, and attackers actively exploit it. But free(NULL) is defined as a no-op, which enables the standard defense:

free(p);
p = NULL;     /* a second free(p) is now harmless: free(NULL) does nothing */

Memory Bugs

13. What is a memory leak? Show one.

Heap memory that can no longer be reached but was never freed. The program still owns it, so the OS won’t reclaim it until exit — in a long-running server the process grows until it dies. The classic form is overwriting your only pointer:

#include <stdlib.h>
#include <string.h>

static char *duplicate(const char *s)
{
    char *copy = malloc(strlen(s) + 1);

    if (copy != NULL) {
        strcpy(copy, s);
    }
    return copy;
}

int main(void)
{
    char *a = duplicate("hello");

    a = duplicate("world");   /* the "hello" block is now unreachable — leaked */
    free(a);                  /* frees "world" only */
    return 0;
}

Nothing crashes, nothing warns — leaks are silent, which is why tools exist to find them (Q22).

14. What is the difference between a memory leak and a dangling pointer?

They are opposites. A leak is memory with no pointer — the block is alive but unreachable. A dangling pointer is a pointer with no memory — the block is dead but the pointer still holds its old address. The leak wastes resources slowly; the dangling pointer is undefined behavior waiting to fire the moment you dereference it.

15. What is use-after-free?

Dereferencing a pointer after its block has been freed. The allocator may have already handed that memory to another malloc, so reads return someone else’s data and writes corrupt it — often far from the actual bug, which makes this one of the nastiest C bugs to debug (and one of the most exploited security holes). We’ve written up a full example with real crash behavior: Use-After-Free in C.

16. Which pointers is it legal to pass to free()?

Only two things: a pointer returned by malloc/calloc/realloc (the exact value, not a pointer into the middle of the block) and NULL. Freeing a stack variable’s address, a string literal, or p + 1 is undefined behavior — the allocator looks for its header where none exists (Q11). Worked examples of each illegal case: Freeing an Invalid Pointer in C.

17. What is a heap buffer overflow?

Writing past the end of an allocated block — strcpy(p, s) where s is longer than the block, or an off-by-one loop bound. Past the end of your block sit the allocator’s headers for the next block, so the corruption typically detonates later, inside malloc or free, in code far from the bug. Rule of thumb: every allocation of strlen(s) must be strlen(s) + 1 — the terminator needs a byte too (see sizeof vs strlen).

18. Why set a pointer to NULL after freeing it?

Three concrete wins: a second free becomes a no-op instead of heap corruption (Q12); an accidental dereference becomes an immediate, debuggable NULL crash instead of silent use-after-free; and if (p != NULL) checks stay truthful. The honest caveat — and strong candidates volunteer it — is that it only fixes the pointer you nulled: other copies of the same address elsewhere in the program still dangle.

sizeof, Padding, and Copying

19. Why does sizeof an array “stop working” inside a function?

Because the array never arrives. A function parameter declared int arr[10] is rewritten by the compiler to int *arr — arrays decay to pointers when passed. So sizeof gives the full array size only in the scope where the array is declared:

#include <stdio.h>

static void take(int *arr)
{
    /* arr is just a pointer here — its size is the pointer's size */
    printf("in function: %lu bytes\n", (unsigned long)sizeof(arr));
}

int main(void)
{
    int arr[10];

    printf("in main:     %lu bytes\n", (unsigned long)sizeof(arr));  /* 40 */
    take(arr);                                                       /*  8 */
    return 0;
}

That’s why C functions take an explicit length parameter. Related trap with strings: sizeof a String Array in C and sizeof a Pointer.

20. Why is sizeof(struct) sometimes bigger than the sum of its members?

Padding. The compiler inserts unused bytes so each member sits at an address divisible by its alignment requirement (an int wants a 4-byte boundary). Member order therefore changes the size:

#include <stdio.h>

struct bad {
    char c;      /* 1 byte + 3 padding      */
    int  i;      /* 4 bytes                 */
    char d;      /* 1 byte + 3 tail padding */
};

struct good {
    int  i;      /* 4 bytes                 */
    char c;      /* 1 byte                  */
    char d;      /* 1 byte + 2 tail padding */
};

int main(void)
{
    printf("bad:  %lu\n", (unsigned long)sizeof(struct bad));   /* 12 */
    printf("good: %lu\n", (unsigned long)sizeof(struct good));  /*  8 */
    return 0;
}

Ordering members largest-first minimizes padding — on an embedded target with thousands of instances, that’s real memory. Follow-up worth knowing: the tail padding exists so the struct works in arrays (every element stays aligned).

21. What is the difference between memcpy() and memmove()?

memcpy assumes source and destination don’t overlap — if they do, it’s undefined behavior. memmove handles overlap correctly, as if the bytes went through a temporary buffer, at a tiny performance cost. Shifting elements inside the same array is the textbook overlap case — see Q24 for it in action. When in doubt, memmove is never wrong; memcpy is merely faster when you can prove independence.

Debugging Tools

22. How do you find memory leaks and heap corruption in practice?

On Linux, Valgrind: run valgrind --leak-check=full ./program and it reports every leaked block with the stack trace that allocated it (“definitely lost: 24 bytes in 1 blocks”) plus invalid reads and writes as they happen. We walk through real output in Finding Memory Leaks with Valgrind. On macOS (where Valgrind doesn’t run) and in CI, AddressSanitizer is the modern equivalent: compile with -fsanitize=address and the program aborts at the exact instruction that touches bad memory. Naming a concrete tool and its output is what separates “knows the theory” from “has shipped C.”

23. What is heap fragmentation?

After many allocations and frees of mixed sizes, free memory ends up scattered in small non-contiguous chunks. A malloc(1 MB) can then fail even though several MB are free in total — no single hole is big enough. Long-running systems mitigate it with fixed-size block pools, arena allocators, or by allocating everything up front — which is exactly why many embedded and safety-critical shops (see MISRA) ban malloc after initialization.

Predict the Output

24. What does this program print?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
    char *s = malloc(8);

    if (s == NULL) {
        return 1;
    }
    strcpy(s, "abcdef");
    memmove(s + 2, s, 4);
    printf("%s\n", s);
    free(s);
    return 0;
}

Output: ababcd. memmove copies the first four characters "abcd" on top of positions 2–5, and because source and destination overlap, only memmove is legal here — memcpy would be undefined behavior (Q21). Walk it byte by byte: a b c d e f becomes a b a b c d, and the terminator at index 6 is untouched.

25. What does this program print — and why doesn’t it crash?

#include <stdio.h>
#include <stdlib.h>

static int *make_counter(void)
{
    int *count = malloc(sizeof *count);

    if (count != NULL) {
        *count = 0;
    }
    return count;   /* heap memory survives the return */
}

int main(void)
{
    int i;
    int *c = make_counter();

    if (c == NULL) {
        return 1;
    }
    for (i = 0; i < 5; i++) {
        (*c)++;
    }
    printf("%d\n", *c);
    free(c);
    return 0;
}

Output: 5. Returning a pointer to heap memory is correct — allocated storage lives until free(), regardless of which function created it (Q3). The trap version of this question returns the address of a local variable, which dies with the stack frame: that one is undefined behavior, dissected in Returning a Pointer to a Local Variable. Being able to say precisely why one is fine and the other is fatal is the mark of a candidate who understands lifetimes.

Practice More

Memory questions are best drilled until the answers are reflexes:

Practice on the go: our free C Programming Quiz app for Android has 150+ questions across 9 categories — including a dedicated memory management section with explanations for every answer.

Recommended Book

Section 8.7 of The C Programming Language by Kernighan & Ritchie walks through building a storage allocator from scratch — still the best way to understand what malloc and free actually do. We’ve solved all of the book’s exercises. Also on Amazon.com.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>