C Interview Questions
Commonly asked C interview questions on pointer arithmetic, malloc vs calloc, and struct padding.
A curated set of C interview questions, ordered roughly from fundamentals to more advanced — the kind you'll actually be asked in real screens and on-sites.
Pointers and arrays
Q: Explain pointer arithmetic — what does p + 1 actually do?
Adding 1 to a pointer doesn't advance by one byte; it advances by sizeof the pointed-to type. So for an int *p, p + 1 moves forward by sizeof(int) bytes (typically 4), landing on the next int in memory. This is exactly why p[i] and *(p + i) are equivalent — array indexing is defined in terms of pointer arithmetic.
Q: What does it mean that "arrays decay to pointers," and why does it matter?
When an array is passed to a function, it's automatically converted to a pointer to its first element, and the original length information is lost — sizeof on that parameter inside the function gives the size of a pointer, not the array. This is exactly why every C function that processes an array also needs a separate length parameter; the array itself can no longer tell you how long it is.
Memory management
Q: What's the difference between malloc and calloc?
Both request heap memory, but malloc(size) returns memory with unspecified (garbage) contents, while calloc(count, size) zero-initializes the entire block before returning it. Use calloc when you need predictable, zeroed initial state; use malloc (often marginally faster, since it skips the zeroing) when you're about to overwrite every byte yourself anyway.
Q: What's the difference between the stack and the heap in C?
The stack holds local variables and function call frames — allocation and cleanup are automatic and tied to scope, extremely fast, but limited in size and gone the moment the function returns. The heap holds memory explicitly requested with malloc/calloc/realloc — it persists until explicitly freed, is far larger, but requires the programmer to manage its lifetime manually, with no automatic cleanup at all.
Q: What is a dangling pointer, and how is it different from a memory leak? A dangling pointer still holds the address of memory that has already been freed (or of a stack variable that has gone out of scope) — dereferencing it is undefined behavior. A memory leak is the opposite problem: memory that's still allocated but for which every pointer to it has been lost, so it can never be freed — the process's memory usage just grows until it exits. One is "freed too early and still referenced"; the other is "never freed at all."
Structs and layout
Q: What is struct padding, and why does the compiler add it?
Compilers insert unused padding bytes between struct members so each member starts at an address satisfying its own alignment requirement (e.g., a 4-byte int typically must start at an address divisible by 4) — misaligned access is slow or, on some architectures, a hardware fault. This is why sizeof a struct is often larger than the sum of its members' individual sizes, and why reordering fields (grouping same-sized types together, largest to smallest) can sometimes shrink a struct's total size.
Q: How would you make a struct's exact memory layout predictable, with no padding?
Using a compiler-specific directive like GCC/Clang's __attribute__((packed)) (or MSVC's #pragma pack) forces the compiler to omit padding between members, which is common when a struct's layout must exactly match a wire protocol or on-disk file format — at the cost of potentially slower, unaligned member access, so it's used only where that layout compatibility is actually required.
File I/O and build process
Q: Why is while (!feof(fp)) considered a bug when reading a file line by line?
feof() only becomes true after a read has already been attempted and has already failed to find more data — it isn't a look-ahead check you can use to decide whether to read again. Looping on it runs the body one extra time after the real content is exhausted, typically reprocessing the last line or a garbage empty read. The correct pattern loops on the read function's own return value (fgets(...) != NULL) and checks feof/ferror only afterward, to find out why the loop actually ended.
Q: What's the difference between a declaration and a definition in C, and why does header-based project structure depend on that distinction?
A declaration states a function or variable's type/signature so the compiler can check calls against it, without providing actual code or storage; a definition is the real implementation or storage, and must exist exactly once across the whole linked program. Header files exist to safely share declarations across many .c files, while each corresponding definition lives in exactly one .c file — this is what lets separate source files call each other's functions without duplicating code or triggering a "multiple definition" error at link time.
Q: Why does splitting a build into separate compilation and a final linking step matter for anything beyond a toy project?
Compiling each .c file to its own object file independently means a build tool like make can recompile only the files that actually changed (or whose header dependencies changed) since the last build, reusing the already-built object files for everything else, then relinking. On a project with many files, that turns a full rebuild into a fast, incremental one for typical day-to-day changes.
Q: What does the -g compiler flag do, and why is -O0 usually paired with it while actively debugging?
-g embeds debug symbols — a mapping from compiled machine instructions back to source lines, function names, and variable names — into the binary, without changing the program's actual logic; this is what lets a debugger like gdb show meaningful source and variable names instead of raw addresses. -O0 disables compiler optimizations that would otherwise reorder, inline, or eliminate code, which can make stepping through a program in a debugger confusing if the executed order no longer matches the source's visual order.