Menu
CoddyTech

C Interview Questions and Answers

C questions on pointers, arrays, memory, structs and the preprocessor, plus output questions. Programs were run with GCC 14 on Linux with musl, and answers note where glibc, MSVC or the standard differ.

92 questions27 output quizzesRunnable code checked on C17 (GCC 14)By Kevin Spektor, Co-founder & CTO

C interview questions for freshers

The basics from campus placements and first rounds: building a program, data types, storage classes and functions.

What is C, and why is it still used?

Fresherbasics

C is a small, compiled, statically typed procedural language with direct access to memory through pointers. It runs kernels, embedded firmware, databases and language runtimes, where speed and predictability matter, and has no garbage collector, exceptions or bounds checking.

Follow-up: is C object oriented? No; structs and function pointers can imitate objects, but there are no classes.

What are the stages of compiling a C program?

Fresherbasics

Four stages: preprocessing (gcc -E expands #include and macros), compilation (gcc -S turns C into assembly), assembly (gcc -c makes an object file) and linking (object files and libraries into an executable).

"Undefined reference to sqrt" is a linker error: math.h declared the function, so link the library with -lm. On musl (this page's runner) the math functions live in libc itself.

What are the basic data types in C and their sizes?

Fresherdata types

char, short, int, long, long long, float, double, long double, and _Bool (C99). The standard fixes only minimum ranges and sizeof(char) == 1; actual sizes depend on the platform.

C
#include <stdio.h>

int main(void) {
    printf("char:      %zu\n", sizeof(char));
    printf("short:     %zu\n", sizeof(short));
    printf("int:       %zu\n", sizeof(int));
    printf("long:      %zu\n", sizeof(long));
    printf("long long: %zu\n", sizeof(long long));
    printf("float:     %zu\n", sizeof(float));
    printf("double:    %zu\n", sizeof(double));
    printf("pointer:   %zu\n", sizeof(void *));
    return 0;
}

Here long is 8 bytes; on 64-bit Windows it is 4. When width matters, use int32_t and int64_t. See C data types.

What is the difference between a declaration and a definition in C?

Fresherbasicsfunctions

A declaration gives a name and its type; a definition also creates the thing (storage for a variable, the body of a function). Declare as often as you like, define once in the whole program.

extern int count;          /* declaration: count lives in another file */
int count = 0;             /* definition: storage is allocated here */

int add(int a, int b);     /* declaration (prototype) */
int add(int a, int b) {    /* definition */
    return a + b;
}

So declarations go in headers; a global defined in a header gives "multiple definition" link errors.

What are the storage classes in C?

Fresherstorage classes

A storage class sets a variable's lifetime, scope and linkage: auto, register, static and extern, plus _Thread_local since C11. auto (the default for locals) lives for the block and starts indeterminate; register is the same but its address cannot be taken. A static local lives for the whole program and starts at 0; a file-level static is also limited to its file. extern declares an object defined elsewhere.

What does the static keyword do in C?

Fresherstorage classes

Inside a function, static makes a local keep its value between calls, initialized once. At file level it gives internal linkage, so other files cannot see the name.

C
#include <stdio.h>

static int calls_total = 0;   /* visible only in this file */

int next_id(void) {
    static int id = 100;      /* initialized once, not on every call */
    calls_total++;
    return id++;
}

int main(void) {
    printf("%d\n", next_id());
    printf("%d\n", next_id());
    printf("%d\n", next_id());
    printf("calls: %d\n", calls_total);
    return 0;
}

It prints 100, 101, 102 and calls: 3.

Does C support call by reference?

Fresherfunctionspointers

No. C passes every argument by value. To let a function change the caller's variable, pass its address; the pointer itself is still copied.

C
#include <stdio.h>

void swap_copy(int a, int b) { int t = a; a = b; b = t; }

void swap_ptr(int *a, int *b) { int t = *a; *a = *b; *b = t; }

int main(void) {
    int x = 1, y = 2;
    swap_copy(x, y);
    printf("after swap_copy: %d %d\n", x, y);
    swap_ptr(&x, &y);
    printf("after swap_ptr:  %d %d\n", x, y);
    return 0;
}

swap_copy leaves 1 2; swap_ptr gives 2 1. An array argument decays to a pointer, and that pointer is copied too.

What is recursion? Write a recursive factorial in C.

Fresherfunctions

Recursion is a function calling itself on a smaller input until a base case it answers directly. Each call takes a stack frame, so very deep recursion overflows the stack.

C
#include <stdio.h>

unsigned long long factorial(unsigned int n) {
    if (n <= 1) return 1;          /* base case */
    return n * factorial(n - 1);   /* smaller problem */
}

int main(void) {
    for (unsigned int i = 0; i <= 20; i += 5)
        printf("%u! = %llu\n", i, factorial(i));
    return 0;
}

20! is the largest factorial that fits in 64 unsigned bits. See the recursion visualizer.

What is a header file, and what is the difference between #include <file.h> and #include "file.h"?

Fresherpreprocessorbasics

A header holds declarations shared by several .c files, and #include pastes its text in. <stdio.h> searches the system directories; "list.h" searches the current file's directory first (the exact order is implementation-defined; this is GCC and Clang). Headers should define no functions or globals except static inline functions, and need an include guard.

What does this print: integer division and % with negative numbers?

Fresheroperators
C
#include <stdio.h>

int main(void) {
    printf("%d %d %d %d\n", 7 / 2, -7 / 2, 7 % 2, -7 % 2);
    return 0;
}

Predict the output

It prints 3 -3 1 -1. Since C99, integer division truncates toward zero, and % takes the sign of the left operand so that (a / b) * b + a % b == a. So test for odd with n % 2 != 0; n % 2 == 1 is false for negative odd numbers.

What does this print: i++ and ++i in separate statements?

Fresheroperators
C
#include <stdio.h>

int main(void) {
    int i = 5;
    int a = i++;
    int b = ++i;
    printf("%d %d %d\n", a, b, i);
    return 0;
}

Predict the output

It prints 5 7 7. i++ yields the old value and then increments, so a is 5; ++i increments first, so i and b are 7. Each statement modifies i once, so this is defined; i++ + ++i in one expression is undefined behavior.

What is type casting in C, and what are implicit conversions?

Fresherdata typesoperators

A cast converts a value explicitly with (type) value. Implicit conversions convert the smaller operand to the larger type, and promote char and short to int before arithmetic.

C
#include <stdio.h>

int main(void) {
    int a = 7, b = 2;
    double wrong = a / b;            /* int division first, then converted */
    double right = (double) a / b;   /* a becomes double, so b does too */
    int truncated = (int) 3.99;      /* casting to int drops the fraction */
    printf("%.1f %.1f %d\n", wrong, right, truncated);
    return 0;
}

It prints 3.0 3.5 3: a / b divides as ints first. Converting a double that does not fit, as in (int) 1e10, is undefined behavior, not a wrap.

What are argc and argv in main?

Fresherfunctionsbasics

argc is the number of command-line arguments and argv the array of strings holding them; argv[0] is normally the program name and argv[argc] is a null pointer.

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

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "usage: %s NUMBER\n", argv[0]);
        return 1;
    }
    int n = atoi(argv[1]);   /* arguments always arrive as strings */
    printf("%d squared is %d\n", n, n * n);
    return 0;
}

./square 7 prints 7 squared is 49. Prefer strtol to atoi, which cannot report bad input. See command-line arguments.

How do you write to and read from a file in C?

Fresherfiles

fopen returns a FILE * or NULL; write with fprintf, read with fgets, then fclose. Mode "w" creates or truncates, "a" appends, "r" fails if the file is missing.

C
#include <stdio.h>

int main(void) {
    FILE *fp = fopen("notes.txt", "w");
    if (fp == NULL) { perror("fopen"); return 1; }
    fprintf(fp, "line %d\n", 1);
    fputs("line 2\n", fp);
    fclose(fp);

    fp = fopen("notes.txt", "r");
    if (fp == NULL) { perror("fopen"); return 1; }
    char buf[64];
    while (fgets(buf, sizeof buf, fp) != NULL)
        printf("read: %s", buf);
    fclose(fp);
    return 0;
}

It prints read: line 1 and read: line 2. Loop on fgets, not feof, which turns true only after a read has failed. See file handling.

Write a C program to check whether a number is prime.

Fresheralgorithms

Test divisors up to √n, since a divisor above √n pairs with one below it; skip even numbers after 2. That is O(√n).

C
#include <stdio.h>
#include <stdbool.h>

bool is_prime(int n) {
    if (n < 2) return false;
    if (n % 2 == 0) return n == 2;
    for (int d = 3; d <= n / d; d += 2)
        if (n % d == 0) return false;
    return true;
}

int main(void) {
    for (int n = 0; n <= 30; n++)
        if (is_prime(n)) printf("%d ", n);
    printf("\n");
    printf("97: %s, 91: %s\n", is_prime(97) ? "prime" : "not prime",
           is_prime(91) ? "prime" : "not prime");
    return 0;
}

It prints the primes up to 30 and 97: prime, 91: not prime. The loop tests d <= n / d because d * d can overflow a signed int, which is undefined behavior.

C pointers interview questions

The topic C interviews spend most time on: invalid pointers, const, function pointers and pointer arithmetic.

What is a pointer in C?

Fresherpointers

A pointer is a variable that stores the address of another object. &x takes an address, *p reads or writes the object behind it, and the pointer's type gives *p and p + 1 the object's size.

C
#include <stdio.h>

int main(void) {
    int x = 10;
    int *p = &x;      /* p holds the address of x */
    *p = 25;          /* write through the pointer */
    printf("x = %d, *p = %d\n", x, *p);
    printf("p points to x: %s\n", p == &x ? "yes" : "no");
    return 0;
}

It prints x = 25, *p = 25 and yes. More in pointers in C.

What is a void pointer, and why can you not dereference it?

Experiencedpointers

A void * holds the address of any object type, which is how malloc, qsort and memcpy stay generic. You cannot dereference it because the compiler does not know the size or type behind it; convert it first.

C
#include <stdio.h>

void print_value(const void *p, char type) {
    if (type == 'i') printf("int: %d\n", *(const int *) p);
    if (type == 'd') printf("double: %.2f\n", *(const double *) p);
}

int main(void) {
    int i = 42;
    double d = 3.14159;
    print_value(&i, 'i');
    print_value(&d, 'd');
    return 0;
}

Arithmetic on void * is not standard C (GCC allows it with size 1), so use char * for byte offsets.

What does this print: writing through a pointer to a pointer?

Fresherpointers
C
#include <stdio.h>

int main(void) {
    int x = 10;
    int *p = &x;
    int **q = &p;
    **q = 20;
    printf("%d\n", x);
    return 0;
}

Predict the output

It prints 20. *q is p and **q is x, so **q = 20 writes to x. A function takes a pointer to a pointer when it must change the caller's pointer:

void make_buffer(char **out, size_t n) {
    *out = malloc(n);   /* changes the caller's pointer */
}

What is the difference between const int *p, int *const p and const int *const p?

Experiencedpointersconst

const int *p points to a const int: p can move but *p cannot be written. int *const p is a const pointer: *p can be written but p cannot move. const int *const p allows neither. Read right to left; const applies to what is on its left, or its right if nothing is on its left. Use const T * for read-only parameters, as strlen does.

What does this print: *(p + 2) next to *p + 2?

Fresherpointers
C
#include <stdio.h>

int main(void) {
    int a[] = {10, 20, 30, 40};
    int *p = a;
    printf("%d %d\n", *(p + 2), *p + 2);
    return 0;
}

Predict the output

It prints 30 12. p + 2 moves two ints forward, scaled by sizeof(int), so *(p + 2) is a[2]. * binds tighter than +, so *p + 2 is a[0] + 2. See pointer arithmetic.

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

Experiencedpointersfunctions

A function pointer stores a function's address so you can call it later or pass it on, declared as return_type (*name)(params). C uses it for callbacks and dispatch tables.

C
#include <stdio.h>
#include <stdlib.h>

int cmp_asc(const void *a, const void *b) {
    int x = *(const int *) a, y = *(const int *) b;
    return (x > y) - (x < y);
}

int main(void) {
    int (*cmp)(const void *, const void *) = cmp_asc;
    int v[] = {5, 2, 9, 1, 7};
    qsort(v, 5, sizeof v[0], cmp);
    for (int i = 0; i < 5; i++) printf("%d ", v[i]);
    printf("\n");
    return 0;
}

It prints 1 2 5 7 9. The comparator avoids x - y, which overflows for large values of opposite sign. See function pointers.

What is the difference between int *p[3] and int (*p)[3]?

Seniorpointersarrays

int *p[3] is an array of 3 pointers; int (*p)[3] is one pointer to an array of 3 ints. [] binds tighter than *, so the parentheses decide.

C
#include <stdio.h>

int main(void) {
    int a = 1, b = 2, c = 3;
    int *arr_of_ptrs[3] = {&a, &b, &c};

    int grid[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int (*row)[3] = grid;          /* points to grid[0] */

    printf("sizeof arr_of_ptrs: %zu\n", sizeof arr_of_ptrs);
    printf("sizeof row: %zu\n", sizeof row);
    printf("*arr_of_ptrs[1] = %d\n", *arr_of_ptrs[1]);
    printf("row[1][2] = %d\n", row[1][2]);   /* row + 1 skips a whole row */
    return 0;
}

Here the array is 24 bytes and the pointer 8. A 2D array decays to a pointer to an array, so row + 1 skips a whole row.

What does the restrict keyword mean?

Seniorpointersoptimization

restrict (C99) promises that, for the pointer's lifetime, an object it points to that is modified is accessed only through that pointer (or one derived from it). The compiler can then assume no overlap and vectorize.

void add(int n, float *restrict out,
         const float *restrict a, const float *restrict b) {
    for (int i = 0; i < n; i++)
        out[i] = a[i] + b[i];
}

Breaking the promise is undefined behavior and is not checked. memcpy declares its parameters restrict for this reason: its buffers must not overlap. C++ has no restrict keyword.

C arrays and strings interview questions

Arrays, how they decay to pointers, null-terminated strings and the functions that overflow buffers.

What does this print: sizeof on an array and on a pointer to it?

Experiencedarrayspointers
C
#include <stdio.h>

int main(void) {
    int arr[10] = {0};
    int *p = arr;
    printf("%zu %zu\n", sizeof arr, sizeof p);
    return 0;
}

Predict the output

It prints 40 8: the whole array is 10 four-byte ints and the pointer is 8 bytes on a 64-bit build (both implementation-defined). An array decays to a pointer in most expressions but is not one. So inside a function, a parameter int a[] is an int * and sizeof a / sizeof a[0] does not give the length. See pointers and arrays.

What does this print: sizeof and strlen on the same string?

Fresherstrings
C
#include <stdio.h>
#include <string.h>

int main(void) {
    char s[] = "hello";
    printf("%zu %zu\n", sizeof s, strlen(s));
    return 0;
}

Predict the output

It prints 6 5. The array holds 5 characters plus the terminating '\0', which strlen does not count. A copy needs strlen(s) + 1 bytes. See strings in C.

What is the difference between char s[] = "hi" and char *s = "hi"?

Experiencedstringspointers

char s[] = "hi" is a 3-byte array holding a copy of the literal, which you may modify. char *s = "hi" points to the literal, usually in read-only memory, and writing through it is undefined behavior.

char a[] = "hi";
a[0] = 'H';          /* fine: a is your array */

char *p = "hi";
p[0] = 'H';          /* undefined behavior: writes to a string literal */

const char *q = "hi"; /* the honest declaration: the compiler now rejects q[0] = 'H' */

In C++ the literal is const char[3], so char *p = "hi" is ill-formed there since C++11.

Write a C function that reverses a string in place.

Fresherstrings

Swap the characters at both ends and move toward the middle: O(n) time, O(1) space.

C
#include <stdio.h>
#include <string.h>

void reverse(char *s) {
    size_t len = strlen(s);
    if (len < 2) return;
    for (size_t i = 0, j = len - 1; i < j; i++, j--) {
        char t = s[i];
        s[i] = s[j];
        s[j] = t;
    }
}

int main(void) {
    char word[] = "interview";
    reverse(word);
    printf("%s\n", word);
    return 0;
}

It prints weivretni. The len < 2 check matters: size_t is unsigned, so len - 1 on an empty string wraps. Passing a literal, as in reverse("abc"), writes to read-only memory.

Why is strcpy unsafe, and is strncpy the fix?

Experiencedstringssecurity

strcpy does not know the destination's size, so a long source overflows it. strncpy is no fix: when the source is too long it leaves the result without a null terminator. Use snprintf, which terminates and returns the length it wanted.

C
#include <stdio.h>

int main(void) {
    char buf[8];
    int needed = snprintf(buf, sizeof buf, "%s", "hello world");
    printf("buf = \"%s\", needed = %d\n", buf, needed);
    if (needed >= (int) sizeof buf) printf("truncated\n");
    return 0;
}

It keeps hello w, reports 11 and prints truncated. Never use gets, removed in C11. See string functions.

What does this print: == and strcmp on two arrays holding the same text?

Fresherstrings
C
#include <stdio.h>
#include <string.h>

int main(void) {
    char a[] = "abc";
    char b[] = "abc";
    printf("%d %d\n", a == b, strcmp(a, b) == 0);
    return 0;
}

Predict the output

It prints 0 1. a == b compares the addresses of two separate arrays; strcmp compares characters and returns 0 when they match. Its nonzero results are any negative or positive value, so test the sign, never == 1.

What does this print: 2[a] on an int array?

Experiencedarrayspointers
C
#include <stdio.h>

int main(void) {
    int a[] = {10, 20, 30, 40};
    printf("%d\n", 2[a]);
    return 0;
}

Predict the output

It prints 30. The standard defines a[i] as *(a + i), and addition commutes, so 2[a] is a[2]. It also explains why p[-1] is valid when p points into the middle of an array.

How do you pass a 2D array to a function in C?

Experiencedarrays

The function must know every dimension except the first, to compute m[i][j]. Fix the column count in the type, or (C99) pass the dimensions first as a variable length array parameter.

C
#include <stdio.h>

void print_fixed(int m[][3], int rows) {
    for (int i = 0; i < rows; i++)
        printf("%d %d %d\n", m[i][0], m[i][1], m[i][2]);
}

int sum_vla(int rows, int cols, int m[rows][cols]) {
    int s = 0;
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < cols; j++) s += m[i][j];
    return s;
}

int main(void) {
    int grid[2][3] = {{1, 2, 3}, {4, 5, 6}};
    print_fixed(grid, 2);
    printf("sum = %d\n", sum_vla(2, 3, grid));
    return 0;
}

int ** does not work: a 2D array is one block, not an array of row pointers. VLAs became optional in C11, C23 again requires VLA parameter types like this one, and MSVC lacks them; see arrays on cppreference.

C memory management interview questions: malloc, free, stack vs heap

Dynamic allocation, stack and heap, leaks and the mistakes that cause crashes.

What is the difference between stack and heap memory in C?

Freshermemory

The stack holds locals and call frames, freed automatically on return; it is fast and small (8 MB for the main thread on most Linux systems, 1 MB on Windows by the linker default, 128 KiB for new threads on musl). The heap holds malloc memory that lives until free. Use the heap when the size is known only at runtime or the data must outlive the function. See stack vs heap.

What is the difference between malloc, calloc and realloc?

Freshermemory

All three return NULL on failure. malloc(size) gives uninitialized memory, calloc(count, size) zeroed memory, and realloc(p, size) resizes a block, moving it if needed.

C
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *a = calloc(4, sizeof *a);          /* 4 ints, all zero */
    if (!a) return 1;
    for (int i = 0; i < 4; i++) a[i] += i;

    int *bigger = realloc(a, 6 * sizeof *a); /* grow to 6 ints */
    if (!bigger) { free(a); return 1; }
    a = bigger;
    a[4] = 40; a[5] = 50;                    /* new part is uninitialized, so set it */

    for (int i = 0; i < 6; i++) printf("%d ", a[i]);
    printf("\n");
    free(a);
    return 0;
}

It prints 0 1 2 3 40 50. calloc returns NULL if count * size overflows (mainstream libcs check it and C23 requires it). See calloc and realloc.

What is wrong with p = realloc(p, new_size);?

Experiencedmemory

If realloc fails it returns NULL and keeps the old block, so assigning straight to p leaks it.

int *tmp = realloc(p, new_size);
if (tmp == NULL) {
    /* p is still valid: report the error, keep or free p */
    free(p);
    return -1;
}
p = tmp;

Avoid realloc(p, 0) too: glibc frees p and returns NULL, musl returns a non-null pointer to a minimal block, and C23 makes the call undefined (see realloc on cppreference).

What is a memory leak, and how do you find one in C?

Experiencedmemory

A leak is heap memory still allocated that the program can no longer reach, because the last pointer to it was lost before free.

void leak(void) {
    char *buf = malloc(100);
    if (!buf) return;
    if (something_failed()) return;   /* buf is leaked on this path */
    free(buf);
}

Find leaks with Valgrind (valgrind --leak-check=full ./app) or AddressSanitizer (gcc -fsanitize=address -g). Avoid them with one owner per allocation and a single cleanup path (goto cleanup;). See memory leaks.

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

Experiencedmemory

Both are undefined behavior. A double free can corrupt the allocator's lists, which attackers exploit; use after free touches memory that may belong to another allocation. free(NULL) does nothing, so setting the pointer to NULL after free makes a second free harmless. AddressSanitizer reports both bugs at the exact line.

Why is returning the address of a local variable a bug?

Experiencedmemorypointers

The local's stack frame is released on return, so the pointer dangles and reading through it is undefined behavior.

char *bad_name(void) {
    char name[16] = "coddy";
    return name;            /* GCC warns: function returns address of local variable */
}

Let the caller pass a buffer instead (the standard library's usual choice), or return malloc memory the caller frees.

What causes a segmentation fault in C?

Experiencedmemorydebugging

The operating system stops a process that touched memory it may not access. In C it is a symptom of undefined behavior: dereferencing NULL or an uninitialized pointer, writing to a string literal, going past an array, using freed memory, or a stack overflow. An out-of-bounds write inside your own memory may not crash at all. Debug with -fsanitize=address, or gdb and bt. See segmentation faults.

Describe the memory layout of a C program.

Seniormemory

From low to high addresses on Linux: text (machine code), data (globals and statics with a non-zero initializer), BSS (zero or uninitialized ones, zeroed at startup and taking no space in the file), the heap growing up, shared libraries and mmap regions, and the stack growing down. So static int big[1000000]; grows BSS but not the executable; with = {1} the file grows by 4 MB.

How does malloc work, and how does free know the size of the block?

Seniormemory

It depends on the libc. glibc keeps a small header before each block with its size, and free(p) reads the bytes before p; musl's mallocng and jemalloc find the size from the group or page the address belongs to. Either way, pass back exactly what malloc returned. Freed blocks go to free lists or size-class bins for reuse, and glibc serves requests above a threshold (128 KiB at start, adjusted at run time) with their own mmap, returned to the kernel on free.

What does malloc(0) return?

Seniormemory

It is implementation-defined: either NULL, or a unique pointer you must not dereference but must free. glibc and musl return non-NULL, but portable code cannot rely on it, so a size that can be 0 needs its own check.

size_t n = count_items();
int *items = malloc(n * sizeof *items);
if (items == NULL && n != 0) {
    /* a real allocation failure */
}

Otherwise an empty list looks like an allocation failure.

C structures and unions interview questions

Structs, unions, padding and the self-referential structs linked lists are made of.

What is the difference between a structure and a union in C?

Fresherstructs

A struct gives each member its own storage; a union makes all members share one, so only one holds a value at a time and the size is the largest member's (plus padding).

C
#include <stdio.h>

struct S { int i; double d; char c; };
union  U { int i; double d; char c; };

int main(void) {
    printf("struct: %zu bytes\n", sizeof(struct S));
    printf("union:  %zu bytes\n", sizeof(union U));
    union U u;
    u.d = 2.5;
    printf("u.d = %.1f\n", u.d);
    return 0;
}

Here the struct is 24 bytes and the union 8. A union usually pairs with a tag field naming the active member. See unions.

What does this print: sizeof of two structs with the same members in a different order?

Experiencedstructsmemory
C
#include <stdio.h>

struct A { char c; int i; char d; };
struct B { int i; char c; char d; };

int main(void) {
    printf("%zu %zu\n", sizeof(struct A), sizeof(struct B));
    return 0;
}

Predict the output

It prints 12 8. Each int is aligned to 4 bytes, so A pads 3 bytes after c and 3 after d, while B keeps the chars together: 4 + 1 + 1 + 2. Padding is implementation-defined (these numbers are GCC on x86-64); order members largest first.

What does this print: assigning one struct to another, then changing the copy?

Fresherstructs
C
#include <stdio.h>

struct Point { int x, y; };

int main(void) {
    struct Point a = {1, 2};
    struct Point b = a;
    b.x = 9;
    printf("%d %d\n", a.x, b.x);
    return 0;
}

Predict the output

It prints 1 9. Struct assignment copies every member, so b is independent. A pointer member is copied shallowly, though: both structs then share one buffer.

What does typedef do, and why is it used with structs?

Fresherstructs

typedef creates an alias for an existing type, not a new type, so you can write Point instead of struct Point.

typedef struct Point {
    int x, y;
} Point;

typedef int (*Compare)(const void *, const void *);  /* names a function pointer type */

It helps most with function pointer types.

How do you define a linked list node in C, and why does it need a pointer to itself?

Experiencedstructslinked lists

A node holds data and a pointer to the next node. A struct cannot contain itself (its size would be infinite), but it can contain a pointer to itself, since a pointer has a fixed size.

C
#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int value;
    struct Node *next;   /* the typedef name is not defined yet here */
} Node;

Node *push(Node *head, int value) {
    Node *n = malloc(sizeof *n);
    if (!n) return head;
    n->value = value;
    n->next = head;
    return n;
}

int main(void) {
    Node *head = NULL;
    for (int i = 1; i <= 4; i++) head = push(head, i * 10);
    for (Node *p = head; p; p = p->next) printf("%d -> ", p->value);
    printf("NULL\n");
    while (head) { Node *next = head->next; free(head); head = next; }
    return 0;
}

It prints 40 -> 30 -> 20 -> 10 -> NULL. See the linked list visualizer.

Can you compare two structs with == in C?

Experiencedstructs

No. == is not defined for structs, so it is a compile error; compare member by member. memcmp is wrong in general, because padding bytes have unspecified values and floating point members (0.0 and -0.0, NaN) need their own rules.

What does this print: an enum with one explicit value in the middle?

Fresherstructsdata types
C
#include <stdio.h>

enum Level { A, B = 5, C };

int main(void) {
    printf("%d %d %d\n", A, B, C);
    return 0;
}

Predict the output

It prints 0 5 6. Enumerators start at 0 and each is one more than the previous unless given a value, so C follows B = 5. In C an enumerator is an int constant (C23 also allows wider ones), and the compiler does not stop an enum variable from holding other values.

What is a flexible array member?

Seniorstructsmemory

A flexible array member (C99) is a size-less array as the last member of a struct, so one malloc holds the struct and its data together.

C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    size_t len;
    char data[];     /* flexible array member, must be last */
} Buffer;

int main(void) {
    const char *text = "packet payload";
    size_t n = strlen(text) + 1;
    Buffer *b = malloc(sizeof *b + n);
    if (!b) return 1;
    b->len = n - 1;
    memcpy(b->data, text, n);
    printf("%zu: %s\n", b->len, b->data);
    free(b);
    return 0;
}

It prints 14: packet payload; sizeof(Buffer) excludes the array. The old char data[1] trick is undefined behavior past element 0.

C preprocessor interview questions: macros and directives

Macros, include guards and conditional compilation, with the macro traps that become output questions.

What is the C preprocessor?

Fresherpreprocessor

The preprocessor runs before the compiler and works on text: it pastes in #include files, expands macros, removes comments and keeps or drops code under #if. It knows nothing about types or scope, which is the root of every macro bug. gcc -E shows its output. See the preprocessor.

What does this print: a SQUARE(x) macro defined as x * x?

Fresherpreprocessor
C
#include <stdio.h>

#define SQUARE(x) x * x

int main(void) {
    printf("%d\n", SQUARE(2 + 3));
    return 0;
}

Predict the output

It prints 11. A macro substitutes text, so SQUARE(2 + 3) becomes 2 + 3 * 2 + 3. Parenthesize every parameter and the whole body:

#define SQUARE(x) ((x) * (x))

That still evaluates x twice, so SQUARE(i++) is undefined behavior; a static inline function has neither problem. See macros.

What does this print: MAX(i++, j) with a ternary macro?

Experiencedpreprocessor
C
#include <stdio.h>

#define MAX(a, b) ((a) > (b) ? (a) : (b))

int main(void) {
    int i = 5, j = 3;
    int m = MAX(i++, j);
    printf("%d %d\n", m, i);
    return 0;
}

Predict the output

It prints 6 7. The expansion ((i++) > (j) ? (i++) : (j)) increments i in the condition and again in the chosen branch, which yields 6. It is defined, since ?: has a sequence point after the condition, but a function would evaluate i++ once.

What is the difference between a macro and a function?

Experiencedpreprocessorfunctions

A macro is text substituted by the preprocessor: no type checking, arguments evaluated as many times as they appear, nothing for a debugger to step into. A static inline function is type-checked, evaluates each argument once and costs nothing when inlined, so prefer it. Only macros can stringify (#x), paste tokens (##) and use the caller's __FILE__ and __LINE__.

What is an include guard, and how is it different from #pragma once?

Fresherpreprocessor

An include guard stops a header from being processed twice in one translation unit, which would redefine its types.

#ifndef LIST_H
#define LIST_H

typedef struct Node Node;
Node *list_push(Node *head, int value);

#endif /* LIST_H */

#pragma once does the same in one line; it is not standard C, but GCC, Clang and MSVC support it.

What do the # and ## operators do in a macro?

Seniorpreprocessor

#param turns a macro argument into a string literal (stringification); a ## b glues two tokens into one (token pasting).

C
#include <stdio.h>

#define SHOW(expr) printf(#expr " = %d\n", (expr))
#define FIELD(name) int field_##name

struct Config {
    FIELD(width);
    FIELD(height);
};

int main(void) {
    struct Config c = {640, 480};
    SHOW(c.field_width * c.field_height);
    SHOW(17 / 5);
    return 0;
}

It prints c.field_width * c.field_height = 307200 and 17 / 5 = 3. assert uses # to print the failing expression.

What is conditional compilation, and where is it used?

Experiencedpreprocessor

#if, #ifdef and #ifndef keep or drop code before compiling: debug logging, platform-specific code, flags from the build (gcc -DDEBUG).

C
#include <stdio.h>

#define DEBUG 1

#if DEBUG
#define LOG(msg) printf("[%s:%d] %s\n", __func__, __LINE__, msg)
#else
#define LOG(msg) ((void) 0)
#endif

int main(void) {
    LOG("starting");
#ifdef _WIN32
    printf("Windows build\n");
#else
    printf("not a Windows build\n");
#endif
    return 0;
}

It prints [main:12] starting and not a Windows build. __func__ is a predefined identifier, not a macro, so #if cannot test it.

Why are multi-statement macros wrapped in do { ... } while (0)?

Seniorpreprocessor

So the macro acts as one statement everywhere, including an if without braces, and still needs its semicolon.

#define SWAP_BAD(a, b) int t = a; a = b; b = t;
#define SWAP(a, b) do { int t_ = (a); (a) = (b); (b) = t_; } while (0)

if (x > y)
    SWAP(x, y);      /* the whole swap is the if body */
else
    puts("sorted");

With SWAP_BAD only the first statement belongs to the if. Plain braces fail too: in {...}; the ; ends the if, so the else does not compile.

C programming output questions

Predict-the-output questions from written tests; each one has a defined result on this build.

What does this print: printf called inside printf?

Fresheroutputfunctions
C
#include <stdio.h>

int main(void) {
    printf("%d\n", printf("Hello"));
    return 0;
}

Predict the output

It prints Hello5. The inner printf prints Hello and returns the number of characters printed, 5. scanf likewise returns how many items it read: check scanf("%d", &n) != 1.

What does this print: sizeof(i++)?

Experiencedoutputoperators
C
#include <stdio.h>

int main(void) {
    int i = 1;
    size_t n = sizeof(i++);
    printf("%zu %d\n", n, i);
    return 0;
}

Predict the output

It prints 4 1. sizeof needs only the operand's type, so i++ is never evaluated. The exception is a variable length array, whose sizeof is computed at runtime and does evaluate its operand.

What does this print: comparing -1 with an unsigned int?

Experiencedoutputdata types
C
#include <stdio.h>

int main(void) {
    int a = -1;
    unsigned int b = 1;
    if (a < b)
        printf("less\n");
    else
        printf("greater\n");
    return 0;
}

Predict the output

It prints greater. The int is converted to unsigned, and -1 becomes UINT_MAX. The same bug hits i < strlen(s) - 1 on an empty string; -Wsign-compare warns.

What does this print: a switch with a missing break?

Fresheroutputcontrol flow
C
#include <stdio.h>

int main(void) {
    int n = 2;
    switch (n) {
    case 1: printf("one ");
    case 2: printf("two ");
    case 3: printf("three "); break;
    default: printf("other ");
    }
    printf("\n");
    return 0;
}

Predict the output

It prints two three. Execution jumps to case 2 and falls through into case 3 until its break. Mark deliberate fallthrough with a comment or C23's [[fallthrough]]; so readers and -Wimplicit-fallthrough know.

What does this print: a counter function with a static local?

Fresheroutputstorage classes
C
#include <stdio.h>

int counter(void) {
    static int count = 0;
    count++;
    return count;
}

int main(void) {
    printf("%d ", counter());
    printf("%d ", counter());
    printf("%d\n", counter());
    return 0;
}

Predict the output

It prints 1 2 3. A static local is initialized once, before the program starts, and keeps its value between calls; a plain local would give 1 1 1. Its initializer must be a constant expression.

What does this print: a float holding 0.1 compared with the literal 0.1?

Fresheroutputdata types
C
#include <stdio.h>

int main(void) {
    float f = 0.1;
    if (f == 0.1)
        printf("equal\n");
    else
        printf("not equal\n");
    return 0;
}

Predict the output

It prints not equal. 0.1 has no exact binary form: f holds the nearest float, the literal is the nearest double, and they differ. Compare with a tolerance, such as fabs(a - b) < 1e-6.

What does this print: i++ && i++ as an if condition?

Fresheroutputoperators
C
#include <stdio.h>

int main(void) {
    int i = 0;
    if (i++ && i++) {
        printf("inside\n");
    }
    printf("%d\n", i);
    return 0;
}

Predict the output

It prints 1. The left i++ yields 0 (false), so && short-circuits and the right side never runs. It is defined because && and || have a sequence point after the left operand, which is also what makes p != NULL && p->value > 0 safe.

What does this print: a for loop with a semicolon after the parentheses?

Fresheroutputcontrol flow
C
#include <stdio.h>

int main(void) {
    int i;
    for (i = 0; i < 3; i++);
    {
        printf("i = %d\n", i);
    }
    return 0;
}

Predict the output

It prints i = 3 once. The ; is the loop's empty body, so the loop counts i to 3 and the braces run once afterwards. if (x > 0); has the same bug.

What does this print: incrementing 255 and decrementing 0 in an unsigned char?

Fresheroutputdata types
C
#include <stdio.h>

int main(void) {
    unsigned char c = 255;
    c++;
    unsigned char d = 0;
    d--;
    printf("%d %d\n", c, d);
    return 0;
}

Predict the output

It prints 0 255. Unsigned arithmetic wraps modulo 2 to the power of the bit width. Signed overflow such as INT_MAX + 1 is undefined behavior, not a guaranteed wrap, and optimizers rely on that.

What does this print: sizeof(char) next to sizeof('a')?

Experiencedoutputdata types
C
#include <stdio.h>

int main(void) {
    printf("%zu %zu\n", sizeof(char), sizeof('a'));
    return 0;
}

Predict the output

It prints 1 4. In C a character constant like 'a' has type int, so its size is sizeof(int). In C++ it is a char and the size is 1.

What does this print: << mixed with +, and & mixed with ==?

Experiencedoutputoperators
C
#include <stdio.h>

int main(void) {
    int a = 1 << 3 + 1;
    int b = (6 & 2 == 2) + 6;
    printf("%d %d\n", a, b);
    return 0;
}

Predict the output

It prints 16 6. + binds tighter than <<, so 1 << 3 + 1 is 1 << 4. == binds tighter than &, so 6 & 2 == 2 is 6 & 1, which is 0. Parenthesize bitwise expressions; -Wall warns about both.

What does this print: arithmetic on character constants?

Fresheroutputdata types
C
#include <stdio.h>

int main(void) {
    char c = 'A' + 2;
    printf("%c %d %d\n", c, c, '3' - '0');
    return 0;
}

Predict the output

It prints C 67 3. A char is a small integer, so 'A' + 2 is 67, shown as C by %c. '3' - '0' is 3 because the standard requires the digits to be consecutive; letters are consecutive only in ASCII-based encodings.

What is the output of i = i++ + ++i;?

Experiencedundefined behavior

There is none: modifying i twice between sequence points (C11: unsequenced side effects on the same object) is undefined behavior, so any output is allowed.

TermMeaningExample
Undefinedno requirements at alli = i++ + 1, signed overflow
Unspecifiedone of several results, not documentedorder of f() + g()
Implementation-defineddocumented by the compilersizeof(int), signedness of char

See undefined behavior.

Embedded C interview questions: volatile and bit manipulation

Where firmware interviews start: volatile, register bit operations, fixed-width types and endianness.

What does the volatile keyword do?

Experiencedembeddedvolatile

volatile tells the compiler a variable can change outside the visible control flow, so every read and write must happen, in order, and cannot be cached or removed. Use it for hardware registers, variables changed by interrupt or signal handlers (volatile sig_atomic_t), and locals that must survive longjmp.

volatile int data_ready = 0;          /* set by an ISR */

void wait_for_data(void) {
    while (!data_ready) { }           /* without volatile this can become an infinite loop */
}

It does not make operations atomic or synchronize threads; use C11 _Atomic or a mutex. MSVC's /volatile:ms, the default on x86 and x64, adds acquire and release ordering as a Microsoft extension; see the /volatile docs.

Can a variable be both const and volatile?

Seniorembeddedvolatile

Yes. const means your code may not write it; volatile means something else may change it. A read-only hardware status register is the textbook case.

#define STATUS_REG (*(const volatile uint32_t *) 0x40021000u)

while ((STATUS_REG & 0x1u) == 0) {
    /* wait until the device sets bit 0 */
}

How do you set, clear, toggle and check a bit in C?

Fresherbit manipulationembedded

Build a mask with 1u << n, then OR to set, AND with the inverted mask to clear, XOR to toggle and AND to test.

C
#include <stdio.h>

#define BIT(n)            (1u << (n))
#define SET_BIT(x, n)     ((x) |= BIT(n))
#define CLEAR_BIT(x, n)   ((x) &= ~BIT(n))
#define TOGGLE_BIT(x, n)  ((x) ^= BIT(n))
#define CHECK_BIT(x, n)   (((x) >> (n)) & 1u)

int main(void) {
    unsigned int reg = 0;
    SET_BIT(reg, 3);
    SET_BIT(reg, 0);
    printf("after set:    0x%02X\n", reg);
    CLEAR_BIT(reg, 0);
    printf("after clear:  0x%02X\n", reg);
    TOGGLE_BIT(reg, 7);
    printf("after toggle: 0x%02X\n", reg);
    printf("bit 3 is %u, bit 1 is %u\n", CHECK_BIT(reg, 3), CHECK_BIT(reg, 1));
    return 0;
}

It prints 0x09, 0x08, 0x88 and bit 3 is 1, bit 1 is 0. Use 1u: 1 << 31 on a 32-bit int is undefined behavior.

What does this print: setting bit 6 and clearing bit 0 of 0x0F?

Experiencedbit manipulation
C
#include <stdio.h>

int main(void) {
    unsigned int x = 0x0F;
    x |= 1u << 6;
    x &= ~(1u << 0);
    printf("0x%X\n", x);
    return 0;
}

Predict the output

It prints 0x4E. 0x0F is 0000 1111; setting bit 6 gives 0100 1111, and clearing bit 0 gives 0100 1110. Bits count from 0.

How do you count the set bits in an integer?

Experiencedbit manipulation

Brian Kernighan's method: n & (n - 1) clears the lowest set bit, so count iterations until n is 0, once per set bit.

C
#include <stdio.h>

int popcount(unsigned int n) {
    int count = 0;
    while (n) {
        n &= n - 1;     /* drop the lowest set bit */
        count++;
    }
    return count;
}

int main(void) {
    unsigned int values[] = {0, 1, 7, 8, 255, 0xF0F0};
    for (int i = 0; i < 6; i++)
        printf("%u has %d set bits (builtin says %d)\n",
               values[i], popcount(values[i]), __builtin_popcount(values[i]));
    return 0;
}

In production use __builtin_popcount, one instruction where the target has it (on x86-64 only with -mpopcnt or a matching -march), or C23's stdc_count_ones.

What does this print: the first byte of an unsigned int holding 1?

Experiencedembeddedmemory
C
#include <stdio.h>

int main(void) {
    union {
        unsigned int i;
        unsigned char c[sizeof(unsigned int)];
    } u;
    u.i = 1;
    printf("%d\n", u.c[0]);
    return 0;
}

Predict the output

It prints 1. On a little-endian machine (x86, and ARM as Linux normally runs it) the least significant byte comes first; big-endian would print 0. Byte order is implementation-defined. Reading another union member is allowed in C but undefined in C++.

How do you swap two numbers without a temporary variable?

Fresherbit manipulation

With XOR: a ^= b; b ^= a; a ^= b;, because x ^ x is 0 and x ^ 0 is x.

C
#include <stdio.h>

int main(void) {
    int a = 12, b = 25;
    a ^= b;
    b ^= a;
    a ^= b;
    printf("a = %d, b = %d\n", a, b);
    return 0;
}

It prints a = 25, b = 12. If both names refer to the same object, the first XOR zeroes it. A temporary variable is clearer and as fast.

Why do embedded programmers use uint8_t and uint32_t instead of int?

Experiencedembeddeddata types

Because int and long vary in width (an int is 16 bits on some microcontrollers), while registers and protocols have exact sizes. <stdint.h> (C99) provides uint8_t, uint32_t and the rest. They are optional: a platform with no 8-bit type has no uint8_t, while int_least16_t and int_fast16_t always exist.

What should you avoid inside an interrupt service routine?

Seniorembedded

Keep it short: record the event and let the main loop do the work. Avoid blocking, non-reentrant calls such as printf and malloc, and floating point where the ISR does not save the FPU registers. Shared variables must be volatile, and multi-byte ones need interrupts disabled (or atomic access) around the main loop's read.

volatile uint8_t rx_byte;
volatile bool rx_ready;

void UART_IRQHandler(void) {
    rx_byte = UART->DR;   /* on many UARTs, reading the data register clears the interrupt */
    rx_ready = true;
}

What happens when you shift a negative number right or left in C?

Seniorbit manipulationundefined behavior

Right-shifting a negative signed value is implementation-defined; GCC and most compilers copy the sign bit, so -8 >> 1 is -4. Left-shifting a negative value is undefined behavior in C17.

C
#include <stdio.h>

int main(void) {
    int n = -8;
    unsigned int u = 0xF0000000u;
    printf("%d\n", n >> 1);        /* implementation-defined, GCC: -4 */
    printf("0x%X\n", u >> 4);      /* unsigned: always fills with zeros */
    return 0;
}

On this GCC build it prints -4 and 0xF000000. Shifting by a negative count or by at least the type's width is undefined. Do bit work on unsigned types.

C interview questions for experienced developers

Linkage, aliasing, generic code, variadic functions and the corners of the standard that cause real bugs.

What is linkage in C, and how do static and extern affect it?

Seniorstorage classeslinking

Linkage decides whether one name in different places means the same object or function. External linkage is shared across files (the default for functions and file-level variables), internal is one file (static), and locals have none.

/* config.c */
int max_users = 100;          /* external linkage, definition */
static int retries = 3;       /* internal: invisible to other files */

/* main.c */
extern int max_users;         /* declaration, refers to config.c's object */

Two files defining the same non-static global fail to link (GCC 10 and later default to -fno-common, so even uninitialized ones conflict).

What is the strict aliasing rule?

Seniorundefined behaviorpointers

The compiler may assume pointers to different types do not alias: accessing an object through an lvalue of an incompatible type is undefined behavior, except through its signed or unsigned variant or a character type (char, signed char, unsigned char). The full list is in cppreference's strict aliasing section.

float f = 1.0f;
uint32_t bits = *(uint32_t *) &f;   /* undefined: float read through uint32_t */

uint32_t ok;
memcpy(&ok, &f, sizeof ok);         /* defined, and compiled to a single move */

At -O2 GCC enables -fstrict-aliasing, so code that worked at -O0 can read stale values. Reinterpret bytes with memcpy or a union (allowed in C).

How do you write type-generic code in C?

Seniorfunctionsgenerics

With void * plus an element size and callbacks (as qsort does), with macros, or with C11's _Generic, which picks an expression at compile time from its argument's type.

C
#include <stdio.h>

#define type_name(x) _Generic((x), \
    int: "int", \
    double: "double", \
    char *: "char *", \
    default: "other")

static void print_int(int v) { printf("%d\n", v); }
static void print_double(double v) { printf("%.2f\n", v); }
static void print_str(const char *v) { printf("%s\n", v); }

#define print_val(x) _Generic((x), \
    int: print_int, \
    double: print_double, \
    char *: print_str)(x)

int main(void) {
    char name[] = "coddy";
    printf("%s %s %s\n", type_name(42), type_name(2.5), type_name(name));
    print_val(42);
    print_val(2.5);
    print_val(name);
    return 0;
}

It prints int double char *, then 42, 2.50 and coddy. print_val selects a function and then calls it, because every _Generic branch must compile for any argument.

How do variadic functions like printf work, and how do you write one?

Seniorfunctions

Declare at least one named parameter (C23 also allows f(...)), then ..., and read the rest with va_start, va_arg and va_end. The function cannot know the count or types, so a count or format string must tell it.

C
#include <stdio.h>
#include <stdarg.h>

int sum(int count, ...) {
    va_list ap;
    va_start(ap, count);
    int total = 0;
    for (int i = 0; i < count; i++)
        total += va_arg(ap, int);
    va_end(ap);
    return total;
}

int main(void) {
    printf("%d\n", sum(3, 10, 20, 30));
    printf("%d\n", sum(5, 1, 2, 3, 4, 5));
    return 0;
}

It prints 60 and 15. Arguments are promoted, so va_arg(ap, char) or float is wrong (use int and double), and a wrong type is undefined behavior.

In f() + g(), which function is called first?

Seniorundefined behavior

It is unspecified: either may run first, and the compiler need not document it or be consistent. Function arguments are the same. Only &&, ||, ?: and the comma operator fix the order. If f and g modify the same global it stays unspecified, since calls never interleave, but i++ + i++ is undefined behavior. When order matters, use separate statements.

How do you detect integer overflow in C?

Seniorundefined behaviordata types

Check before the operation: signed overflow is undefined behavior, so a check after it such as if (a + b < a) may be optimized away. Compare against <limits.h> limits, or use the GCC and Clang builtins.

C
#include <stdio.h>
#include <limits.h>
#include <stdbool.h>

bool safe_add(int a, int b, int *out) {
    if ((b > 0 && a > INT_MAX - b) || (b < 0 && a < INT_MIN - b))
        return false;
    *out = a + b;
    return true;
}

int main(void) {
    int r;
    printf("%s\n", safe_add(INT_MAX, 1, &r) ? "ok" : "overflow");
    printf("%s\n", safe_add(100, 23, &r) ? "ok" : "overflow");
    printf("builtin: %s\n", __builtin_add_overflow(INT_MAX, 1, &r) ? "overflow" : "ok");
    return 0;
}

It prints overflow, ok and builtin: overflow. C23 standardizes this as ckd_add in <stdckdint.h>.

How does inline work in C, and how is it different from C++?

Seniorfunctionslinking

In C99 and later, inline without extern in a header is only an inline definition: if the compiler emits a call, one .c file must provide the external definition with extern inline, or -O0 builds fail with "undefined reference". So headers use static inline.

/* vec.h */
static inline int clamp(int x, int lo, int hi) {
    return x < lo ? lo : x > hi ? hi : x;
}

C++ merges duplicate inline definitions at link time, so plain inline works there.

What are setjmp and longjmp, and when would you use them?

Seniorfunctionserror handling

setjmp(env) saves the execution context and returns 0; a later longjmp(env, val) jumps back, and setjmp returns val. It is a non-local goto, the closest C has to exceptions.

C
#include <stdio.h>
#include <setjmp.h>

static jmp_buf on_error;
static int error_code;

void parse(int depth) {
    if (depth == 3) {
        error_code = 42;
        longjmp(on_error, 1);      /* abandon the whole call chain */
    }
    parse(depth + 1);
}

int main(void) {
    if (setjmp(on_error) == 0) {
        parse(0);
        printf("not reached\n");
    } else {
        printf("recovered with code %d\n", error_code);
    }
    return 0;
}

It prints recovered with code 42. setjmp is allowed only in a condition like this or as a statement, so int code = setjmp(env); is undefined behavior (see setjmp on cppreference). Nothing in between is cleaned up, and locals modified after setjmp must be volatile to have a defined value.

Should you use #define or const for constants in C?

Experiencedconstpreprocessor

Use enum for integer constants and const for other typed values. In C a const int is not a constant expression, so case n: and a file-scope static int arr[n]; are not valid C; that is where enum or #define is needed. Inside a function, int arr[n]; compiles, but as a variable length array. C23 adds constexpr for objects.

How would you implement a growable array (like a C++ vector) in C?

Seniormemorydata structures

Keep a pointer, a length and a capacity, and when full realloc to double the capacity, so appends are amortized O(1); a fixed increment makes n appends O(n²).

C
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int *data;
    size_t len, cap;
} IntVec;

int vec_push(IntVec *v, int x) {
    if (v->len == v->cap) {
        size_t cap = v->cap ? v->cap * 2 : 4;
        int *p = realloc(v->data, cap * sizeof *p);
        if (!p) return -1;            /* v->data is still valid */
        v->data = p;
        v->cap = cap;
    }
    v->data[v->len++] = x;
    return 0;
}

int main(void) {
    IntVec v = {0};
    for (int i = 1; i <= 10; i++) {
        vec_push(&v, i * i);
        printf("len %zu cap %zu\n", v.len, v.cap);
    }
    printf("last = %d\n", v.data[v.len - 1]);
    free(v.data);
    return 0;
}

The capacity goes 4, 8, 16 and the last element is 100. Pointers into data go stale after a growth.

Write a binary search in C and point out the classic bug.

Experiencedalgorithms

Keep a range [lo, hi] that must contain the target and halve it, O(log n). The classic bug is (lo + hi) / 2, which overflows when lo + hi exceeds INT_MAX; write lo + (hi - lo) / 2.

C
#include <stdio.h>

int binary_search(const int *a, int n, int target) {
    int lo = 0, hi = n - 1;
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] == target) return mid;
        if (a[mid] < target) lo = mid + 1;
        else hi = mid - 1;
    }
    return -1;
}

int main(void) {
    int a[] = {2, 5, 8, 12, 16, 23, 38, 56};
    printf("%d %d %d\n", binary_search(a, 8, 23), binary_search(a, 8, 2), binary_search(a, 8, 7));
    return 0;
}

It prints 5 0 -1. See the binary search visualizer.

Preparing for the interview

How should I prepare for a C programming interview?
Learn the fundamentals until you can predict the output of short programs: pointers, arrays, strings, structs, storage classes and dynamic memory. Then write the classics by hand in C (string reverse, linked list, binary search, bit counting) and finish with undefined behavior and the preprocessor.
What C topics are asked of freshers, and what of experienced candidates?
Freshers get definitions and output questions: data types, static, pointers, strings, structs versus unions, macros and recursion. Experienced candidates get memory layout, malloc internals, debugging tools, linkage, strict aliasing, undefined behavior and volatile, and must explain why.
Do I need data structures and algorithms for a C interview?
For most software roles, yes: expect a coding question on arrays, strings, linked lists or bits, where pointer handling is part of the test. Embedded roles ask more about registers and interrupts.
How long does it take to prepare for a C interview?
If you already write C, two to three weeks on the topics here and 30 to 50 small problems is usually enough. If C is new to you, plan on one to two months, mostly on pointers and memory.
Which C standard should I know for interviews?
Answer in terms of C99 and C11, which most code targets, and mention C23 (ckd_add, constexpr) only when relevant. Know that // comments, <stdbool.h> and declarations inside for are C99.
Coddy programming languages illustration

Learn to code with Coddy

GET STARTED