Pointers • Memory • Structs & Bits • Strings & Files • Undefined Behaviour • 2026

C Programming Interview Questions

30 questions What each one tests, an answer frame, a spoken answer 35 min read

This page is for anyone facing a C round, from a first job to a senior systems role. Most C interviews start with pointers and arrays, move to malloc and free, stack and heap, then test storage classes, const, structs and unions, bit tricks and macros. Strong rounds end on undefined behaviour and a short coding task done by hand, like reversing a string. Senior rounds add a debugging story and a judgement call. Each question shows what the interviewer is really checking, the shape of a strong answer and a short answer you can say out loud. Practise saying them, then swap in your own stories.

Search all questions by round, difficulty and level, or save the ones you want to practise.

Pointers 6 questions

Easy Technical round Fresher Practice question

1. What is a pointer in C, and what do the & and * operators actually do?

What the interviewer is really testing:
Whether you have a clear picture of addresses and values, because every later pointer question builds on it.
Answer frame:

Pointer: a variable whose value is the address of another object, with a type that says what lives there.

The ampersand: &x gives the address of the object x.

The star: in a declaration it makes a pointer type; in an expression *p follows the address to the object.

Sample spoken answer:

"A pointer is just a variable that holds an address instead of a normal value, and its type tells the compiler what kind of object sits at that address. The ampersand gives me the address of something, so if I write int *p = &x, p now holds where x lives in memory. The star does two jobs. In a declaration it means this is a pointer. In an expression it dereferences, so *p means the int that p points to, and writing *p = 20 changes x itself. The pointer's type matters because it tells the compiler how many bytes to read and how far to move if I do arithmetic on it. The main rule I keep in mind is that a pointer must point at something valid before I dereference it."

Code:
int x = 10;
int *p = &x;        // p holds the address of x
*p = 20;            // write through p: x is now 20
printf("%d %d\n", x, *p);   // prints 20 20
Red flag to avoid:

Mixing up the pointer and the value it points to, for example saying p is 20 after *p = 20.

They may ask next:
  • What is the size of a pointer, and does it depend on the type it points to?
  • Why do we pass a pointer to scanf but not to printf?
Say it in 60 seconds
Easy Technical round Fresher, Mid-level Practice question

2. If p is an int pointer, where does p + 1 point? Why not simply the next byte?

What the interviewer is really testing:
Whether you know that pointer arithmetic is scaled by the type, and the limits on where a pointer may legally go.
Answer frame:

Scaling: adding 1 moves by sizeof the pointed-to type, so p + 1 is the next int.

Indexing: a[i] is defined as *(a + i).

Subtraction: p - q gives a count of elements, only valid inside the same array.

Limits: only within the array or one past its end.

Sample spoken answer:

"Pointer arithmetic counts in elements, not bytes. If p is an int pointer and an int is four bytes on my machine, p + 1 is four bytes further on, which is exactly the next int in an array. That's how the compiler turns a[i] into *(a + i): it scales i by the size of the element for me. Subtracting two pointers works the same way, so if p points at a[5] and q at a[2], p minus q is 3, not 12. There are limits, though. Arithmetic is only defined while the pointer stays inside the same array, or points one past the end, which is what makes loops with an end pointer legal. Going further, or subtracting pointers from two different arrays, is undefined behaviour. And you can't do standard arithmetic on a void pointer, because void has no size."

Code:
int a[5] = {10, 20, 30, 40, 50};
int *p = a;             // points at a[0]
p = p + 2;              // moves 2 * sizeof(int) bytes: now at a[2]
printf("%d\n", *p);     // 30
printf("%td\n", p - a); // 2: a count of elements, not bytes
Red flag to avoid:

Saying p + 1 always moves one byte, or that you can freely walk a pointer before the start of an array.

They may ask next:
  • Why is it legal to point one past the end of an array but not to dereference it?
  • Is 2[a] valid C? What does it mean?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

3. People say an array is just a pointer in C. How true is that? Where do arrays and pointers really differ?

What the interviewer is really testing:
Whether you understand array decay and its exceptions, which explains the classic sizeof bug in function parameters.
Answer frame:

Decay: in most expressions an array name turns into a pointer to its first element.

Exceptions: not with sizeof, not with unary &, and not for a string used to initialise a char array.

Differences: an array owns its storage and can't be reassigned; a pointer is a separate variable.

Parameters: int a[] in a parameter list is really int *a.

Sample spoken answer:

"It's only half true. An array is a block of storage, and a pointer is a variable that holds an address. The confusion comes from decay: in most expressions an array name is converted to a pointer to its first element, which is why I can pass an array to a function or index a pointer. But there are exceptions. sizeof on an array gives the size of the whole array, and &arr gives a pointer to the whole array, with a different type from a pointer to the first element. An array also can't be assigned to or pointed somewhere else. The classic bug is inside a function: a parameter written as int a[] is really int *a, so sizeof a gives the size of a pointer, not the array. That's why C functions that take arrays also take a length."

Code:
#include <stdio.h>

void show(int a[]) {                 // really: int *a
    printf("%zu\n", sizeof a);       // size of a pointer
}

int main(void) {
    int arr[10];
    printf("%zu\n", sizeof arr);     // 10 * sizeof(int)
    show(arr);                       // arr decays to &arr[0]
    return 0;
}
Red flag to avoid:

Saying arrays and pointers are identical, or using sizeof on an array parameter to get its length.

They may ask next:
  • What is the type of &arr, and how does &arr + 1 differ from arr + 1?
  • How would you write a macro that gives the element count of an array, and when does it silently go wrong?
Say it in 60 seconds
Easy Technical round Fresher Practice question

4. What are null, dangling and wild pointers? How does each one happen, and how do you guard against them?

What the interviewer is really testing:
Whether you can name the common ways a pointer goes bad and have simple habits that prevent them.
Answer frame:

Null: deliberately points nowhere; check before dereferencing.

Dangling: points at memory that was freed or went out of scope.

Wild: never initialised, so it holds a random address.

Habits: initialise every pointer, set it to NULL after free, never return a local's address.

Sample spoken answer:

"A null pointer is one I've set to NULL on purpose to say it points at nothing. Dereferencing it is undefined behaviour, which on most systems means a crash, so I check for NULL, especially after malloc or fopen. A dangling pointer used to point at something valid, but that memory is gone: I freed it, or it was a local variable in a function that has returned. The pointer still holds the old address, so the bug can stay hidden until the memory gets reused. A wild pointer was never initialised at all, so it holds whatever was on the stack. My habits are simple. I initialise every pointer, either to a real address or to NULL. After free I set the pointer to NULL if it's still in scope. And I never return the address of a local variable."

Red flag to avoid:

Believing that free sets the pointer to NULL, or that a dangling pointer always crashes straight away.

They may ask next:
  • Why is a dangling pointer often harder to catch than a null pointer?
  • If two pointers point to the same block and you free one, what happens to the other?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

5. What is a function pointer? Show me how you would declare one and tell me where you have actually used it.

What the interviewer is really testing:
Whether you can read and write function pointer syntax and know the real uses: callbacks, qsort and dispatch tables.
Answer frame:

Declaration: int (*op)(int, int) is a pointer to a function taking two ints and returning an int.

Uses: callbacks, comparators for qsort, tables of handlers instead of long switch statements.

Typedef: a typedef makes the declarations readable.

Sample spoken answer:

"A function pointer holds the address of a function, so I can decide at run time which function to call. The syntax is the tricky part: int (*op)(int, int) is a pointer to a function that takes two ints and returns an int. The brackets around *op matter, because without them it declares a function that returns a pointer. The most common place I've used one is qsort. The standard library doesn't know how to compare my data, so I hand it a comparator function. I've also used arrays of function pointers as a dispatch table, where a command code indexes straight into the handler to call, which is cleaner than a long switch. In bigger code I put a typedef on the function type so the declarations stay readable."

Code:
#include <stdlib.h>

static int cmp_int(const void *a, const void *b) {
    int x = *(const int *)a;
    int y = *(const int *)b;
    return (x > y) - (x < y);   // avoids overflow that x - y could cause
}

void sort_ints(int *arr, size_t n) {
    qsort(arr, n, sizeof arr[0], cmp_int);
}
Red flag to avoid:

Writing int *op(int, int) and calling it a function pointer, or never having used one outside a textbook.

They may ask next:
  • Why is returning x - y from a comparator a bug waiting to happen?
  • Why does qsort take void pointers, and what do you give up because of that?
Say it in 60 seconds
Medium Coding round Fresher, Mid-level Practice question

6. When do you need a pointer to a pointer? Write a function that adds a node to the front of a linked list.

What the interviewer is really testing:
Whether you understand that C passes everything by value, so changing the caller's pointer needs its address.
Answer frame:

By value: a function gets a copy of the pointer, so assigning to it changes nothing outside.

Fix: pass the address of the pointer, then write through it.

Other uses: argv, arrays of strings, functions that allocate memory for the caller.

Sample spoken answer:

"C passes every argument by value, including pointers. If I pass a list's head pointer to a function and assign a new node to it inside, I only change the function's copy, and the caller's head stays the same. So when a function must change which address the caller's pointer holds, I pass a pointer to that pointer. For pushing onto the front of a list, I allocate the node, point its next at the current head, and then write the new node into *head, which updates the caller's variable. The same pattern shows up when a function allocates a buffer and hands it back through a parameter, and in argv, which is an array of char pointers. The call site makes it visible, because the caller writes push with an ampersand in front of the list."

Code:
#include <stdlib.h>

struct node { int val; struct node *next; };

int push(struct node **head, int val) {
    struct node *n = malloc(sizeof *n);
    if (n == NULL) return -1;
    n->val = val;
    n->next = *head;
    *head = n;              // changes the caller's pointer
    return 0;
}

// usage: struct node *list = NULL; push(&list, 5);
Red flag to avoid:

Passing the head by value and wondering why the list stays empty, or forgetting to check what malloc returned.

They may ask next:
  • How would you write a function that deletes every node with a given value using a pointer to a pointer?
  • What would go wrong if push took a plain struct node pointer instead?
Say it in 60 seconds

Memory Management 5 questions

Easy Technical round Fresher, Mid-level Practice question

7. Walk me through malloc, calloc, realloc and free. When would you pick each one?

What the interviewer is really testing:
Whether you know the four allocation functions precisely, including what they return on failure and what state the memory is in.
Answer frame:

malloc: a block of the given size, contents not initialised.

calloc: count and size, memory set to all-bits zero.

realloc: grows or shrinks a block, may move it and copies the old contents.

free: gives the block back; every allocation needs exactly one free.

Sample spoken answer:

"All three allocate on the heap and return NULL if they fail, so I always check. malloc takes a size in bytes and gives me memory with whatever garbage was there, so I use it when I'm about to fill the memory myself. calloc takes a count and an element size and returns memory set to zero. I use it when I want a clean array or struct, and because the count and size come in separately, a good library checks that multiplying them doesn't overflow. realloc resizes an existing block. It may extend it in place or move it to a new address and copy the old contents over, so any other pointers into the old block become invalid. free returns the block to the allocator. The rule is one free for every successful allocation, and never touch the memory afterwards."

Red flag to avoid:

Assuming malloc returns zeroed memory, or never checking for NULL.

They may ask next:
  • Is there any point casting the result of malloc in C?
  • What does realloc do if you pass it a NULL pointer?
Say it in 60 seconds
Medium Coding round Mid-level Practice question

8. What is wrong with writing p = realloc(p, new_size)? Show me the safe way to grow a buffer.

What the interviewer is really testing:
Whether you know that a failed realloc leaves the old block alive, and that a successful one can move it.
Answer frame:

The bug: if realloc fails it returns NULL and you have just lost your only pointer to the old block.

Safe pattern: assign to a temporary, check it, then replace the original.

Moves: on success the block may move, so every other pointer into it is stale.

Growth: grow by a factor, not by one, to avoid copying on every insert.

Sample spoken answer:

"If realloc can't find the memory, it returns NULL but leaves the original block untouched. With p = realloc(p, n), that NULL overwrites my only pointer to the old block, so I've leaked it and lost the data in one line. The safe pattern is to assign to a temporary first, check it for NULL, and only then assign it back. If it failed, the old buffer is still valid, so I can report the error or free it cleanly. Two other things matter. On success the block may have moved, so any pointers I kept into the middle of it are now dangling; I keep indexes instead of pointers if I need them. And when growing a dynamic array I usually double the capacity rather than add one element at a time, so the copying cost stays low overall."

Code:
int grow(int **arr, size_t *cap) {
    size_t new_cap = *cap ? *cap * 2 : 16;
    int *tmp = realloc(*arr, new_cap * sizeof **arr);
    if (tmp == NULL) {
        return -1;          // *arr is still valid and still owned by the caller
    }
    *arr = tmp;
    *cap = new_cap;
    return 0;
}
Red flag to avoid:

Writing p = realloc(p, n) without seeing the leak, or keeping raw pointers into a buffer that gets reallocated.

They may ask next:
  • Why is doubling the capacity better than adding a fixed amount each time?
  • How would you guard against new_cap times the element size overflowing?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

9. What lives on the stack and what lives on the heap in C? Why is returning the address of a local array a bug?

What the interviewer is really testing:
Whether you understand object lifetimes well enough to avoid dangling pointers, and know the proper fixes.
Answer frame:

Stack: local variables, freed automatically when the function returns; fast but limited in size.

Heap: malloc memory, lives until you free it; bigger, but you own the cleanup.

The bug: a local's lifetime ends at return, so its address dangles.

Fixes: caller passes a buffer, return malloc memory, or use static with care.

Sample spoken answer:

"Local variables have automatic storage, which in practice means the stack. They're created when the function is entered and gone when it returns, so they cost almost nothing but they can't outlive the call, and the stack is fairly small, so big arrays don't belong there. Heap memory from malloc lives until I call free, which suits data whose size I only know at run time or that has to outlive the function. So if I return the address of a local array, the caller gets a pointer to memory whose lifetime has ended. It may even seem to work until the next function call overwrites that stack space. I have three fixes. My favourite is to let the caller pass in a buffer and its size. Or I return malloc memory and document that the caller frees it. A static local also works, but every call then shares one buffer, which isn't thread-safe."

Red flag to avoid:

Saying the returned local address is fine because the value is still there when you print it.

They may ask next:
  • What happens if you declare a very large array as a local variable?
  • Why does the buggy version sometimes seem to work in testing?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

10. What does free actually do to your pointer? What goes wrong with a double free or a use after free?

What the interviewer is really testing:
Whether you understand heap corruption and why these bugs are dangerous, not just that they are forbidden.
Answer frame:

What free does: returns the block to the allocator; the pointer keeps the old address.

Double free: corrupts the allocator's bookkeeping; undefined behaviour, often a crash far away.

Use after free: reads or writes memory that may now belong to someone else.

Defences: clear ownership, NULL after free, sanitizers in testing.

Sample spoken answer:

"free hands the block back to the allocator, and that's all. It doesn't change my pointer, so the pointer still holds the old address, and it doesn't clear the memory. free on a NULL pointer is safe and does nothing. A double free is undefined behaviour. In practice the allocator keeps its own bookkeeping in and around freed blocks, so freeing twice can corrupt that and crash much later in unrelated code. Use after free is worse because it's quiet. The block may already be handed out to another part of the program, so my write silently changes someone else's data, and attackers have used exactly this to take over programs. My defences are a clear owner for every allocation, setting the pointer to NULL right after free so a second free is harmless, and running tests under AddressSanitizer or Valgrind, which report these at the exact line."

Red flag to avoid:

Thinking free sets the pointer to NULL, or that a use after free is harmless if nothing crashes.

They may ask next:
  • Setting the pointer to NULL after free: which bugs does it prevent and which does it not?
  • How does AddressSanitizer catch a use after free that would otherwise go unnoticed?
Say it in 60 seconds
Hard Situational round Mid-level, Senior Practice question

11. A long-running C service slowly uses more and more memory until it gets restarted every few days. How do you find the leak?

What the interviewer is really testing:
Whether you can investigate a leak methodically in a real system, and tell a true leak from growth that has another cause.
Answer frame:

Confirm: watch memory over time and tie the growth to a kind of traffic or event.

Reproduce: replay that traffic in a test setup and run the service under a leak detector.

Other causes: caches with no limit, fragmentation, lists that are never cleared.

Fix and prevent: fix the owner of the lost allocation, add a leak check to CI.

Sample spoken answer:

"First I'd confirm it's really a leak. I'd graph the process memory over days and look for what it tracks: request count, a certain kind of request, errors or reconnects. Growth that only climbs on error paths is a strong hint, because error paths are where frees get skipped. Then I'd reproduce it outside production by replaying that traffic against a build running under Valgrind or with LeakSanitizer, which reports each allocation that's never freed along with the stack that made it. The stack usually points straight at the owner. I'd also keep an open mind, because not all growth is a leak. It can be a cache with no size limit, a list that's never trimmed, or heap fragmentation. Once fixed, I'd add a leak check to the test run and keep the memory graph with an alert, so the restart stops being the fix."

Red flag to avoid:

Accepting the scheduled restart as the solution, or guessing at code changes without measuring.

They may ask next:
  • How would you tell a true leak from heap fragmentation?
  • Your leak detector is too slow to run on production traffic. What do you do instead?
Say it in 60 seconds

Storage & Const 3 questions

Medium Technical round Fresher, Mid-level Practice question

12. What storage classes does C have? Walk me through auto, register, static and extern.

What the interviewer is really testing:
Whether you can separate lifetime, scope and linkage, especially the two different meanings of static.
Answer frame:

auto: the default for locals; lives for the block.

register: a hint to keep it in a register; you can't take its address; compilers mostly ignore it.

static: a local keeps its value between calls; at file scope it hides the name from other files.

extern: declares something defined elsewhere, usually in another file.

Sample spoken answer:

"Storage classes decide how long a variable lives and who can see it. auto is the default for local variables: created when the block starts, gone when it ends, and nobody writes it out. register was a hint to keep the variable in a CPU register. You can't take its address, and modern compilers make their own choices, so it's mostly historical. static has two meanings. On a local variable it gives static lifetime, so it's initialised once and keeps its value between calls, which is handy for a counter. On a global variable or function it gives internal linkage, so the name is private to that source file. extern declares a variable or function that's defined somewhere else, usually in another file, without creating storage. Functions are extern by default."

Code:
int next_id(void) {
    static int id = 0;   // initialised once, keeps its value between calls
    return ++id;
}

static int cache_hits;   // file scope: invisible to other source files
Red flag to avoid:

Saying static at file scope means the value never changes, or mixing up lifetime and visibility.

They may ask next:
  • Is a static local variable safe to use from two threads at once?
  • What is the default initial value of a static variable, and of an auto one?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

13. You need one global variable shared by three source files. Where does the definition go, and what goes in the header?

What the interviewer is really testing:
Whether you understand declaration versus definition and how the linker resolves names across files.
Answer frame:

Definition: exactly one .c file creates the variable, with an initialiser if needed.

Declaration: the header says extern, which promises it exists without creating storage.

Linker: each file compiles alone; the linker ties uses to the one definition.

Sample spoken answer:

"I'd put the definition in exactly one source file, say config.c, where I write int log_level = 1. That's the line that creates storage. In config.h I write extern int log_level, which is only a declaration: it tells every file that includes the header that the variable exists and what type it is, without creating another copy. Each source file compiles on its own, and the linker then connects every use to the one definition. If I put the definition itself in the header, every file that includes it gets its own definition, and the build typically fails with a multiple definition error at link time. If nobody defines it, I get an undefined reference instead. I'd also ask whether it needs to be global at all; often a static variable with a getter and setter function is easier to reason about."

Code:
/* config.h */
extern int log_level;        /* declaration: no storage */

/* config.c */
#include "config.h"
int log_level = 1;           /* the one definition */

/* main.c */
#include "config.h"          /* uses log_level; the linker finds it */
Red flag to avoid:

Putting the initialised definition in the header, or not knowing the difference between a compile error and a link error.

They may ask next:
  • What error do you get if no file defines the variable, and at which stage?
  • Why is an include guard needed in the header, and what does it protect against?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

14. Explain the difference between const int *p, int *const p and const int *const p. Which one would you use for a function parameter?

What the interviewer is really testing:
Whether you can read const by position and use it to document intent in function signatures.
Answer frame:

Pointer to const: const int *p can move, but can't change the value through it.

Const pointer: int *const p can change the value, but can't point elsewhere.

Both: const int *const p locks the pointer and the value.

Parameters: const on the pointed-to data tells callers the function only reads it.

Sample spoken answer:

"I read it right to left from the name. const int *p is a pointer to a const int, so I can move p to point somewhere else, but I can't change the value through p. int *const p is a const pointer to an int, so the value can change but p is fixed to one address. const int *const p locks both. For function parameters the first form is the useful one. A signature like size_t count(const char *s) promises the caller the function only reads the string, and the compiler enforces it. Putting const on the pointer itself in a parameter matters less, because the pointer is a copy anyway. One thing I'd add: in C a const variable isn't a true constant expression, so I can't use a const int as a case label; I'd use an enum or a define there."

Red flag to avoid:

Mixing up which part is constant, or thinking const makes the data read-only for every pointer that can reach it.

They may ask next:
  • What happens if you cast away const and modify an object that was defined as const?
  • Why does strchr take a const char pointer but return a plain char pointer?
Say it in 60 seconds

Structs & Unions 2 questions

Medium Technical round Fresher, Mid-level Practice question

15. Why is sizeof a struct often bigger than the sum of its members? How would you shrink it?

What the interviewer is really testing:
Whether you understand alignment and padding, which matters for memory use, file formats and network code.
Answer frame:

Alignment: each type likes to start at an address that is a multiple of its alignment.

Padding: the compiler adds gaps between members and at the end so arrays of the struct stay aligned.

Shrink: order members from largest alignment to smallest.

Packing: compiler-specific, can slow access; not a portable file format.

Sample spoken answer:

"Most CPUs access data fastest, or only safely, when it starts at an address that's a multiple of its size, like a four-byte int on a four-byte boundary. So the compiler inserts padding. If I have a char, then an int, then a char, on a typical platform with four-byte ints the int needs three bytes of padding before it, and the struct gets three more at the end so that in an array the next element's int is aligned too. That's twelve bytes for six bytes of data. Reordering so the int comes first and the two chars follow brings it down to eight. Compilers also offer packing pragmas or attributes, but they're not standard, and unaligned access can be slower or even fault on some CPUs. And because padding bytes hold unspecified values, I don't compare structs with memcmp or write them raw to a file."

Code:
struct loose { char a; int n; char b; };   // typically 12 bytes
struct tight { int n; char a; char b; };   // typically 8 bytes
Red flag to avoid:

Saying sizeof a struct always equals the sum of its members, or reaching for packing as the default fix.

They may ask next:
  • How does offsetof help when you are debugging a struct layout?
  • Why is writing a struct straight to a file a problem when another machine reads it?
Say it in 60 seconds
Easy Technical round Fresher Practice question

16. What is the difference between a struct and a union? Give me a real case where a union is the right choice.

What the interviewer is really testing:
Whether you know how the memory is laid out in each and can name a sensible use, such as a tagged union.
Answer frame:

Struct: every member has its own storage; size is at least the sum.

Union: all members share the same storage; size is at least the largest member.

Use: a tagged union, where an enum field records which member is live.

Sample spoken answer:

"In a struct every member gets its own space, one after another, so I can use all of them at once and the size is at least the sum of the members. In a union every member starts at the same address and shares the same bytes, so the size is roughly the largest member, and only one member holds a meaningful value at a time. Writing one member overwrites the others. The real use is saving memory when a value can be one of several types. The common pattern is a tagged union: a struct with an enum that says what kind of value this is, plus a union holding an int, a double or a string pointer. I used that for a small config parser, where each setting could be a number, a flag or text. The code always checks the tag before reading the union."

Red flag to avoid:

Saying a union stores all its members at once, or using one without any tag to know which member is valid.

They may ask next:
  • What happens if you write to one member of a union and read a different one?
  • How would you write a tagged union for a value that can be an int, a double or a string?
Say it in 60 seconds

Bits & Macros 4 questions

Easy Coding round Fresher, Mid-level Practice question

17. Write the expressions to set, clear, toggle and test bit n of an integer.

What the interviewer is really testing:
Whether bitwise operators are second nature to you, and whether you know to use unsigned types for bit work.
Answer frame:

Mask: 1u shifted left by n selects bit n.

Set, clear, toggle: OR with the mask, AND with the inverted mask, XOR with the mask.

Test: shift right by n and AND with 1.

Safety: use unsigned; shifting by the type's width or more is undefined.

Sample spoken answer:

"Everything starts from a mask, which is 1u shifted left by n, a number with only bit n set. To set the bit I OR the value with the mask. To clear it I AND with the inverted mask, so every other bit is kept and bit n becomes zero. To toggle it I XOR with the mask. To test it I shift the value right by n and AND with 1, which gives 1 or 0. I use unsigned types and write 1u, not plain 1, because shifting a 1 into the sign bit of a signed int is undefined behaviour. Shifting by the full width of the type or more is also undefined, so if n can come from outside, I check it's in range first."

Code:
unsigned int flags = 0;
unsigned int n = 3;

flags |=  (1u << n);              // set bit 3
flags &= ~(1u << n);              // clear bit 3
flags ^=  (1u << n);              // toggle bit 3
unsigned int on = (flags >> n) & 1u;   // test bit 3: 0 or 1
Red flag to avoid:

Using logical operators like && instead of bitwise ones, or clearing a bit with XOR.

They may ask next:
  • How would you set bits 4 to 7 of a byte to a given 4-bit value, leaving the rest alone?
  • Why is 1 << 31 a problem with a 32-bit int but 1u << 31 is fine?
Say it in 60 seconds
Medium Coding round Fresher, Mid-level Practice question

18. Write a function that counts the number of 1 bits in an unsigned integer. Can you beat checking every bit?

What the interviewer is really testing:
Whether you know the n & (n - 1) trick and can explain why it works, not just recite it.
Answer frame:

Simple: test the lowest bit and shift, once per bit of the type.

Better: n & (n - 1) clears the lowest set bit, so loop once per 1 bit.

In practice: compilers offer a population count builtin that maps to one instruction on many CPUs.

Sample spoken answer:

"The simple version checks the lowest bit with n & 1, adds it to a count, and shifts n right until it's zero. That loops once per bit position. The better version uses the fact that n & (n - 1) clears the lowest set bit. Subtracting one flips the lowest 1 to 0 and all the zeros below it to 1s, so ANDing with the original wipes out just that one bit. If I loop until n is zero, I run once per set bit instead of once per bit position, which helps when numbers are sparse. The same trick checks for a power of two: n is non-zero and n & (n - 1) is zero. In production code I'd use the compiler's population count builtin, which many CPUs run as a single instruction, and keep this loop as the portable fallback."

Code:
unsigned int count_bits(unsigned int n) {
    unsigned int count = 0;
    while (n != 0) {
        n &= n - 1;     // clears the lowest set bit
        count++;
    }
    return count;
}
Red flag to avoid:

Writing a loop on a signed int that shifts right and never ends for negative numbers.

They may ask next:
  • Why does this need an unsigned type? What changes if someone passes a negative int?
  • How would you count bits quickly across millions of numbers using a lookup table?
Say it in 60 seconds
Easy Coding round Fresher Practice question

19. Swap two integers without using a temporary variable. Then tell me why you probably would not do it in real code.

What the interviewer is really testing:
Whether you know the XOR trick, and more importantly its trap and why a plain temporary is better.
Answer frame:

XOR swap: a ^= b, b ^= a, a ^= b.

Arithmetic swap: a = a + b and so on, which can overflow a signed int.

Trap: if both pointers refer to the same variable, XOR swap sets it to zero.

Real code: a temporary is clearer and the compiler makes it just as fast.

Sample spoken answer:

"The usual answer is the XOR swap: a gets a XOR b, then b gets b XOR a, which gives back the original a, then a gets a XOR b, which gives the original b. There's also an add and subtract version, but with signed ints the addition can overflow, and signed overflow is undefined behaviour, so I'd avoid it. The XOR version has its own trap. If I write it as a function taking two pointers and both point at the same variable, the first step turns it into zero and it stays zero. So I'd check for that. Then I'd tell the interviewer I wouldn't use it in real code. A temporary variable is clearer, has no traps, and the optimiser usually produces the same or better machine code. It's a nice puzzle, not a performance trick."

Code:
void xor_swap(int *a, int *b) {
    if (a == b) return;   // same object: XOR would zero it
    *a ^= *b;
    *b ^= *a;
    *a ^= *b;
}
Red flag to avoid:

Claiming the XOR swap is faster in modern code, or missing the same-address case.

They may ask next:
  • Can you write it as a one-line macro, and what goes wrong if you do?
  • Why would a compiler produce code just as fast for the version with a temporary?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

20. What goes wrong with a macro like #define SQUARE(x) x*x? When would you use an inline function instead, and when is a macro still right?

What the interviewer is really testing:
Whether you know the real macro traps and have a sound rule for when the preprocessor is the right tool.
Answer frame:

Precedence: text substitution, so SQUARE(a + 1) becomes a + 1*a + 1; parenthesise everything.

Double evaluation: SQUARE(i++) evaluates i++ twice, which is undefined behaviour.

Inline: type-checked, arguments evaluated once, visible in the debugger.

Macros still win: include guards, conditional builds, file and line in logs, token pasting.

Sample spoken answer:

"A macro is pure text substitution before the compiler sees the code. So SQUARE(a + 1) expands to a + 1 * a + 1, which is 2a + 1, not the square. Wrapping the parameter and the whole body in brackets fixes that, but not the second problem: SQUARE(i++) expands to i++ times i++, which modifies i twice without sequencing, and that's undefined behaviour. Macros also have no type checking and don't show up nicely in a debugger. A static inline function fixes all three: the argument is evaluated once, the types are checked, and the compiler can still inline it for speed. I still use macros where only the preprocessor can do the job: include guards, conditional compilation for different platforms, logging that captures the file and line, and stringizing or token pasting. For multi-statement macros I wrap the body in do while zero so they behave like one statement."

Code:
#define SQUARE_BAD(x) x * x
#define SQUARE(x)     ((x) * (x))
static inline int square(int x) { return x * x; }

// SQUARE_BAD(a + 1)  ->  a + 1 * a + 1   (wrong)
// SQUARE(i++)        ->  ((i++) * (i++))  (undefined behaviour)
// square(i++)        ->  argument evaluated once: fine
Red flag to avoid:

Saying macros are always faster than functions, or not knowing that SQUARE(i++) is undefined behaviour.

They may ask next:
  • Why do multi-statement macros get wrapped in do { } while (0)?
  • How would you write a logging macro that prints the file and line it was called from?
Say it in 60 seconds

Strings & Files 4 questions

Easy Coding round Fresher Practice question

21. Write a function that reverses a C string in place. What inputs would you test it with?

What the interviewer is really testing:
Whether you can write careful pointer or index code with the edge cases handled, including the empty string.
Answer frame:

Approach: one index from each end, swap, move inward until they meet.

Edge cases: empty string, one character, even and odd lengths.

Cost: O(n) time, O(1) extra space.

Trap: the caller must pass writable memory, not a string literal.

Sample spoken answer:

"I find the length with strlen, then keep two indexes, one at the start and one at the last character. I swap those two characters and move both inward until they meet or cross. That's O(n) time and no extra memory. The edge case that bites people is the empty string. If the length is zero and I compute length minus one with size_t, it wraps around to a huge number, so I return early when the length is below two. For tests I'd use an empty string, one character, an even length like abcd, an odd length like abc, and a string with spaces. I'd also mention to the interviewer that the caller must pass a writable char array. Passing a string literal would compile but modifying it is undefined behaviour and usually crashes."

Code:
#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;
    }
}
Red flag to avoid:

Computing len - 1 on an empty string, or testing the function on a string literal.

They may ask next:
  • How would you reverse the order of words in a sentence, in place?
  • What changes if the string holds multi-byte UTF-8 characters?
Say it in 60 seconds
Medium Technical round Fresher, Mid-level Practice question

22. What is the difference between char s[] = "hello" and char *s = "hello"? Why might writing to s[0] crash in one of them?

What the interviewer is really testing:
Whether you know where string literals live and that modifying one is undefined behaviour.
Answer frame:

Array: a writable copy of the characters, including the terminating zero, owned by the variable.

Pointer: points at the string literal itself, which you must not modify.

Crash: literals are often kept in read-only memory, so the write faults.

Habit: declare literal pointers as const char *.

Sample spoken answer:

"With char s[] = hello, the compiler creates an array of six chars, the five letters plus the terminating zero, and copies the literal into it. That array belongs to me, so s[0] = 'H' is perfectly fine, and sizeof s is six. With char *s = hello, s is just a pointer to the string literal itself. Modifying a string literal is undefined behaviour, and on most systems literals sit in a read-only section, so writing s[0] gives a segmentation fault. sizeof s here is the size of a pointer. C lets me assign a literal to a plain char pointer for historical reasons, which is why this compiles without complaint. My habit is to write const char * for anything that points at a literal, so the compiler catches the bad write for me."

Red flag to avoid:

Saying both forms create a modifiable copy, or not knowing that string literals may be read-only.

They may ask next:
  • If two pointers are initialised with the same literal, can they point to the same address?
  • What does sizeof give for each version, and why?
Say it in 60 seconds
Medium Coding round Fresher, Mid-level Practice question

23. Write code to read a text file line by line and print it. What is wrong with a loop that says while (!feof(fp))?

What the interviewer is really testing:
Whether you handle files correctly: check fopen, loop on the read call itself, tell errors from end of file, and close.
Answer frame:

Open: fopen with the right mode; check for NULL and report why with perror.

Loop: loop on fgets returning non-NULL, not on feof.

Why feof fails: it only turns true after a read has already failed, so the last line is handled twice.

Finish: check ferror, then fclose.

Sample spoken answer:

"I open the file with fopen in read mode and check for NULL straight away, printing the reason with perror, because a missing file or a permissions problem is normal and shouldn't crash. Then I loop on fgets itself: while fgets returns non-NULL, I have a line. fgets stops at the newline or when the buffer is full, and it keeps the newline, so a very long line comes back in pieces. After the loop I check ferror to tell a read error from a normal end of file, and I always fclose. The feof loop is a classic bug because feof doesn't look ahead. It only becomes true after a read has already hit the end and failed. So the loop runs one extra time, and that failed read's leftover buffer usually gets processed twice, which shows up as a duplicated last line."

Code:
#include <stdio.h>

int print_file(const char *path) {
    FILE *fp = fopen(path, "r");
    if (fp == NULL) {
        perror(path);
        return -1;
    }
    char line[256];
    while (fgets(line, sizeof line, fp) != NULL) {
        fputs(line, stdout);
    }
    int failed = ferror(fp);
    fclose(fp);
    return failed ? -1 : 0;
}
Red flag to avoid:

Not checking fopen for NULL, or driving the loop with feof.

They may ask next:
  • When does the b in a mode like rb actually matter?
  • How would you read a line of any length without a fixed-size buffer?
Say it in 60 seconds
Medium Situational round Fresher, Mid-level, Senior Practice question

24. In code review you see strcpy copying a user-supplied name into a 32-byte buffer. The author says names are never that long. What do you do?

What the interviewer is really testing:
Whether you see a buffer overflow as a security bug, know the safer functions and their own traps, and push back politely.
Answer frame:

Risk: input you don't control can be any length; an overflow corrupts memory and can be exploited.

Fix: check the length, or use snprintf with the buffer size; know that strncpy may not add the terminator.

Tone: explain the risk with a concrete input, suggest the change, don't just block.

Sample spoken answer:

"I wouldn't approve it. The author may be right about normal users, but the input comes from outside, so an attacker or a buggy client can send anything. strcpy copies until it finds a zero byte and knows nothing about the buffer size, so a long name writes past the end, corrupts whatever sits next to it, and in the worst case lets someone control the program. I'd suggest checking the length first and rejecting names that are too long, or copying with snprintf using sizeof the buffer, which always terminates the string and tells you if it was cut short. I'd mention that strncpy isn't a drop-in fix, because it doesn't add the terminating zero when the source fills the buffer. I'd leave the comment with a concrete example input so it's clearly not a style point, and offer to help with the change."

Red flag to avoid:

Accepting it because names are usually short, or recommending strncpy with no mention of the missing terminator.

They may ask next:
  • Why is strncpy not a safe replacement for strcpy on its own?
  • How would you find every other risky string copy in a large codebase?
Say it in 60 seconds

Undefined Behaviour 3 questions

Hard Technical round Mid-level, Senior Practice question

25. What is undefined behaviour in C? Give me a few examples, and explain why a program with it can work today and break tomorrow.

What the interviewer is really testing:
Whether you understand that the compiler assumes undefined behaviour never happens, and how that turns small mistakes into strange bugs.
Answer frame:

Meaning: the standard sets no requirements at all on what happens.

Examples: signed overflow, out-of-bounds access, null dereference, use after free, i = i++.

Why it bites: the optimiser assumes it never happens and reasons from that.

Related terms: implementation-defined and unspecified behaviour are different, and tamer.

Sample spoken answer:

"Undefined behaviour means the C standard puts no requirements on what the program does. It might crash, give the expected answer, or do something odd far away. Common examples are signed integer overflow, reading past the end of an array, dereferencing a null or freed pointer, reading an uninitialised local, and modifying a variable twice without sequencing, like i = i++. The reason it can work today and break tomorrow is that the optimiser is allowed to assume it never happens. So if I write a loop that relies on a signed int wrapping to negative, the compiler may decide the loop can never end, or it may delete a null check that comes after a dereference. A new compiler version or a higher optimisation level changes the result. It's different from implementation-defined behaviour, like the size of an int, which each compiler must document. To catch it I use warnings and the undefined behaviour sanitizer in tests."

Red flag to avoid:

Saying undefined behaviour just means you get a random value, or that it is safe if the tests pass.

They may ask next:
  • What is the difference between undefined, unspecified and implementation-defined behaviour?
  • Why can a compiler remove a null check that comes after the pointer has already been dereferenced?
Say it in 60 seconds
Hard Technical round Mid-level, Senior Practice question

26. Why does comparing -1 with an unsigned value, or with sizeof, give the wrong answer in C? Show me a loop that never ends because of it.

What the interviewer is really testing:
Whether you know the usual arithmetic conversions and can spot signed and unsigned mix-ups in real code.
Answer frame:

Conversion: when int meets an unsigned int of the same rank, the int is converted to unsigned.

Result: -1 becomes the largest unsigned value, so -1 < 1u is false.

sizeof: it returns size_t, which is unsigned, so the same trap applies.

Defence: turn on sign-compare warnings and keep counters and sizes in one type.

Sample spoken answer:

"When you mix a signed int and an unsigned int in one expression, C converts the int to unsigned before comparing. So -1 turns into the largest unsigned value, and -1 < 1u is false. sizeof returns size_t, which is unsigned, so if (-1 < sizeof(int)) is false too, which surprises people. The loop version is counting down with an unsigned index: for size_t i = n - 1; i >= 0; i--. An unsigned value is always zero or more, so the condition is always true. When i reaches zero and decrements, it wraps to a huge number instead of stopping, and the loop reads far outside the array. Note that unsigned wraparound itself is well defined; the bug is in the logic. My defences are compiling with -Wall and -Wextra, which turns on the sign-compare warning in C, and writing the countdown as for i = n; i-- > 0."

Code:
int x = -1;
unsigned int y = 1;
if (x < y) puts("less");
else       puts("not less");    // this one prints

for (size_t i = n - 1; i >= 0; i--) { /* never stops */ }
for (size_t i = n; i-- > 0; )      { /* correct countdown */ }
Red flag to avoid:

Saying the compiler compares the real numeric values, or fixing the loop by casting without understanding the conversion.

They may ask next:
  • What does integer promotion do to two unsigned char values before they are added?
  • Is unsigned overflow undefined behaviour like signed overflow?
Say it in 60 seconds
Medium Situational round Mid-level, Senior Practice question

27. A compiler upgrade just added dozens of new warnings the day before a release, and a teammate wants to switch them off. What do you do?

What the interviewer is really testing:
Whether you treat warnings in C as possible real bugs while still being practical about a deadline.
Answer frame:

Triage: group the warnings by type; some point to real undefined behaviour.

Deadline: fix the dangerous ones now, record the rest instead of hiding them.

Options: ship on the old compiler, or silence narrowly with a tracked ticket.

Afterwards: get to zero warnings, then treat new ones as errors.

Sample spoken answer:

"I wouldn't switch them off wholesale, because in C a new warning is often the compiler pointing at a real bug, but I also wouldn't block the release to fix dozens of lines in one night. I'd spend an hour sorting them by type. Things like uninitialised variables, sign comparisons, format string mismatches and possible buffer overruns I'd look at properly, since those can be real undefined behaviour. Style warnings like unused parameters can wait. If we're short on time, the safer choice is often to ship with the compiler version we tested on and do the upgrade right after the release. If we must ship on the new one, I'd silence only the specific low-risk warnings, not all of them, and log a ticket to clean them up. Once we reach zero warnings, I'd make new ones fail the build so they don't pile up again."

Red flag to avoid:

Adding a flag that silences all warnings, or insisting every warning be fixed before the release without triage.

They may ask next:
  • Which warnings would you treat as must-fix before any release, and why?
  • What are the downsides of turning every warning into an error from day one?
Say it in 60 seconds

Real Work 3 questions

Medium Behavioral round Fresher, Mid-level, Senior Practice question

28. Tell me about a crash or memory corruption bug you tracked down in a C program. How did you find the real cause?

What the interviewer is really testing:
Whether you debug methodically with the right tools, and whether you found the cause rather than moving the crash somewhere else.
Answer frame:

Symptom: what failed, and why it was hard to reproduce.

Tools: debugger backtrace, core dump, sanitizer or Valgrind, a smaller reproduction.

Cause and fix: the actual line at fault, the fix and the test you added.

Sample spoken answer:

"In my final-year project we built a small log parser in C, and it crashed now and then with a segmentation fault inside free, which made no sense at first because the free call itself looked fine. The backtrace in gdb only showed where the damage was noticed, not where it happened. So I rebuilt with AddressSanitizer and ran the same input files. It stopped on the first bad write, a heap buffer overflow in the code that copied a field into a fixed buffer. We'd sized it for the field plus nothing, forgetting the terminating zero, so every field of exactly maximum length wrote one byte past the end and damaged the allocator's data. The fix was one character in the size, plus a check on the length. I added the long-field case as a test, and from then on we ran the sanitizer build in every test run."

Red flag to avoid:

A story where the fix was adding a NULL check or making a buffer bigger without ever finding the line that caused it.

They may ask next:
  • Why does a heap overflow often crash later in free or malloc rather than at the bad write?
  • What would you do if the crash only happened on a machine where you could not run a sanitizer?
Say it in 60 seconds
Hard Behavioral round Mid-level, Senior Practice question

29. Tell me about a time C code worked in a debug build but broke once optimisation was turned on. What was really going on?

What the interviewer is really testing:
Whether you have met undefined behaviour in the wild and know to fix the code rather than blame or switch off the optimiser.
Answer frame:

Symptom: what changed between builds and how you noticed.

Diagnosis: narrowing it to one function, reading warnings, using sanitizers.

Root cause: the undefined behaviour the optimiser was entitled to exploit.

Fix: correct the code, keep optimisation on, add a guard against repeats.

Sample spoken answer:

"At my last company, a checksum routine gave different answers in the release build than in the debug build. The first reaction in the team was that the compiler had a bug, but that's almost never true. I cut it down to one function and built it with the undefined behaviour sanitizer, which flagged signed integer overflow. The code added bytes into an int accumulator and relied on it wrapping around. In the debug build the machine just wrapped. With optimisation on, the compiler assumed signed overflow can't happen and rearranged the arithmetic, so the result changed. The fix was to make the accumulator an unsigned type of a fixed width, where wraparound is defined, which is what the algorithm wanted all along. We kept full optimisation, and I added the sanitizer build to CI so the next one would show up in tests, not in the field."

Red flag to avoid:

Blaming the compiler and turning optimisation off, with no idea what the code did wrong.

They may ask next:
  • Why is lowering the optimisation level not a real fix?
  • What other kinds of undefined behaviour commonly show up only in optimised builds?
Say it in 60 seconds
Medium Behavioral round Mid-level, Senior Practice question

30. Tell me about a time you had to settle who allocates and who frees memory across a C module or library boundary.

What the interviewer is really testing:
Whether you design C interfaces with clear ownership, which is how real C teams avoid leaks and double frees.
Answer frame:

Problem: unclear ownership causing leaks, double frees or mismatched allocators.

Decision: the rule you chose, such as caller-provided buffers or paired create and destroy functions.

Making it stick: naming, comments in the header, tests and review.

Sample spoken answer:

"At my last company we had a parsing library used by two other teams, and the header didn't say who owned the strings it returned. One team freed them, the other didn't, so we had a leak on one side and a double free waiting to happen on the other. I proposed one rule for the whole library: anything the library allocates, the library frees. Every object got a create function and a matching destroy function, and functions that return text either write into a buffer the caller passes with its size, or return a pointer that stays valid until the owning object is destroyed. I wrote the ownership rule at the top of the header and next to each function, and we added leak checks with Valgrind to both teams' test runs. The double free reports stopped, and new functions followed the same pattern without argument."

Red flag to avoid:

Saying ownership was obvious and needed no documentation, or that each caller should just be careful.

They may ask next:
  • Why can it be a problem to free memory in one module that was allocated by another?
  • When would you choose a caller-provided buffer over returning allocated memory?
Say it in 60 seconds
Were you asked something else? Share it A person checks every question before it goes on the site. No name is shown.
For the call itself

The questions above are the prep. The call has ten more.

ClapAssist is an AI interview assistant for Mac and Windows. It listens to the interview on your computer and shows you what to say, in short lines you can read while you talk. Your resume and notes are never stored on our servers. It stays out of screen share on every plan; only you can see it.

Download ClapAssist with 10 free minutes
Mac and Windows · Stays out of screen share · No card