Menu

Earn Premium with Referrals

Invite your friends and earn Premium rewards through our referral program.

See how it works and start inviting friends.

Top 10 - Part 1
C

Top 10 - Part 1

Practice the 10 most important and frequently asked C programming interview questions with a focus on core concepts.

1. What happens when calling free(ptr) on a dynamically allocated memory pointer in C?

Answer: The memory block is returned to the heap allocator and marked reusable, but ptr itself is unchanged — it keeps the address and becomes a dangling pointer.

free(ptr) tells the allocator that the memory at ptr may be reused. Two things it does not do:

  • It does not zero the memory or set ptr to NULL.
  • It does not retroactively invalidate other pointers that happen to point at the same address.

So after free, ptr still holds the old address, but the memory it points to is no longer yours. Using ptr afterward (reading or writing) is undefined behavior — that’s the classic dangling pointer. (Also: freeing the same block twice is a double-free, another UB.)

Good practice: set ptr = NULL immediately after free so the pointer is obviously invalid. The interview answer: free returns the block to the allocator but leaves ptr holding the stale address — a dangling pointer; dereferencing it is UB.

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

Answer: malloc(size) takes a single size and leaves memory uninitialized; calloc(num, size) takes count and element-size and zero-initializes every byte.

  • malloc(n) — allocates n bytes, uninitialized. The contents are garbage (whatever was in memory). Also does not check for overflow if you multiply sizes yourself.
  • calloc(n, size) — allocates n × size bytes and sets every byte to 0. It also has built-in overflow protection on the multiplication (returns NULL on overflow).
int *a = malloc(10 * sizeof(int));   // 10 ints, junk values
int *b = calloc(10, sizeof(int));    // 10 ints, all zeros

Both allocate from the heap and return void*. The zeroing costs a little time but can prevent uninitialized-memory bugs and is required for things like arrays you’ll partially fill. The interview answer: malloc takes one size and leaves memory uninitialized; calloc takes count and element size and zeroes all bytes (with overflow-safe multiplication).

3. According to the C standard, what is the effect of modifying a string literal via a pointer, such as char *str = "Hello"; str[0] = 'h';?

Answer: Undefined behavior — string literals live in read-only memory (like .rodata), and writing through a pointer to them is not allowed.

In C, string literals are stored in read-only memory segments (in typical implementations). The declaration char *str = "Hello"; makes str point at that read-only storage. Attempting str[0] = 'h'; tries to write to read-only memory:

  • Per the C standard, modifying a string literal is undefined behavior (the standard doesn’t even promise the memory is writable).
  • In practice, this usually manifests as a segmentation fault (SIGSEGV).

The safe pattern is to copy the literal into writable storage first: char str[] = "Hello"; — a real array initialized from the literal, safe to modify. The interview answer: modifying a string literal through a pointer is UB — the literal sits in read-only memory, typically crashing with SIGSEGV.

4. What is the result of evaluating sizeof(arr) versus sizeof(ptr) where int arr[10]; int *ptr = arr; on a 64-bit architecture?

Answer: sizeof(arr) is 40 bytes (the whole array); sizeof(ptr) is 8 bytes (a pointer).

The key fact: arr keeps its array type in the scope where it’s declared. So sizeof(arr) gives the total byte size of the array: 10 × sizeof(int) = 10 × 4 = 40 bytes.

ptr is a plain pointer variable holding an address. Its size is the platform’s pointer size: 8 bytes on 64-bit.

The trap: if arr is passed to a function, it decays to a pointer, and sizeof inside the function gives 8, not 40. But in the declaring scope, sizeof(arr) is the array’s full size. The interview answer: sizeof(arr) = 40 (entire array), sizeof(ptr) = 8 (pointer on 64-bit).

5. What is the purpose of the volatile keyword in C variable declarations?

Answer: It tells the compiler the variable can change outside the program’s control, forcing a fresh memory read/write on every access instead of caching.

volatile prevents the compiler from assuming it knows the variable’s value. Without it, the compiler may keep a copy in a register or optimize away repeated reads (since “nothing else could change it”). With volatile, every access goes to the actual memory location.

Why it matters — the value genuinely changes from the program’s perspective:

  • Memory-mapped hardware registers (device I/O).
  • Variables modified by signal handlers.
  • Shared state updated by another thread or by interrupt-driven code.
volatile uint8_t *status = (volatile uint8_t *)0x4000;  // hardware register
while (*status & BUSY) { /* poll */ }   // must re-read every iteration

Note: volatile is about reads and writes not being optimized away — it is not about atomicity or thread-safety. For concurrent threads you still need synchronization. The interview answer: volatile forces direct memory access on every read/write, preventing the compiler from caching or eliding accesses to values that change externally.

6. What happens when an array parameter is passed to a function, as in void foo(int arr[10])?

Answer: The parameter decays to a pointer — it becomes int *arr, and the size information is lost inside foo.

Array parameters in function signatures are adjusted by the compiler to pointers. void foo(int arr[10]) is exactly equivalent to void foo(int *arr). Consequences:

  • sizeof(arr) inside foo returns the pointer size (8 on 64-bit), not 40.
  • arr is an address; the function has no idea how many elements exist.
  • You must pass the length separately (or use a sentinel) to know the array’s extent.
void foo(int arr[], int n) {   // arr[] also decays to int*
    for (int i = 0; i < n; ++i) { /* ... */ }
}

This is the fundamental reason C functions that take arrays always take a size parameter too. The interview answer: array parameters decay to pointers (int arr[10] becomes int *arr), so size info is lost inside the function.

7. What will be the output of the following integer evaluation due to Sequence Point / Evaluation rules?

int i = 5;
int val = i++ + ++i;

Output: Undefined behavior (the program has no defined result).

The expression i++ + ++i modifies i twice in the same full expression, with no sequence point ordering the two modifications. The C standard says: if a scalar is modified more than once (or modified and read) between two sequence points, the behavior is undefined. So:

  • The result isn’t “12” or “13” — it could be anything.
  • The compiler may evaluate in any order; there’s no guaranteed value.

This is the classic C trap: don’t use a variable more than once in an expression where it’s being modified. The interview answer: undefined behaviori is modified twice without an intervening sequence point.

8. What does the static keyword mean when applied to a global variable declared outside any function?

Answer: It gives the variable internal linkage — its scope is restricted to the translation unit (source file) where it’s declared.

At file scope, static changes linkage, not storage (a file-scope variable is already static-storage-duration). With static, the variable’s name is only visible within its own translation unit; it won’t collide with same-named symbols in other source files at link time.

// in file1.c
static int counter = 0;   // only file1.c sees this
  • Without static: extern linkage — the name is visible program-wide (though const globals have internal linkage by default in C++ but not C).
  • With static: internal linkage — private to the file. This is the standard way to make file-local state and helper globals that shouldn’t leak into the global namespace.

Note this is different from static inside a function, which means “initialized once, persists between calls.” The interview answer: at global scope, static restricts the variable’s linkage to its own translation unit (file), preventing external symbol collisions.

9. What does realloc(ptr, 0) do according to C standard implementations when ptr is a valid non-null pointer?

Answer: It’s implementation-defined — either frees the memory (returning NULL) or returns a non-dereferenceable pointer that must still be freed.

Passing 0 as the new size to realloc is a murky corner of the standard. Implementations may:

  • Free ptr and return NULL (common, feels like free).
  • Return a unique pointer to zero-size memory that you must still call free on (some platforms, e.g. historical glibc behavior).

Because the two behaviors differ (one lets you free the result, the other means you already lost the block and free(NULL) is fine — but if you ignore the returned pointer you could leak or double-free), the standard calls it implementation-defined. The C committee’s guidance in C11/C17: don’t use realloc(ptr, 0) — it’s ambiguous and its semantics were a known defect. Use free(ptr) to release, and realloc only with a positive size.

The interview answer: implementation-defined — either frees and returns NULL, or returns a non-dereferenceable pointer that still needs free; it should be avoided.

10. What is a Memory Leak in C programming?

Answer: Allocating heap memory (via malloc/calloc/realloc) and losing the last reference to it without calling free, so it can never be reclaimed until the program exits.

A memory leak happens when you allocate memory, then drop the pointer to it (overwrite the pointer, lose it in an error path, let it go out of scope) before free is called. The block is still allocated — it consumes the process’s address space — but there’s no way to reach it to free it. Symptoms:

  • Program’s memory usage grows monotonically over time.
  • Long-running processes (servers, daemons) eventually exhaust memory and crash.

C has no garbage collector and no RAII — you must pair every malloc with a free on all code paths, including error paths. Tools like Valgrind and ASan detect leaks.

Note the distinction from the distractors: an out-of-bounds array access is a buffer overflow, reading an uninitialized local is uninitialized use, dereferencing NULL is a null dereference — none of those are leaks. The interview answer: heap memory allocated and never freed while its last pointer reference is lost, leaking address space until the program ends.

My Private Notes

Notes are auto-saved locally to this device.