30 C Interview Questions and 15 Lab Viva Questions

These C interview questions focus on reasoning, not guessing compiler output. The page contains exactly 30 interview questions (Q1–Q30) and 15 additional lab viva prompts (V1–V15) with answers. The language baseline is hosted C17 unless a target-specific detail is explicitly identified. Study an answer, explain its preconditions, and test the defined examples.

C Interview Questions — conceptual cover illustration

Viva preparation stays in the lab-practice section rather than creating a second page with duplicate interview answers. For hardware-focused preparation, use embedded C interviews; for self-assessment, try 100 C MCQs.

Language and behavior

Q1. What does a hosted C program start with?

The host calls main; use int main(void) when no arguments are needed, or a standard argument-taking form. Freestanding targets such as firmware can define different startup conventions. Header inclusion supplies declarations, not executable startup code.

Q2. How do undefined, unspecified and implementation-defined behavior differ?

Undefined behavior imposes no requirements on the implementation, as with out-of-bounds access or signed integer overflow. Unspecified behavior permits multiple choices without requiring documentation, such as the order of independent function-argument evaluations. Implementation-defined behavior is a documented implementation choice, such as whether plain char is signed.

Q3. Are int and a byte always 32 and 8 bits?

No. sizeof(char) is 1 by definition, while CHAR_BIT gives the number of bits in a C byte and is at least 8. int has a minimum required range, not a universal width. Inspect limits.h and use available fixed-width types only when their existence and range match the target.

Q4. How do signed and unsigned arithmetic differ?

Unsigned arithmetic wraps modulo one more than the largest representable value of its type. An unrepresentable signed arithmetic result is generally undefined behavior. Integer promotions and usual arithmetic conversions happen before the operation, so declaring a small unsigned object does not mean every expression is evaluated in that small type.

Q5. Does C evaluate function arguments left to right?

Not as a general language guarantee. Do not predict an output from expressions that modify and use the same scalar without the required sequencing. Split side effects into separate statements; a particular compiler run is not proof that undefined behavior has a portable result.

Functions, scope and linkage

Q6. Is C pass-by-reference?

C passes arguments by value, including pointer values. A copied pointer can be used to modify a caller-owned object, but assigning a new address to the local pointer does not replace the caller’s pointer. Use a pointer-to-pointer or return the new pointer when needed.

Q7. What is a function prototype for?

A prototype declares a function’s return and parameter types so calls can be checked. Under the C17 baseline used here, int f(void) declares no parameters, whereas int f() is not a prototype specifying no parameters. Include the proper header before calling a standard-library function.

Q8. What does static mean at block scope and file scope?

A block-scope static object has static storage duration but retains block scope and no linkage. A file-scope static object or function has internal linkage. Storage duration, scope and linkage are different concepts; physical stack and data segments are implementation details.

Q9. What does extern do?

An extern declaration can name an object or function defined elsewhere, subject to the linkage rules. It does not magically expose a different translation unit’s internal-linkage static object. Put compatible declarations in a header and normally one external object definition in a source file.

Q10. When would you use a function pointer?

A function pointer selects a function through a compatible function type, for example a callback or qsort comparator. Calling through an incompatible function-pointer type is not a safe cast-based workaround. Keep the signature and object lifetime of callback context explicit.

Arrays, strings and pointers

Q11. How are an array and a pointer different?

An array contains elements; a pointer is a separate object that can point to an element. Many array expressions convert to a pointer to the first element, but sizeof on an actual non-VLA array measures the whole array. An array parameter adjusts to a pointer, so pass the length separately.

Q12. Where may pointer arithmetic go?

Within an array object, pointer arithmetic may produce a pointer to an element or one past the last element. The one-past pointer is useful for iteration but must not be dereferenced. Subtraction is defined for pointers into the same array when the result is representable in ptrdiff_t; arbitrary address subtraction is not portable.

Q13. What do const int *p and int * const p mean?

The first prevents modification of the pointed-to int through p, but p itself can change. The second fixes the pointer value while allowing writes to a mutable target. A pointer-to-const does not imply the underlying object can never change through another valid alias.

Q14. What is a dangling pointer?

Its referent’s lifetime has ended, such as after free or after an automatic local object goes out of scope. Dereferencing it is undefined, not a guaranteed crash. Assigning one owner pointer to NULL after free does not repair other aliases; ownership and lifetime design matter.

Q15. How does a C string end?

A C string is terminated by the first null character. A buffer holding n visible characters needs at least n + 1 char elements, and strlen counts preceding characters, not allocated capacity. A string literal must not be modified; use a writable array when changes are needed.

Dynamic memory and representation

Q16. What differs between malloc and calloc?

malloc reserves storage with indeterminate contents; initialize before reading. calloc reserves an array’s storage and initializes all bits to zero. All-bits-zero is not a universal promise of a null pointer or floating-point zero representation. Check failure and multiplication bounds when computing allocation sizes.

Q17. How do you use realloc without losing ownership?

For a nonzero requested size, assign the result to a temporary pointer. If realloc fails, the old allocation remains valid; if it succeeds, use the new pointer and do not reuse aliases to the old allocation. Initialize any newly added elements, and avoid teaching zero-size realloc as a portable ownership pattern.

Q18. What are memory leaks and double frees?

A leak loses or neglects ownership of allocated storage without releasing it. A double free releases the same allocation twice and has undefined behavior. Pair allocation with a clear cleanup owner; free(NULL) is harmless, but an invalid non-null pointer is not safe.

Q19. Why can sizeof a struct exceed the sum of its members?

Implementations may add padding between members and at the end for alignment. Exact offsets depend on the ABI; inspect sizeof, _Alignof and offsetof rather than claiming a universal layout. Bytewise comparison or raw file writes of a struct can include padding and are not a portable serialization scheme.

Q20. How is a union different from a struct?

Union members overlap storage; its size is sufficient for its largest member and may include padding for alignment. A struct provides storage for each member. Prefer a tag recording the active variant; reinterpreting a different union member has representation and possible trap-value caveats, so it is not a portable numeric conversion.

Expressions and preprocessing

Q21. How do typedef and define differ?

typedef introduces a type alias understood by the compiler and subject to scope rules. A macro performs preprocessing token replacement. With typedef int *IntPtr, both IntPtr a and IntPtr b are pointers; the declaration produced by a macro expanding to int * can leave a second comma-separated declarator as an int.

Q22. What is risky about function-like macros?

Missing parentheses can change precedence, and repeated parameter expansion can evaluate an argument multiple times. Parentheses do not fix repeated side effects. Prefer an ordinary or static inline function when type requirements and single evaluation are important.

Q23. What is the difference between logical and bitwise operators?

&& and || test truth, return 0 or 1 and short-circuit the right operand. & and | combine integer bits after conversions and do not offer logical short-circuiting. A pointer guard should use p != NULL && *p == value, not a bitwise operator.

Q24. What makes a shift expression unsafe?

A negative shift count or a count at least the width of the promoted left operand is undefined. Signed left-shift rules add further restrictions, and right shift of a negative signed value is implementation-defined in C17. Prefer appropriately sized unsigned values for masks and validate the bit position.

Q25. Does volatile synchronize threads?

No. volatile affects accesses according to the implementation’s rules; it is neither mutual exclusion nor a general memory barrier or atomicity guarantee. Use C atomics where supported or the platform’s synchronization primitives. Hardware-register access additionally depends on the target’s memory map and compiler contract.

I/O, algorithms and debugging

Q26. Why must scanf results be checked?

The return value reports how many conversions succeeded, or EOF for an input failure before a conversion. A failed conversion can leave a variable unchanged and the offending input pending. For robust numeric input, reading a line then using strtol with end-pointer and range checks is often clearer; scanf numeric overflow is not solved just by checking its return value.

Q27. Why store getchar or fgetc results in int?

They return an unsigned-char value converted to int or the EOF indicator. Storing the result in char before testing EOF can lose the distinction on ordinary targets. Compare with EOF before converting and also check ferror to distinguish a read error from normal end-of-file.

Q28. What is a safe qsort comparator for integers?

Read each value through the appropriate pointer type and return (a > b) - (a < b). Returning a - b can overflow for extreme integers. The library qsort interface does not promise stability or a particular internal sorting algorithm.

Use it for repeated lookups on a sorted random-access array when maintaining the ordering is justified. Document whether duplicates return any, first or last match. Sorting an unsorted array just to answer one query may cost more than a linear scan.

Q30. How do you investigate a C crash?

Reproduce with the exact input and compiler flags, enable warnings, and reduce the failing case. A debugger and supported AddressSanitizer/UndefinedBehaviorSanitizer builds can reveal invalid accesses or arithmetic. These tools do not prove all behavior correct, and SIGSEGV or SIGFPE are platform outcomes, not required results of undefined behavior.

Defined-output practice programs

Value copies versus writes through pointers

#include <stdio.h>
static void swap_values(int a, int b) { int t = a; a = b; b = t; }
static void swap_objects(int *a, int *b) { int t = *a; *a = *b; *b = t; }
int main(void) {
    int a = 2, b = 9;
    swap_values(a, b);
    printf("After value call: %d %d\n", a, b);
    swap_objects(&a, &b);
    printf("After pointer call: %d %d\n", a, b);
    return 0;
}

Run value and pointer argument demonstration online

Expected output

Reference output for the displayed program, not a live execution result.

After value call: 2 9
After pointer call: 9 2

Both calls pass arguments by value. Only the second call modifies the caller’s objects. This is not an example of a special pass-by-reference mechanism.

Grow an owned allocation safely

#include <stdio.h>
#include <stdlib.h>
int main(void) {
    size_t old_count = 3, new_count = 5;
    int *values = malloc(old_count * sizeof *values);
    if (values == NULL) return 1;
    for (size_t i = 0; i < old_count; ++i) values[i] = (int)i + 1;
    int *resized = realloc(values, new_count * sizeof *values);
    if (resized == NULL) { free(values); return 1; }
    values = resized;
    for (size_t i = old_count; i < new_count; ++i) values[i] = (int)i + 1;
    for (size_t i = 0; i < new_count; ++i) printf("%s%d", i ? " " : "", values[i]);
    putchar('\n');
    free(values);
    return 0;
}

Run safe allocation growth online

Expected output

Reference output for the displayed program, not a live execution result.

1 2 3 4 5

The two counts are deliberately small fixed values, so their byte sizes are representable. A reusable input-driven allocator must check new_count <= SIZE_MAX / sizeof *values before multiplying. The reference output assumes successful allocations; either failure exits with status 1. Extra bytes from realloc are not initialized by realloc, so the program initializes them before reading.

Lab viva practice

These are short oral tasks about running, tracing and testing a lab program, not another claim of 15 advanced interview concepts.

V1. Explain your program before running it.

State input, output and the invariant your loop maintains. For a sum loop, after processing k inputs the accumulator represents the first k values; also explain the chosen overflow policy.

V2. What changes if you remove the newline in Hello World?

The message characters are unchanged except for the missing newline, but terminal layout and buffered-output visibility may differ. A newline is not a universal immediate-flush guarantee for every stream.

V3. Demonstrate an off-by-one error without executing invalid memory access.

For int a[5], list valid indices 0 through 4 and identify a loop condition i <= 5 as wrong before running it. Rewrite the bound as i < 5 and test one and five elements.

V4. How would you test an empty array function?

Pass a null pointer with count zero only when the function explicitly promises not to access it. Do not declare int a[0] in standard C17 or assume an arbitrary library function permits NULL even for a zero count.

V5. Show why a value-only swap does not change the caller.

Run the complete swap demonstration in the defined-output practice section. The first function exchanges only parameter copies; the second writes through copied pointers to the caller’s objects.

V6. Design factorial boundary tests.

Test zero, one, a typical value, rejected negatives, the largest result supported by the selected integer type and the next value. Check multiplication before it overflows; do not assume every int is 32 bits.

V7. Choose leap-year tests that expose a wrong rule.

Use 1900 and 2100 (common), 2000 and 2400 (leap), plus a normal multiple of four. A divisibility-by-four-only solution fails at the non-400 century boundaries.

V8. Why test a prime function with a perfect square?

The divisor bound must include the square root: 49 is composite due to 7. Also test 0, 1, 2 and an ordinary prime, and prefer i <= n / i to avoid squaring overflow.

V9. Trace a sorting pass out loud.

For insertion sort on 3 1 2, insert 1 into the first-element prefix to get 1 3 2, then insert 2 to get 1 2 3. Distinguish an element shift from a swap.

V10. How can you demonstrate sorting stability?

Attach labels to equal keys and compare their relative order after sorting by key. Sorted integers alone cannot establish stability because equal integers do not carry visible identity.

Search for 4 in 2 4 4 7. A first-match contract requires index 1; returning 2 might be correct for an any-match contract but not this one. Also test absent and endpoint targets.

V12. How do you handle bad numeric input?

Specify accepted syntax and range, then test empty input, trailing letters, extra numbers and out-of-range values. If using line parsing, check complete consumption and library range errors, not only whether a conversion started.

V13. What evidence distinguishes a compile error from a runtime failure?

A compile error prevents a new executable from being produced; a runtime failure occurs after a successful build begins executing. Remove or clearly identify stale executables so an old run is not mistaken for the new source.

V14. What cleanup belongs on an error path?

Release each owned allocation or open file exactly once, and preserve the original error. Draw an ownership table for resources acquired before the failing step; avoid freeing borrowed pointers.

V15. How would you defend a claimed output in a viva?

First establish that the code has defined behavior under stated type and platform assumptions, then trace it and compare with a test. For undefined behavior the answer is not a fixed number, even if one compiler printed it.

Continue practicing

Use program structure, binary search and the sorting comparison to rehearse explanations with complete programs. Run C in VS Code when you need a local terminal and debugger. No PDF download or certification is promised.

Related learn