---
title: C Programming Interview Questions & Answers (2026): Pointers, Memory & More
description: The C programming interview questions that get asked in 2026 — pointers, memory management, arrays vs pointers, storage classes and the classic gotchas — with real answers.
url: https://usegreenroom.app/blog/c-programming-interview-questions
last_updated: 2026-06-20
---

← Back to blog

Technical

# C programming interview questions and answers

June 20, 2026 · 24 min read

![C programming interview questions and answers — cover from Greenroom, the AI mock interviewer](/assets/blog/c-programming-interview-questions-hero.webp)

It's 11:40 PM the night before a systems-engineering interview, and you're staring at a whiteboard app on your laptop, drawing boxes for a linked list you could write blindfolded in Python. Then your brain helpfully asks: "wait, what actually happens to `local` after this function returns?" You freeze. You've used C for three semesters. You have never once drawn what the stack frame looks like at the exact millisecond it gets popped. Forty-five minutes and one very questionable cup of instant coffee later, you've sketched something that looks like a parking lot diagram, labeled "RAM," and you still aren't sure if it's right.

This is the specific, very avoidable panic that C interviews are built to provoke — not because interviewers are sadists, but because C is the one language left in mainstream interviewing that refuses to hide anything from you. There's no garbage collector quietly cleaning up your memory bugs, no runtime catching your array overruns mid-flight, and no language-level abstraction standing between your code and the actual bytes sitting in RAM. Interviews for systems, embedded, driver, and core engineering roles lean on C heavily for exactly this reason — the questions cluster around **pointers and memory**, because that's precisely where the gap between "knows C syntax" and "actually understands C" becomes visible to another human being in real time. This guide covers the **C programming interview questions** that actually get asked, organized by area, with a real answer and a short code example for each — so that 11:40 PM whiteboard panic happens during your prep, not during the actual interview.

## Pointers (the core)

### What is a pointer, and what is a pointer to a pointer?

A pointer is a variable whose value is the memory address of another variable, declared with `*` and dereferenced with `*` to read or write the value it points to. A pointer to a pointer (`int **pp`) stores the address of a pointer variable rather than the address of the actual data — dereferencing it once (`*pp`) gives you back the inner pointer, and dereferencing it twice (`**pp`) gives you the underlying value. The classic real use case is when a function needs to modify a pointer itself (not just the data it points to) — for example, a function that allocates memory and needs the caller's pointer variable to start pointing at the new block:

<pre><code>void allocate(int **p, int n) {
    *p = malloc(n * sizeof(int));
}

int main(void) {
    int *arr = NULL;
    allocate(&arr, 10);   // arr now points to the new block
    free(arr);
}</code></pre>

Interviewers ask this because it tests whether you actually understand that passing a plain `int *` to a function only gives that function a *copy* of the address — it can change what's at that address, but it can't make the caller's variable point somewhere else. That requires a pointer to a pointer.

### What is the difference between an array and a pointer?

An array is a contiguous block of memory whose name refers to its first element, with a fixed size that's known at compile time and baked into the type — `int arr[10]` is fundamentally a different type from `int *p`, even though `arr` "decays" into a pointer to its first element in most expressions. The critical difference shows up with `sizeof`: `sizeof(arr)` gives the total size of the array in bytes (40 on a system with 4-byte ints), while `sizeof(p)` gives the size of a pointer itself (typically 8 bytes on 64-bit systems), regardless of what it points to. You also can't reassign an array name (`arr = anotherArray;` is a compile error) because `arr` isn't a variable holding an address — it *is* the memory. Interviewers often follow up with "what happens when you pass an array to a function" — the answer is that it decays to a pointer at the function boundary, which is exactly why `sizeof` on a parameter that "looks like an array" inside a function actually gives you the pointer size, a bug that trips up a surprising number of candidates.

### What is a dangling pointer, a wild pointer, and a null pointer?

A **dangling pointer** points to memory that has already been freed or gone out of scope — the pointer variable itself still holds an address, but that address is no longer valid, and dereferencing it is undefined behavior even though the compiler won't stop you. A **wild pointer** is one that was never initialized at all, so it holds garbage from whatever was previously in that memory — even more dangerous than a dangling pointer because there's no telling what it points to. A **null pointer** (`NULL`, or `0`) deliberately points to nothing, used as a sentinel to mean "this pointer isn't valid right now" — and checking for it before dereferencing is the standard defensive pattern. The most common interview trap is returning the address of a local variable from a function:

<pre><code>int *makeDangling(void) {
    int local = 42;
    return &local;   // local's stack frame is gone after return
}</code></pre>

The returned pointer is dangling the instant the function returns, because `local` lived on the stack frame that just got popped — the address is technically still "there" until something overwrites it, which is exactly what makes this bug so insidious: the code often appears to work in testing and then fails unpredictably in production.

### What is pointer arithmetic?

Pointer arithmetic means adding or subtracting integers from a pointer, where the compiler automatically scales the offset by the size of the pointed-to type — `p + 1` on an `int *` advances by 4 bytes (on a typical system), not 1 byte, because it's meant to move to the *next element*, not the next byte. This is exactly how array indexing works under the hood: `arr[i]` is defined as `*(arr + i)`, which is why `arr[i]` and `i[arr]` are both legal and identical in C — a fact interviewers sometimes throw in as a curveball. You can also subtract two pointers into the same array to get the number of elements between them, but adding two pointers together, or doing arithmetic across unrelated arrays, is undefined behavior:

<pre><code>int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
printf("%d\n", *(p + 2));   // prints 30
printf("%ld\n", (p + 4) - p); // prints 4</code></pre>

### What is a function pointer and where is it used?

A function pointer stores the address of a function rather than a variable, letting you call that function indirectly through the pointer — declared with the function's return type and parameter types, e.g. `int (*op)(int, int);`. The most common real-world use is implementing callback-style behavior in C, where there's no first-class function type or lambda syntax: a sort routine that takes a comparator, a dispatch table that maps commands to handler functions, or a plugin system that loads functions at runtime.

<pre><code>int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

int main(void) {
    int (*op)(int, int) = add;
    printf("%d\n", op(3, 4));   // 7
    op = sub;
    printf("%d\n", op(3, 4));   // -1
}</code></pre>

Interviewers like this question because it tests whether you understand that functions, like data, have addresses — and the `qsort` standard library function is a perfect real example: it takes a function pointer for the comparison logic, which is the same pattern you'd reimplement yourself in embedded or systems code that has no STL/templates to lean on.

## Memory management

### `malloc` vs `calloc` vs `realloc` vs `free` — what does each one actually do?

All four operate on the heap. `malloc(size)` allocates a single block of `size` bytes and leaves the contents **uninitialized** — reading from it before writing is undefined behavior, even though it often happens to read as zero in practice (don't rely on that). `calloc(n, size)` allocates space for `n` elements of `size` bytes each and explicitly zero-initializes every byte, at a small extra cost for the zeroing — useful when you need a clean buffer and don't want to call `memset` yourself afterward. `realloc(ptr, newSize)` resizes a previously allocated block, possibly moving it to a new address if it can't grow in place — it returns the new pointer, which you must capture, because the old pointer may now be invalid. `free(ptr)` releases the block back to the heap allocator; calling `free` on the same pointer twice ("double free") or on memory you didn't allocate corrupts the heap's internal bookkeeping and is one of the nastiest classes of C bugs to track down.

<pre><code>int *arr = malloc(5 * sizeof(int));      // uninitialized
int *zeroed = calloc(5, sizeof(int));    // all zeros
arr = realloc(arr, 10 * sizeof(int));    // grow to 10 ints
free(arr);
free(zeroed);</code></pre>

### What is the difference between stack and heap memory?

The **stack** holds local variables and function call frames, grows and shrinks automatically as functions are called and return, and is extremely fast because allocation is just moving a pointer — but it's limited in size (typically a few MB) and anything on it disappears the instant its function returns. The **heap** is a much larger pool of memory you explicitly manage with `malloc`/`free`, persists until you free it (or the program exits), and is what you use for data that needs to outlive the function that created it or whose size isn't known until runtime. The tradeoff interviewers want you to articulate: stack allocation is essentially free but automatically destroyed and size-limited, while heap allocation is flexible and long-lived but slower, manually managed, and the source of leaks and dangling pointers when that management goes wrong.

### What is a memory leak, and how do you detect one?

A memory leak happens when memory is allocated on the heap with `malloc`/`calloc`/`realloc` but never freed, and the program loses every pointer that referenced it — so the memory stays reserved for the lifetime of the process with no way to reclaim it. A single leaked block in a short-lived CLI tool barely matters, but the same pattern in a long-running server or embedded device that runs for days accumulates until the process runs out of memory and crashes or starts swapping. The most common cause is an early `return` or thrown error path that skips the `free()` call that would normally run at the end of the function — which is why disciplined C code often uses a single `cleanup:` label with `goto` so every exit path passes through the same free logic. Detection tools matter here because leaks don't show up as crashes immediately: `valgrind --leak-check=full` runs your program and reports every block still allocated when it exits, along with the call stack that allocated it, and AddressSanitizer (`-fsanitize=address`) catches leaks plus a wider class of memory errors at a smaller performance cost, which is why it's often the default in CI.

### What is a segmentation fault and what causes it?

A segmentation fault is the operating system killing your process because it tried to access memory it doesn't have permission to touch — not "wrong data," but memory outside any region the OS has actually mapped for that process, or memory marked read-only that you tried to write to. Common causes: dereferencing a null or wild pointer, dereferencing a dangling pointer to memory that's been freed and unmapped, writing past the end of an allocated buffer far enough to hit unmapped pages, or a stack overflow from runaway recursion eating through the stack's bounded space. The segfault itself is actually a *safety mechanism* — without it, an out-of-bounds write would silently corrupt whatever happens to live at that address, which is far harder to debug than a clean, immediate crash; the real debugging tool here is again a sanitizer or `gdb` with a core dump, since the line that crashes is often not the line that introduced the actual bug.

### What is a NULL pointer dereference, and how do you guard against it?

A NULL pointer dereference is reading or writing through a pointer whose value is `NULL` (0) — there's no valid memory at address zero, so the OS immediately raises a fault, almost always surfacing as a segfault. It typically happens when a function that can fail (like `malloc`, or a lookup that may not find anything) returns `NULL` on failure and the caller uses the result without checking:

<pre><code>int *p = malloc(1000000000000); // may fail and return NULL
*p = 5;   // crash if malloc returned NULL</code></pre>

The fix is always to check the return value of any function that can return `NULL` before dereferencing it, and to set pointers to `NULL` explicitly after `free`-ing them so a later accidental use crashes predictably and immediately instead of silently corrupting memory through a dangling pointer.

![C interview topics — pointers, memory, storage classes, arrays](/assets/blog/pool-structured-screen.webp)

C interviews live and die on pointers and memory — know them cold.

## Storage classes & keywords

### What do `auto`, `register`, `static`, and `extern` each do?

`auto` is the default storage class for local variables — automatically allocated on the stack and destroyed when the block exits; it's so rarely written explicitly that most C programmers have never typed it. `register` is a hint to the compiler to keep a variable in a CPU register instead of memory for faster access — modern compilers' optimizers are better at this decision than the hint usually is, so it's largely a legacy keyword today. `static` has two distinct meanings depending on context: on a local variable, it makes the variable persist across function calls instead of being reinitialized each time (and initializes it only once); on a global variable or function, it restricts visibility to the current file, preventing other translation units from linking to it. `extern` declares that a variable or function is defined in another file, telling the compiler "trust me, this exists elsewhere — just link to it" — the mechanism that lets a global variable be shared across multiple `.c` files via a shared header.

<pre><code>int counter(void) {
    static int count = 0;   // persists between calls
    return ++count;
}
// counter() returns 1, 2, 3... on successive calls</code></pre>

### What does `const` mean, and what does `volatile` mean?

`const` tells the compiler that a variable shouldn't be modified after initialization — the compiler enforces this at compile time and will reject code that tries to assign to a `const` variable, which makes intent explicit and catches accidental mutation bugs early. `volatile` tells the compiler the *opposite* kind of thing: that a variable's value can change at any time through means the compiler can't see — a hardware register, a value updated by an interrupt handler, or memory shared with another thread — so the compiler must not optimize away repeated reads of it or cache it in a register across iterations. The two are not opposites and can combine (`const volatile`, common for a hardware status register that your code reads but never writes): `const` is about what *your code* is allowed to do, `volatile` is about what the *compiler* is allowed to assume.

### What is the difference between `#define` and `const`?

`#define` is a preprocessor text-substitution directive — it literally swaps the macro name for its replacement text *before compilation even starts*, has no type, no scope (it's visible from the point of definition to the end of the file, ignoring block scope), and doesn't show up in the symbol table for a debugger to inspect. `const` creates an actual typed variable that the compiler type-checks, that respects scope rules, and that a debugger can see and set breakpoints around. The practical interview answer: prefer `const` for typed constants in modern C because you get type safety and proper scoping, and reserve `#define` for things that genuinely need to be preprocessor-level — conditional compilation, include guards, or macros that generate code.

<pre><code>#define MAX_SIZE 100      // pure text substitution, no type
const int maxSize = 100;  // typed, scoped, debuggable</code></pre>

### What is the difference between a structure and a union? Show an example.

A `struct` allocates separate memory for each of its members, laid out one after another (with possible padding for alignment), so its total size is roughly the sum of its members' sizes — every member exists simultaneously and independently. A `union` allocates a single block of memory sized to fit its *largest* member, and all members share that same memory — writing to one member overwrites whatever was stored by any other member, so only one member holds a meaningful value at any given time. Unions are used when you need to represent "one of several possible types" in the smallest possible space — a classic use is a tagged variant type, where a separate `enum` field tracks which member is currently valid.

<pre><code>struct Point {
    int x;
    int y;
};   // sizeof = 8 (both members coexist)

union Value {
    int i;
    float f;
    char c[4];
};   // sizeof = 4 (members share the same 4 bytes)

union Value v;
v.i = 65;
printf("%c\n", v.c[0]);  // reads the same bytes 'i' just wrote, as a char</code></pre>

### What is a void pointer, and why would you ever use one?

A `void *` is a generic pointer that can point to any data type, but it cannot be dereferenced directly — the compiler has no idea how many bytes to read or how to interpret them, so you must cast it to a concrete type first. The standard library leans on this constantly: `malloc` returns `void *` because it has no idea what type of data you're about to store in the block it just allocated, and `qsort`/`memcpy` accept `void *` parameters so the same function can operate on arrays of any type without C needing generics or templates. The interview follow-up worth anticipating: "why doesn't `sizeof(void)` make sense, and what does `sizeof` on a `void *` actually give you?" — the answer is that `sizeof(void)` is meaningless because void represents the absence of a type, but `sizeof(void *)` is just the size of a pointer itself (typically 8 bytes on 64-bit systems), same as any other pointer type.

<pre><code>void printAsInt(void *ptr) {
    int *p = (int *)ptr;   // cast before dereferencing
    printf("%d\n", *p);
}

int main(void) {
    int x = 42;
    printAsInt(&x);   // prints 42
}</code></pre>

### What is the difference between deep copy and shallow copy in C, particularly with structs?

Assigning one struct to another (`struct A b = a;`) performs a shallow, member-by-member copy — every field that holds a plain value gets duplicated correctly, but if the struct contains a pointer (say, to a dynamically allocated string), only the pointer's *address* gets copied, not the data it points to. Both structs now point to the exact same heap block, which means freeing one struct's pointer and then accessing the "other" struct's copy of that pointer is a use-after-free waiting to happen, and modifying the data through one struct silently changes what the other one sees too. A deep copy means explicitly allocating new memory and copying the actual pointed-to data, field by field, for every pointer member — there's no built-in language feature that does this for you in C, which is exactly the point interviewers are testing: do you know the assignment operator won't save you here, and can you write the manual deep-copy function yourself?

<pre><code>struct Person {
    char *name;
    int age;
};

struct Person deepCopy(struct Person src) {
    struct Person dst = src;                 // shallow copy first
    dst.name = malloc(strlen(src.name) + 1);  // allocate new memory
    strcpy(dst.name, src.name);               // copy the actual data
    return dst;
}</code></pre>

### What's the difference between `strcpy`, `strncpy`, and why does buffer size matter so much in C string handling?

`strcpy(dst, src)` copies `src` into `dst` including the null terminator, with absolutely no check on whether `dst` is actually big enough to hold it — if `src` is longer than the buffer you allocated for `dst`, you get a buffer overflow, silently overwriting whatever memory happens to sit right after that buffer. `strncpy(dst, src, n)` caps the copy at `n` bytes, which sounds safer but has its own trap: if `src` is *longer* than `n`, the result isn't null-terminated at all, and if it's *shorter*, the remaining bytes up to `n` get padded with extra null bytes — a behavior almost nobody remembers correctly under interview pressure. The practically correct pattern almost every senior C engineer uses is to always allocate one byte more than you think you need for the terminator, pass the buffer's true size to whichever function you use, and explicitly null-terminate yourself afterward rather than trusting the function to have done it for you. This question is really a proxy for "have you actually been bitten by a string-handling bug in production," and most candidates who haven't shipped real C code give an answer that sounds right but skips the null-termination trap entirely.

### What is undefined behavior, formally, and why doesn't the compiler just catch it?

Undefined behavior is anything the C standard explicitly declines to define the result of — not "an error," not "a crash," but a deliberate gap where the standard says the compiler is allowed to do *literally anything*, including producing code that appears to work, code that crashes, or code that behaves differently depending on optimization flags. The standard leaves these gaps on purpose, mostly for performance: if the compiler had to insert runtime checks for every potential overflow, null dereference, or out-of-bounds access, C would lose the exact "close to the metal, no hidden cost" property that makes it useful for systems and embedded work in the first place. The reason the compiler doesn't catch it for you at compile time is that most undefined behavior depends on runtime values the compiler can't fully know in advance — whether `arr[i]` is in bounds depends on what `i` actually is when the program runs, not just what the code says. The interview-correct framing: undefined behavior isn't a corner case to memorize a list of — it's the standard's way of saying "we trust you to not do this," and tools like sanitizers exist specifically to catch the trust violations a human reviewer would otherwise miss.

## Classic gotchas

### Why does `i = i++` have undefined behavior?

The C standard says that if a variable is modified more than once between two sequence points without a defined ordering between those modifications, the behavior is undefined — and `i = i++` does exactly that: the `++` post-increment modifies `i`, and the `=` assignment also modifies `i`, with the standard not guaranteeing which write happens last. In practice this means different compilers (or even the same compiler with different optimization flags) can produce different results — some leave `i` incremented, some leave it unchanged, and a strictly conforming compiler is allowed to do *anything*, including behavior that looks bizarre. The interview-correct answer isn't "it equals X" — it's that the question itself is a trap, and the right engineering response is to never write code like this: split it into `i++; ` or `i = i + 1;`, never combine `i++` with another assignment to `i` in the same expression.

### What's the difference between `++i` and `i++`?

Both increment `i` by 1, but they differ in what the *expression itself* evaluates to. `++i` (pre-increment) increments `i` first, then the expression evaluates to the new, already-incremented value. `i++` (post-increment) evaluates to the *original* value first, and the increment is applied after that value has been used in the surrounding expression.

<pre><code>int i = 5;
int a = ++i;  // i becomes 6, a = 6
int j = 5;
int b = j++;  // b = 5, j becomes 6</code></pre>

When the result isn't used at all (a bare `i++;` on its own line), there's no observable difference — but inside a larger expression, like `arr[i++] = x;` vs `arr[++i] = x;`, the choice changes which index actually gets written, which is exactly why interviewers like asking you to trace through a loop using one or the other.

### What happens when you access an array out of bounds?

C performs no bounds checking at all — `arr[10]` on a 5-element array doesn't raise an error, it just computes `*(arr + 10)` and reads or writes whatever happens to be at that address, which might be unrelated data, another variable's memory, the saved return address on the stack, or unmapped memory that triggers a segfault. This is undefined behavior, not a guaranteed crash, which is precisely what makes it dangerous: the program might run "fine" for years in testing and then corrupt data or crash unpredictably in production once memory layout shifts slightly. Writing past the end of a stack-allocated buffer is the classic mechanism behind buffer-overflow security vulnerabilities — overwriting adjacent stack memory (including, in the worst case, a saved return address) to redirect program execution, which is why bounds-respecting functions and tools like AddressSanitizer matter so much in real production C code, not just in interview answers.

### What happens if you call `free` on the same pointer twice, or use memory after freeing it?

A **double free** corrupts the heap allocator's internal bookkeeping structures (the metadata it uses to track which blocks are free and how big they are), and the result is undefined — it might crash immediately, corrupt unrelated allocations, or (in the worst case for security) be exploitable to redirect program behavior. **Use-after-free** means dereferencing a pointer after the memory it pointed to has been freed and potentially reused for something else entirely — you might read garbage, read another object's data that now occupies that address, or crash, depending on timing and what the allocator does with freed blocks. The defensive pattern for both: set a pointer to `NULL` immediately after freeing it, and never free a pointer you're not certain hasn't already been freed — tools like Valgrind and AddressSanitizer specifically flag both bug classes because they're common, hard to spot by code review alone, and frequently security-relevant.

### How does pass by value vs pass by reference work in C, using pointers?

C only has pass by value — when you call a function, the function receives a *copy* of whatever you pass, so modifying a parameter inside the function never affects the caller's original variable. C simulates "pass by reference" by passing a pointer: you're still passing the pointer *by value* (a copy of the address), but since that copy points at the same memory as the original variable, dereferencing it inside the function lets you modify the caller's actual data.

<pre><code>void byValue(int x) { x = 100; }       // caller's variable unchanged
void byPointer(int *x) { *x = 100; }   // caller's variable changes

int main(void) {
    int a = 5;
    byValue(a);
    printf("%d\n", a);     // still 5
    byPointer(&a);
    printf("%d\n", a);     // now 100
}</code></pre>

This is exactly why functions like `scanf` take addresses (`&variable`) rather than the variables themselves — there's no other way in C for a function to write a value back into a variable that lives in the caller's scope.

<div class="verdict"><strong>The core truth:</strong> C interviews are pointer-and-memory interviews. The candidate who can draw the memory layout and explain exactly what a pointer holds, where it lives, and when it stops being valid — not just recite syntax — passes every time.</div>

## A worked memory-trace question, the way an interviewer would actually run it

Most C interviews don't ask isolated trivia — they hand you a short snippet and say "walk me through what happens, line by line." Here's a representative one, with the kind of trace you'd want to produce out loud:

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

int *createArray(int size) {
    int *arr = malloc(size * sizeof(int));
    for (int i = 0; i < size; i++) {
        arr[i] = i * i;
    }
    return arr;
}

int main(void) {
    int *squares = createArray(5);
    printf("%d\n", squares[3]);
    free(squares);
    squares = NULL;
    return 0;
}</code></pre>

A strong answer narrates memory, not just output: "`createArray` allocates 5 ints — 20 bytes — on the heap with `malloc`, since this memory needs to outlive the function call; if it were a local array instead, it would be destroyed the instant `createArray` returns, and we'd hand the caller a dangling pointer." Then: "the loop fills it with squares — `arr[0]=0, arr[1]=1, arr[2]=4, arr[3]=9, arr[4]=16`. Back in `main`, `squares` now holds that heap address. `squares[3]` is `9`, so that's what prints. `free(squares)` releases the block, and setting `squares = NULL` immediately afterward is defensive — it means if any later code accidentally tries to use `squares` again, it crashes predictably on a null dereference instead of silently corrupting memory through a dangling pointer to memory that might since have been reused by another allocation." That last sentence — explaining *why* the defensive NULL assignment matters, not just that it's "good practice" — is usually the line that separates a pass from a borderline result, because it shows you understand the failure mode you're guarding against, not just the convention.

A weaker but common answer just says "it prints 9" and stops — technically correct, but it skips every signal the interviewer was actually trying to collect: do you understand heap lifetime, do you understand why `free` followed by reuse is dangerous, do you understand what "dangling" really means in terms of what's physically still sitting in that memory address. Interviewers will frequently follow up a bare "it prints 9" with "okay, now what happens if I call `printf("%d\n", squares[3])` again right after the `free`?" — testing whether you understand that the memory isn't wiped, it's just no longer guaranteed to be yours, so the read might "work" by accident or might print garbage, and either way it's undefined behavior you should never write deliberately.

## How GeeksforGeeks, LeetCode, and a senior's WhatsApp PDF stack up against actually rehearsing this out loud

Most C prep still happens the same three ways it did a decade ago, and each one solves a different 20% of the problem while leaving the rest for the interview room to expose.

**GeeksforGeeks and similar question-dump sites** are genuinely good at one thing: giving you a comprehensive list of "what could be asked" with a written answer underneath each one. The problem isn't the content — it's the format. Reading "a dangling pointer points to freed memory" and being able to *recognize* that sentence as correct is a wildly different skill from generating that explanation yourself, live, while an interviewer is staring at you waiting for the next sentence. Recognition-based studying creates a specific, dangerous illusion of mastery: you read the answer, nod, think "yeah, I knew that," and move on — without ever testing whether you could have produced it cold.

**LeetCode-style judges** are excellent for algorithmic coding rounds, but C interviews aren't really algorithm interviews — they're memory-model interviews. A judge tells you whether your code compiles and passes test cases; it has no opinion on whether you can explain *why* `i = i++` is undefined behavior, or narrate what's happening in the stack frame when a function returns a dangling pointer. That reasoning-out-loud skill is exactly what gets evaluated in the room, and it's exactly what a green checkmark from a judge cannot test.

**The senior's WhatsApp-forwarded PDF of "Top 50 C Questions"** is the most common prep artifact in campus placement season, and it's not useless — it's just frozen in time and one-directional. It tells you what got asked in *someone else's* interview, with no way to ask a follow-up, no way to check if your spoken explanation of pointer arithmetic actually makes sense to a listener, and zero feedback when you confidently get the `++i` vs `i++` distinction backwards. It's a study list, not a rehearsal partner.

**Greenroom's approach is different on the one axis that actually matters for C: it makes you say the answer out loud and then pushes back.** A spoken mock interview will ask you to explain why `arr[10]` on a 5-element array doesn't crash predictably, then follow up with "so what *is* at that memory address, then?" — the exact kind of probe a real interviewer uses to separate memorized definitions from real understanding. The honest tradeoff: Greenroom won't replace working through a textbook's worth of pointer exercises, and it isn't a place to learn C from zero. What it's for is the specific gap between "I've read the answer" and "I can produce the answer fluently while someone questions it" — which, for C specifically, is almost the entire interview.

## How to prepare

C rounds are explanation-heavy: "what does this print and why?" is the default format, and the interviewer is listening for whether you can trace memory state line by line, not whether you can recite a definition. Practise reasoning about pointers, the stack/heap split, and undefined behavior out loud — say what each line does to memory before you say what it prints. [Greenroom](/) runs spoken technical mock interviews that ask realistic follow-ups on exactly this kind of reasoning, not just whether your final answer matches a key. Pair it with our [C++](/blog/cpp-interview-questions) and [OS](/blog/operating-system-interview-questions) guides.

## Frequently asked questions

### What are the most common C interview questions?

Common C questions focus on pointers (pointer to a pointer, array vs pointer, dangling/wild/null pointers, pointer arithmetic, function pointers), memory management (malloc/calloc/realloc/free, stack vs heap, memory leaks, segmentation faults), storage classes (auto, static, extern, register), keywords like const and volatile, structures vs unions, and classic gotchas like undefined behavior in i = i++.

### What is the difference between an array and a pointer in C?

An array is a contiguous block of memory whose name refers to its first element, with a fixed size known at compile time; a pointer is a variable that stores an address and can be reassigned. Arrays decay to pointers in many contexts, but sizeof on an array gives the total bytes while sizeof on a pointer gives the pointer size, and you can't reassign an array name.

### What is the difference between malloc and calloc?

Both allocate memory on the heap. malloc(size) allocates a single block of the given number of bytes and leaves the contents uninitialized, while calloc(n, size) allocates memory for n elements of the given size and initializes all bytes to zero. calloc is slightly slower because of the zeroing but is convenient when you need zero-initialized memory.

### What is a dangling pointer and how is it different from a memory leak?

A dangling pointer still exists and points to memory that has already been freed or gone out of scope, so using it is undefined behavior even though the pointer variable itself looks fine. A memory leak is the opposite kind of mistake — memory that's still allocated but the pointer to it has been lost, so it can never be freed. One is "a pointer to memory that's gone," the other is "memory that's still there but unreachable."

### Why is C considered harder to interview for than higher-level languages?

Because C makes you manage memory and pointers explicitly, with no runtime safety net catching mistakes — every bug class that garbage-collected languages hide (use-after-free, buffer overruns, double frees) is something a C interview can ask you to reason about directly. Interviewers use C specifically because it reveals whether a candidate understands what's actually happening in memory, not just whether they can write code that compiles.

### How should I prepare for a C interview?

Master pointers and memory management, since C interviews center on them, and be ready to reason about memory layout and undefined behavior rather than just recite syntax. Practise explaining 'what does this print and why' out loud, ideally with a voice-based mock interview that follows up, because C rounds are explanation-heavy and verbal.

C interviews reward reasoning about pointers and memory out loud. Greenroom runs spoken technical interviews that follow up on your reasoning. Free to start.
