Interviews
Interview Questions About C: 10 Topics with Sample Answers
Practice interview questions about C, from pointers and memory to volatile and macros. Each of 10 topics has sample answers, pitfalls, and tips.
Interview Pilot Editorial Team
Updated November 1, 2026
20 min read

You're sharing your screen in a technical interview. The interviewer asks what a pointer stores, and you answer confidently: an address. Then they show a linked-list cleanup loop that frees a node before reading its next member. You hesitate, suggest setting the pointer to NULL, and realize that your definition doesn't explain how to recover the rest of the list.
That follow-up is where interview questions about C become less about vocabulary and more about engineering judgment. A strong candidate tracks which objects exist, which pointers still refer to them, and which operations the language permits. Getting the expected output isn't enough if the program reaches it through undefined behavior.
C's close relationship with systems programming helps explain that emphasis. Unix was rewritten largely in C in 1973, demonstrating that an operating system could be implemented in a higher-level language rather than almost entirely in assembly, as documented in the history of C). Portability and direct control remain useful context, but an interview answer needs to connect those ideas to concrete code.
Prepare to explain allocations, array decay, storage duration, and const before moving into sequencing, structure layout, hardware access, callbacks, linked lists, and macros. For each topic, practice the follow-up that changes an assumption. That's how you move from recognizing a familiar question to defending an answer when the interviewer changes the code.
1. Dynamic Memory Management and Pointer Lifetime Errors
The interviewer's probe: A dynamic array needs more capacity. Why might items = realloc(items, bytes) leak memory, and what happens to pointers into the old allocation?
What it tests: Ownership, allocation failure, and object lifetime, not just whether you remember the allocation functions.
A strong answer explains that malloc returns uninitialized storage, while calloc initializes the allocated bytes to zero. For a nonzero requested size, realloc may move the allocation; if it fails, the original allocation remains valid.
The answer that survives the follow-up
Use a temporary pointer: int *tmp = realloc(items, bytes);. Check tmp before assigning it to items, and validate the capacity calculation before multiplying by sizeof(*items). After successful reallocation, rebuild any interior pointers from indexes rather than retaining pointers into the old object.
The offer-losing answer: Setting the original pointer to NULL makes every alias safe. It doesn't. Other pointers can still refer to storage whose lifetime has ended.
- Linked-list cleanup: Save
node->nextbefore freeingnode. - Returned buffers: Don't return a pointer to an automatic local array.
- Failure paths: State who releases every successfully allocated object if a later operation fails.
Practical rule: Explain ownership before explaining cleanup. If nobody can name the owner, adding more calls to
freewon't fix the design.
Use AddressSanitizer or Valgrind and reproduce failures before fixing code. Connect allocation behavior to broader process-memory concepts through these operating system interview questions.
Keep track of the object behind each address, not just the pointer variable.

2. Pointers vs. Arrays and Array Decay
An interviewer writes void print(int a[]) and asks you to calculate the caller's array length inside the function. The trap is the parameter declaration, not the arithmetic.
An array is an object containing elements. A pointer is an object that stores a pointer value. In many expressions, an array expression converts to a pointer to its initial element, but that conversion doesn't make the underlying types interchangeable.
Explain what changes at the function boundary
Strong sample answer: In this parameter declaration, a is adjusted to pointer type. Consequently, sizeof(a) measures the pointer, not the caller's array. I'd pass the element count separately using a size_t parameter.
At the caller, sizeof(array) / sizeof(array[0]) works for an actual array object. Applying the same expression to a pointer doesn't recover the allocation's length.
- Writable string storage:
char s[] = "hello"creates an array initialized from the literal. - String-literal pointer:
const char *p = "hello"expresses that the characters mustn't be modified throughp. - API boundary: A buffer pointer needs a length or another explicit termination contract.
The wrong answer under pressure: Arrays are constant pointers. That shortcut fails when the interviewer asks about assignment, sizeof, or &array, whose type is a pointer to the whole array.
For a worked exercise, implement a function that searches an integer array. Explain why its parameters need both the starting address and the element count, and why a pointer alone cannot establish the accessible bounds.
The useful distinction is between a container and a reference to its contents.

3. Storage Classes auto, static, extern, and register
Can a variable disappear from scope without its object ceasing to exist? That follow-up exposes whether you've confused visibility with lifetime.
A function-local static int counter; has block scope, but its object has static storage duration. The name isn't accessible outside the block, while the stored value persists between calls. An ordinary automatic local object generally exists only for the corresponding block execution.
Strong sample answer: Scope describes where a name is visible, storage duration describes an object's lifetime, and linkage determines whether declarations identify the same entity. I'd explain those separately before interpreting any keyword.
Compare the declarations in real code
auto: In traditional explicit-type declarations, it specifies automatic storage duration for a block-scope object. Ask which language version the interview targets before discussing newer declaration forms.- Local
static: Retains state across calls, which can be useful for counters but introduces shared mutable state. - File-scope
static: Gives an object or function name internal linkage. extern: Commonly declares an object defined elsewhere; an initializer can make anexterndeclaration a definition.register: Doesn't force hardware-register allocation. In traditional C usage, it also restricts taking the declared object's address.
The answer that raises concern: Static means global. A static local object is a direct counterexample.
As a practical exercise, declare a shared status object with extern in a header and define it in a source file. Then make a helper function static and explain why callers in other translation units shouldn't depend on it.
Don't claim automatic variables always live on a stack. That's an implementation choice, not the definition of automatic storage duration.
4. const Pointer Qualifiers and Their Placement
Start with const int *p, then move const to produce int *const p. An interviewer who asks what assignments remain legal is testing declarator fluency and API design, not your ability to recite “read-only.”
For const int *p, you may change the pointer, but you can't modify the pointed-to integer through that pointer. For int *const p, the pointer itself can't be reassigned after initialization, but the pointed-to integer can be modified if the underlying object permits it.
Read outward from the identifier
Strong sample answer: In const int *const p, both restrictions apply. However, a pointer to const-qualified data doesn't necessarily point to an object originally defined as const; it restricts access through that pointer.
That distinction matters when an interviewer introduces another alias. If the actual object is mutable, another valid non-const pointer may modify it, and reads through p can observe the changed value.
- Read-only input: Use
const struct config *cfgwhen a function shouldn't modify configuration through its parameter. - Fixed pointer: Use a const-qualified pointer when reassignment would violate a local invariant.
- Lookup data: A
static constarray expresses persistent, read-only table data without exposing a mutable global interface.
Casting away
constchanges the access type. It doesn't make an object defined asconstwritable.
The offer-losing answer: A cast makes the write legal. Modifying an object defined with const-qualified type through a cast produces undefined behavior.
For practice, design a string-inspection function and explain why it accepts const char *. Then explain why top-level const on a pointer parameter doesn't prevent the caller from changing its own pointer.
5. Undefined Behavior and Sequence Points
The probe: What does i = i++ print?
The strongest response rejects the premise: this expression has undefined behavior in C because the modifications aren't properly sequenced. There isn't a portable output to calculate, and running it doesn't establish a language guarantee.
The interviewer may then ask whether undefined behavior always causes a segmentation fault. It doesn't. A program may appear to work, fail only with optimization enabled, or corrupt unrelated state.
Give a diagnosis, not a prediction
Strong sample answer: I'd separate the operations into statements with a clear intended order, then inspect surrounding code for similar sequencing problems. I'd compile with -Wall -Wextra -Wpedantic and use UndefinedBehaviorSanitizer, while recognizing that no diagnostic tool proves the absence of undefined behavior.
Distinguish the categories clearly:
- Undefined behavior: The language imposes no requirements on the execution.
- Unspecified behavior: The language permits alternatives without requiring the implementation to document its choice.
- Implementation-defined behavior: The implementation must document its selected behavior.
Signed integer overflow and out-of-bounds access belong in the same preparation session. For each, explain the precondition that makes the operation valid rather than merely naming the defect.
Toptal's C interview questions provide useful conceptual prompts, but your rehearsal should extend into reproductions, warnings, sanitizers, and repairs.
The wrong answer under pressure: My compiler prints the expected value, so it's safe. A compiler run is an observation about that execution, not evidence that the source has defined semantics.
6. Struct Padding, Alignment, and sizeof
An interviewer shows a structure containing char, int, and another char, then asks for its size. Don't guess a universal byte count. Ask about the target implementation or explain why the declaration alone doesn't establish a portable answer.
Compilers may insert padding between members to satisfy alignment requirements and at the end to support arrays of the structure. The resulting layout depends on the implementation's type sizes and alignment rules.
Strong sample answer: I'd inspect sizeof, offsetof, and the implementation's alignment facilities. Reordering members may reduce padding, but I'd first check whether the layout is externally visible or constrained by an interface.
The follow-up is usually about serialization
Can you write the structure directly to a network socket? Not as a portable wire format. Member layout, padding, byte order, and representation can differ between systems.
- Protocol messages: Encode fields explicitly into byte buffers.
- Memory inspection: Use
offsetofto examine member offsets rather than inferring them. - Packed structures: Treat compiler-specific packing as a deliberate trade-off, not a universal fix.
The offer-losing answer: sizeof(struct) is always the sum of its members. A related mistake is using memcmp as a general test of member-wise equality, since padding can differ.
If you propose member reordering for performance, measure it. An ACM study comparing compiler and language effects in HPC benchmarks found that compiler selection sometimes affected performance more than language choice.
For a cache-sensitive array of records, compare representative workloads using the same compiler, flags, and hardware. Preserve correctness tests alongside the benchmark.
7. The volatile Keyword
The wrong answer to eliminate immediately: volatile makes a variable thread-safe.
It doesn't provide atomic read-modify-write operations, establish interthread synchronization, or replace a mutex. The interviewer's revealing follow-up is whether a volatile shared counter can safely be incremented concurrently.
Separate hardware access from synchronization
Strong sample answer: Volatile-qualified accesses constrain compiler optimization according to the implementation's rules. They're relevant when values can change outside ordinary visible program flow, such as hardware registers, but shared-memory concurrency requires atomics or synchronization.
For a memory-mapped status register, firmware may repeatedly perform volatile reads until hardware signals readiness. The register's address, access width, and ordering requirements must still come from the platform's hardware contract.
- Thread communication: Use
_Atomic, facilities from<stdatomic.h>, or locks as appropriate. - Signal notification: A
volatile sig_atomic_tflag is a conventional constrained mechanism for communicating with a signal handler. - Read-only hardware state:
const volatilecan express a register that software reads but mustn't modify through that access path.
Interview distinction:
constlimits permitted modification through an access path.volatileconcerns observable accesses. Neither keyword means thread synchronization.
The visual reminder is useful: protecting an object's handling isn't the same as coordinating every actor that touches it.

Practice these distinctions with embedded systems interview questions, particularly register polling and interrupt-related state. Don't stop at selecting the keyword; explain what guarantees must come from the compiler, processor, and device documentation.
8. Function Pointers and Callbacks with qsort
The interviewer asks you to sort integers with qsort, then challenges your comparator with extreme values. If you returned a - b, signed overflow may invalidate the implementation.
A function pointer allows code to invoke behavior supplied by its caller. For qsort, the comparison callback receives pointers to elements through const void * parameters and must return a negative value, zero, or a positive value according to their ordering.
Defend the callback contract
Strong sample answer: I'd convert each argument to const int *, dereference it, and compare the values without subtraction. The expression (a > b) - (a < b) provides the required sign without overflowing from subtracting the original integers.
The generic pointer interface lets the sorting function handle different element types. It doesn't remove type obligations: the comparator must interpret the objects according to their actual element type.
- Element width: Pass
sizeof(*items)so the call stays consistent with the array's type. - Ordering: Keep comparisons consistent and avoid modifying elements inside the comparator.
- Equal keys: Don't assume
qsortpreserves their original relative order.
The answer that loses confidence: Any function becomes a valid callback if you cast its address. Casting a function pointer doesn't repair an incompatible function signature.
A useful follow-up asks you to sort an array of pointers to strings. Now the comparator arguments point to array elements that are themselves pointers, so another level of indirection is required.
Outside sorting, use the same reasoning for event handlers or a command-dispatch table. Explain the callback's signature, argument lifetime, and ownership contract before discussing declaration syntax.
9. Reversing a Singly Linked List
Reverse the links without allocating replacement nodes. That instruction tests whether you can preserve access to data while changing the structure that makes it reachable.
Use the diagram to trace the unreversed remainder before changing any link.

Strong sample answer: Maintain prev, curr, and next. Save curr->next into next, redirect curr->next to prev, advance prev to curr, and advance curr to next. When curr becomes null, return prev as the new head.
Explain the invariant aloud
The prefix ending at prev is already reversed, while curr identifies the untouched remainder. Saving the next pointer before changing the link preserves access to that remainder.
- Empty input: Return a null head without dereferencing it.
- Minimal lists: Trace a single node and a short chain by hand.
- Input contract: Clarify whether cycles are possible before assuming traversal terminates.
The offer-losing mistake: Overwriting curr->next before saving it, then trying to use that modified link to advance. You've redirected the path you still needed.
The iterative approach takes linear time and constant auxiliary space. A recursive version uses call-stack space proportional to the list length, which matters for long lists.
Watch the pointer updates, then reproduce them without the walkthrough.
Extend the exercise with coding interview questions. After reversal, verify reachability, termination, and cleanup rather than checking only the printed values.
10. Macros vs. Inline Functions and Preprocessor Pitfalls
A candidate fixes #define SQUARE(x) x * x by adding parentheses. The interviewer then supplies i++ as the argument. Parentheses repair precedence, not repeated evaluation.
Macros operate through preprocessing-token substitution. If a parameter appears repeatedly in the replacement, an argument with side effects may be evaluated repeatedly and can create undefined behavior.
Strong sample answer: For a typed arithmetic helper, I'd prefer a static inline function. Function arguments are evaluated before the body executes, and using the parameter repeatedly doesn't reevaluate the caller's expression.
Know what inline doesn't promise
The inline keyword doesn't guarantee machine-code substitution or faster execution. A compiler can inline functions without that keyword and decline to inline functions declared with it.
Use the tool that fits the requirement:
- Typed computation: Prefer a function for type checking and predictable argument evaluation.
- Conditional compilation: Use preprocessing directives for build-time feature selection.
- Token operations: Macros remain useful for stringification and token pasting.
- Constants: Consider an enumeration constant or a const-qualified object according to the required type and context.
The wrong answer under pressure: A fully parenthesized macro behaves exactly like a function. It still lacks function-call evaluation semantics and ordinary parameter type checking.
For a worked exercise, inspect the expansion of a square macro with gcc -E. Test an arithmetic expression as the argument, then a side-effecting expression, and explain why the failures have different causes.
Also review assert and NDEBUG. Never put essential program work inside an assertion expression, because disabling assertions mustn't remove behavior required for correctness.
How the 10 C interview topics compare by core concept, main pitfall, and what they test
| Item | Core Concept | Main Pitfall | What It Tests |
|---|---|---|---|
| Dynamic Memory Management and Pointer Lifetime Errors | Heap allocation, resizing, ownership, and freeing memory | Dangling pointers, use-after-free, double frees, leaks, or mishandled realloc failures | Safe allocation, ownership, and pointer lifetime reasoning |
| Pointers vs. Arrays and Array Decay | Arrays often convert to pointers, but remain distinct types | Using sizeof on an array parameter as if it retained the original array size | Memory layout, array decay, and function parameter behavior |
| Storage Classes: auto, static, extern, and register | Storage duration, scope, and linkage of variables | Confusing when an object exists with where its name is visible | Understanding variable lifetime, visibility, and cross-file declarations |
| const Pointer Qualifiers and Their Placement | const can qualify the pointed-to value, the pointer, or both | Misreading which part of a pointer declaration is immutable | C declarator fluency and const-correct API design |
| Undefined Behavior and Sequence Points | Sequencing rules and behaviors the C standard does not define | Assuming expressions with unsequenced conflicting accesses have predictable results | Distinguishing undefined, unspecified, and implementation-defined behavior |
| Struct Padding, Alignment, and sizeof | Alignment requirements can add padding within and after structs | Assuming struct size equals the sum of member sizes or is portable | Memory layout, alignment, and portability reasoning |
| The volatile Keyword | Volatile-qualified accesses may change outside visible code flow | Treating volatile as atomicity or thread synchronization | Distinguishing hardware-facing access constraints from concurrency tools |
| Function Pointers and Callbacks with qsort | Function pointers let generic code invoke caller-supplied behavior | Incorrect comparator logic, unsafe casts, or subtraction overflow | Callback declarations, generic programming, and comparator design |
| Reversing a Singly Linked List | Reverse node links in place using pointer updates | Losing the next node or mishandling empty and single-node lists | Pointer manipulation, edge cases, and time and space complexity |
| Macros vs. Inline Functions and Preprocessor Pitfalls | Textual macro expansion versus typed inline functions | Repeated argument evaluation, missing parentheses, and weak type checking | Preprocessor behavior and choosing safe reusable code constructs |
Turning These Ten Topics Into a Practice Plan
Pick two topics per session and practice them as explanations, implementations, and follow-ups. Start by writing the answer by hand without reference material. Then turn the answer into a small program whose behavior you can test.
For memory management, build a resizable buffer and deliberately exercise allocation-failure cleanup. For arrays, compare an actual array expression with an adjusted function parameter. For callbacks, test values that would expose comparator overflow instead of choosing only convenient inputs.
Make each session produce evidence
- State the contract: Describe valid inputs, ownership, lengths, and relevant language-version assumptions.
- Write the implementation: Keep the example small enough that you can explain every access and mutation.
- Test the boundary: Include empty inputs, capacity limits, invalid data where applicable, and cleanup after errors.
- Explain the pitfall: Say aloud what a plausible wrong answer overlooks and how your implementation avoids it.
Compile suitable exercises with warnings enabled and use AddressSanitizer and UndefinedBehaviorSanitizer. A practical development command can include -Wall -Wextra -Wpedantic -g -fsanitize=address,undefined, subject to toolchain support. Read diagnostics before changing the code, then rerun the failing input after the repair.
Don't equate passing tests with memory safety. An ACM evaluation of automated repair for real-world C vulnerabilities reported an 88.2% Pass@1 rate, while 87.3% of generated patches were memory-safe. The distinction matters: functional results and memory-safety validation answer different questions.
Rehearse the interviewer's next question
After solving an exercise, change an assumption. Ask what happens if reallocation moves the buffer, a callback receives pointer elements, or a list contains a cycle. This forces you to reason from the contract instead of recognizing the original prompt.
Record your spoken explanation and listen for unsupported shortcuts such as arrays are pointers, volatile is thread-safe, or static means global. Replace each shortcut with a precise statement and a small counterexample. If English isn't your first language, rehearse concise technical sentences rather than memorizing a long speech.
Our software engineering question sets at Interview Pilot cover programming, data structures, algorithms, debugging, and testing, so you can extend these C drills into a broader preparation schedule. Keep the emphasis on explaining your reasoning independently, and use guided practice to expose gaps rather than conceal them.
Finish each session by revisiting the question you handled least confidently. Write the ownership rule, language guarantee, or invariant you missed, then solve a changed version without looking at the original answer. You're ready when you can defend both the implementation and the reason the tempting alternative is wrong.
Interview Pilot offers guided AI mock interviews with answer evaluations and a searchable question bank for technical preparation. Visit Interview Pilot to rehearse your C explanations, practice follow-up questions, and build a broader software engineering interview routine.
Topics
C interview questions
C programming interview
C pointers interview
C coding interview
C interview prep
Continue reading

Interviews
What Is Technical Interview Questions Explained
Learn what is technical interview questions, the main types, how interviewers score answers, and a practical 2026 prep plan.
November 1, 2026
20 min read

Interviews
Sales Interview Pitch: How to Prepare and Deliver It
Prepare a sales interview pitch or presentation that wins the room: a five-slide structure, discovery questions, objection handling and practice tips.
November 1, 2026

Interviews
10 Tax Interview Questions You Should Prepare For
Master the 10 most common tax interview questions with sample answers, key topics to review, and tips for entry-level to senior tax roles.
October 31, 2026
17 min read