- C interviews revolve around pointers, memory, storage classes, structs and undefined behavior. Each has a small set of rules that explains nearly every question.
- Know the difference between an array and a pointer (sizeof, assignability, decay), and between malloc, calloc, realloc and free, including the
p = realloc(p, n)leak. staticchanges lifetime for locals and linkage for globals. Interviewers ask it in nearly every C round.- For tricky outputs, apply four rules: integer promotion, signed-to-unsigned conversion, macro text substitution, and sequence points. Every output below was compiled and checked with GCC.
- When something is undefined or implementation-defined, say so. "It depends on the compiler, and here is why" beats a confident guess.
C is still the first language for many engineering students in India, so it remains a staple of campus technical interviews, and it is the main language for embedded, firmware, driver and systems roles. C interviews are less about syntax and more about whether you understand what the machine is doing: where a variable lives, how long it lives, what a pointer actually holds, and what the compiler is allowed to assume.
All code below compiles with gcc -Wall -Wextra. Outputs marked as implementation-defined were checked with GCC on x86, and the reason they could differ is explained. Compile and play with them yourself; predicting an output and then checking it is the fastest way to learn these rules.
Who Asks C Questions, and How
| Context | Typical questions |
|---|---|
| Campus technical rounds (service and product) | Pointers, storage classes, malloc vs calloc, output prediction, string functions |
| Embedded and firmware roles | volatile, bit manipulation, struct packing, endianness, ISR rules, memory layout |
| Semiconductor and systems companies | Memory layout, function pointers, undefined behavior, writing memcpy or a linked list |
| Written MCQ tests | Tricky outputs on precedence, promotion, macros, sizeof |
Basics and Data Types: Questions 1-8
Q1. What are the stages of compiling a C program?
- Preprocessing (
gcc -E): expands#includeand macros, handles conditional compilation, strips comments. - Compilation (
gcc -S): translates the preprocessed code into assembly. - Assembly (
gcc -c): converts assembly into an object file with machine code and unresolved symbols. - Linking: combines object files and libraries, resolves symbols like
printf, and produces the executable.
A follow-up: "undefined reference to foo" is a linker error (declared but never defined or not linked), while "implicit declaration of foo" comes from the compiler.
Q2. What are the sizes of C data types?
They are implementation-defined, with minimum ranges set by the standard: char is exactly 1 byte (but a byte may exceed 8 bits in theory), short and int are at least 16 bits, long at least 32, long long at least 64. On typical 64-bit Linux (LP64), int is 4 bytes, long and pointers are 8. On 64-bit Windows (LLP64), long stays 4 bytes. When exact widths matter (network protocols, registers), use <stdint.h> types like uint32_t.
Q3. Is char signed or unsigned?
Implementation-defined. It is signed on GCC for x86 and unsigned by default on ARM Linux. So char c = 255; printf("%d", c); prints -1 on x86 and 255 on many ARM targets. Use unsigned char for raw bytes and signed char when you need small signed numbers.
Q4. What is the difference between declaration and definition?
A declaration introduces a name and its type (extern int count;, int add(int, int);). A definition also allocates storage or provides the body (int count = 0;, the function body). You can declare many times but define once across the program (the one-definition rule).
Q5. What is the difference between ++i and i++?
Both increment i. ++i evaluates to the new value; i++ evaluates to the old value. As standalone statements (i++; in a for loop) they are equivalent, and compilers generate identical code for ints. They differ only when the expression's value is used.
Q6. What are integer promotion and the usual arithmetic conversions?
Integer promotion: in expressions, types smaller than int (char, short, bit-fields) are converted to int first. So char a = 100, b = 100; int c = a + b; gives 200, not an overflowed char. Usual arithmetic conversions: when two operands have different types, they are converted to a common type. The trap: when a signed int meets an unsigned int of the same rank, the signed value is converted to unsigned. That is the source of the most famous C output question (see the tricky outputs section).
Q7. What is the difference between = and == and why do some people write if (5 == x)?
= assigns and == compares. if (x = 5) assigns 5 and is always true. Writing the constant on the left ("Yoda condition") makes the typo 5 = x a compile error. Modern compilers warn about assignment in conditions with -Wall, which is the better safeguard.
Q8. What does main return, and what are argc and argv?
main returns an int status to the environment: 0 or EXIT_SUCCESS for success, non-zero for failure. Reaching the end of main without a return is treated as returning 0 (since C99). argc is the argument count and argv an array of strings, where argv[0] is usually the program name and argv[argc] is NULL.
Pointers: Questions 9-20
Q9. What is a pointer?
A variable that stores a memory address. Its type tells the compiler what lives at that address, which decides how many bytes a dereference reads and how pointer arithmetic scales. int *p = &x; stores x's address; *p reads or writes x.
Q10. How does pointer arithmetic work?
Adding n to a pointer moves it by n × sizeof(*p) bytes, not n bytes. If int *p holds 1000 and int is 4 bytes, p + 1 is 1004. Subtracting two pointers into the same array gives the number of elements between them (type ptrdiff_t). Arithmetic outside the bounds of an array (except one past the end) is undefined behavior, even without dereferencing.
Q11. What is the difference between an array and a pointer?
int a[10]; | int *p = a; | |
|---|---|---|
| What it is | A block of 10 ints | A variable holding an address |
sizeof | 40 (10 × 4) | Size of a pointer (4 or 8) |
| Assignable | No, a = p; is an error | Yes |
&x type | int (*)[10], pointer to the whole array | int ** |
In most expressions, an array decays to a pointer to its first element, which is why a[i] and p[i] look the same (both mean *(x + i)). The exceptions are sizeof, unary &, and string literals used to initialize arrays.
Q12. Why does sizeof(arr) inside a function give the wrong size?
Because an array parameter is really a pointer: void f(int arr[10]) is identical to void f(int *arr). Inside, sizeof(arr) is the pointer size. That is why C functions take the length as a separate parameter, like qsort(base, n, size, cmp).
Q13. Write a function that swaps two integers.
#include <stdio.h>
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
int main(void) {
int x = 3, y = 7;
swap(&x, &y);
printf("%d %d\n", x, y); /* 7 3 */
return 0;
}
C passes everything by value. To modify the caller's variables, pass their addresses. A swap(int a, int b) version swaps only its own copies. Interviewers may ask for the XOR-swap trick; mention that it breaks when both pointers point to the same variable (the value becomes 0) and is not faster on modern CPUs.
Q14. What are NULL, void pointers and wild pointers?
- NULL pointer: a pointer that points nowhere, guaranteed to compare unequal to any valid object's address. Dereferencing it is undefined behavior (usually a crash).
- void pointer (
void *): a generic pointer that can hold any object address. You cannot dereference it or do arithmetic on it in standard C without casting.mallocreturnsvoid *, which converts implicitly to any object pointer type in C (no cast needed, unlike C++). - Wild pointer: an uninitialized pointer holding garbage. Always initialize pointers, to NULL if nothing else.
Q15. What is a dangling pointer? Give two ways to create one.
int *make_dangling(void) {
int local = 42;
return &local; /* BUG: local dies when the function returns */
}
void use_after_free(void) {
int *p = malloc(sizeof *p);
free(p);
*p = 5; /* BUG: p still holds the old address */
}
Fixes: return heap memory or a static variable instead of a local's address, and set pointers to NULL right after free so accidental reuse crashes predictably instead of corrupting memory.
Q16. Explain const with pointers.
const int *p1; /* pointer to const int: *p1 = 5 is an error, p1 = &y is fine */
int const *p2; /* same as p1 */
int *const p3 = &x; /* const pointer to int: *p3 = 5 is fine, p3 = &y is an error */
const int *const p4 = &x; /* neither the pointer nor the pointee can change */
Read declarations right to left: "p3 is a const pointer to int". Use const T * for function parameters you only read, like size_t strlen(const char *s); it documents intent and lets callers pass read-only data.
Q17. What is the difference between char *s = "hello" and char s[] = "hello"?
char s[] = "hello"; creates a 6-byte array on the stack (or in data) initialized with a copy of the string; you can modify it. char *s = "hello"; makes s point to a string literal, usually stored in read-only memory; modifying it (s[0] = 'H') is undefined behavior and typically segfaults. Also, sizeof gives 6 for the array but the pointer size for the pointer. Prefer const char *s = "hello"; so the compiler catches modification attempts.
Q18. What is a pointer to a pointer, and when do you need one?
A variable holding the address of a pointer. You need it when a function must change the caller's pointer itself, such as inserting at the head of a linked list or allocating memory for the caller.
#include <stdlib.h>
struct Node { int val; struct Node *next; };
int push_front(struct Node **head, int val) {
struct Node *n = malloc(sizeof *n);
if (n == NULL) return -1;
n->val = val;
n->next = *head;
*head = n; /* updates the caller's head pointer */
return 0;
}
/* usage: struct Node *list = NULL; push_front(&list, 10); */
Q19. What are function pointers? Give a real use.
A function pointer stores the address of a function so you can call it indirectly. Uses: callbacks (comparators for qsort), dispatch tables (command handlers, state machines), and plug-in style interfaces (the Linux kernel's file_operations struct is a table of function pointers).
#include <stdio.h>
#include <stdlib.h>
static int cmp_desc(const void *a, const void *b) {
int x = *(const int *)a, y = *(const int *)b;
return (x < y) - (x > y); /* avoids overflow of y - x */
}
int main(void) {
int v[] = {5, 1, 9, 3};
size_t n = sizeof v / sizeof v[0];
qsort(v, n, sizeof v[0], cmp_desc);
for (size_t i = 0; i < n; i++) printf("%d ", v[i]); /* 9 5 3 1 */
printf("\n");
int (*op)(const void *, const void *) = cmp_desc; /* function pointer variable */
printf("%d\n", op(&v[0], &v[1])); /* -1: 9 sorts before 5 */
return 0;
}
Note the comparator: return y - x; is a classic bug because it can overflow for large values of opposite signs.
Q20. How are 2D arrays passed to functions?
All dimensions except the first must be known: void print(int m[][4], int rows), which is the same as int (*m)[4], a pointer to arrays of 4 ints. An int ** is a different thing (an array of row pointers) and is not compatible with int m[3][4]. Since C99 you can use variable-length array parameters: void print(int rows, int cols, int m[rows][cols]).
Dynamic Memory: Questions 21-28
Q21. Explain the memory layout of a C program.
Typical layout on Linux; exact placement is up to the platform. int g = 5; goes to data, static int s; goes to BSS, local variables go on the stack, and malloc memory comes from the heap.
Q22. malloc vs calloc vs realloc vs free?
| Function | What it does | Notes |
|---|---|---|
malloc(size) | Allocates size bytes, uninitialized | Contents are garbage |
calloc(n, size) | Allocates n × size bytes, zeroed | Checks n × size for overflow |
realloc(p, size) | Resizes a block, possibly moving it | Copies old contents; returns NULL on failure and leaves p valid |
free(p) | Releases a block | free(NULL) is a safe no-op |
Q23. What is wrong with p = realloc(p, newsize)?
If realloc fails, it returns NULL but the old block is still allocated. Assigning the result straight to p overwrites the only pointer to it: a memory leak, plus a NULL p you will likely dereference.
int *grow(int *arr, size_t new_count) {
int *tmp = realloc(arr, new_count * sizeof *tmp);
if (tmp == NULL) {
/* arr is still valid; caller decides what to do */
return NULL;
}
return tmp;
}
In production code, also check that new_count * sizeof *tmp cannot overflow before calling.
Q24. What happens if you free memory twice?
A double free is undefined behavior. It can corrupt the allocator's internal lists, crash later at an unrelated point, or be exploited by attackers. Allocators like glibc detect some cases and abort with "double free detected". Setting the pointer to NULL after free makes a second free(NULL) harmless.
Q25. What is a memory leak and how do you find one?
Heap memory that is no longer reachable but was never freed. In a long-running process (a server, a firmware loop) leaks accumulate until the system runs out of memory. Tools: Valgrind (valgrind --leak-check=full ./app) and AddressSanitizer / LeakSanitizer (gcc -fsanitize=address -g), which also catch use-after-free and buffer overflows. Mentioning these tools in interviews signals real experience.
Q26. Stack vs heap allocation?
Stack allocation is automatic, extremely fast (just moving the stack pointer), and freed when the function returns, but the stack is small (often 1 to 8 MB for a thread) and sizes are fixed per frame. Heap allocation is manual, slower, and can be any size and lifetime, but must be freed and can fragment. Large arrays should go on the heap: int big[10000000]; as a local will likely overflow the stack.
Q27. Does free know how many bytes to release?
Yes. The allocator stores bookkeeping metadata (typically in a header just before the returned address) recording the block size. That is why you pass only the pointer, and why freeing a pointer that did not come from malloc, or one offset from it, corrupts the heap.
Q28. Should you cast the result of malloc in C?
Not in C. void * converts implicitly to any object pointer type, and an unnecessary cast can hide a missing #include <stdlib.h> in old C89 code. The idiomatic form is T *p = malloc(n * sizeof *p);, which stays correct if p's type changes. In C++, the cast is required, but C++ code should use new or containers anyway.
Storage Classes and Qualifiers: Questions 29-34
Q29. What are the storage classes in C?
| Specifier | Storage | Default value | Scope | Lifetime |
|---|---|---|---|---|
auto | Stack | Garbage | Block | Until block exits |
register | Register (a hint) | Garbage | Block | Until block exits; cannot take its address |
static (local) | Data/BSS | Zero | Block | Whole program |
static (global) | Data/BSS | Zero | File only (internal linkage) | Whole program |
extern | Data/BSS | Zero (where defined) | Across files | Whole program |
Q30. What does static do to a local variable?
It keeps its value between calls: the variable is initialized once (before program start, so the initializer must be a constant) and lives for the whole program, though it is visible only inside the function.
#include <stdio.h>
int next_id(void) {
static int id = 0; /* initialized once, not on every call */
return ++id;
}
int main(void) {
int a = next_id();
int b = next_id();
int c = next_id();
printf("%d %d %d\n", a, b, c); /* 1 2 3 */
return 0;
}
Notice the separate statements. printf("%d %d %d", next_id(), next_id(), next_id()) may print "3 2 1", because the order of evaluating function arguments is unspecified. With GCC on x86 it did print 3 2 1 in our test. That makes a great follow-up question.
Q31. What does static do to a global variable or function?
It gives it internal linkage: the name is visible only within that source file. This is C's main encapsulation tool, letting two files each have a private helper named init without a linker clash. Make every file-local function and global static by default.
Q32. What is extern?
extern int count; declares a variable defined in another file, without allocating storage. The usual pattern: define int count = 0; in one .c file and declare extern int count; in a header included wherever it is needed. Functions are extern by default.
Q33. What does volatile mean, and when is it needed?
volatile tells the compiler the value can change outside the program's visible control flow, so every read and write must actually access memory and cannot be cached in a register or removed. Use it for memory-mapped hardware registers, variables modified by an interrupt service routine, and variables changed by signal handlers (with volatile sig_atomic_t). It is not a thread-synchronization tool in C: it provides no atomicity or memory ordering. Use C11 <stdatomic.h> or mutexes for threads.
#include <stdint.h>
#define UART_STATUS (*(volatile uint32_t *)0x40001000u) /* example register address */
#define TX_READY (1u << 5)
void wait_for_tx(void) {
while ((UART_STATUS & TX_READY) == 0) {
/* without volatile, the compiler may read once and loop forever */
}
}
Q34. Can a variable be both const and volatile?
Yes. const volatile uint32_t *status describes a read-only hardware status register: your code must not write it (const), but the hardware can change it, so every read must go to memory (volatile).
Preprocessor and Macros: Questions 35-38
Q35. Why do macros need parentheses?
Macros are text substitution before compilation. #define SQUARE(x) x*x turns SQUARE(2+3) into 2+3*2+3 = 11, not 25. The fix is to parenthesize every parameter and the whole body: #define SQUARE(x) ((x)*(x)). Even then, SQUARE(i++) increments i twice (undefined behavior here, since the two increments are unsequenced). That is why a static inline function is usually better.
Q36. Macro vs inline function?
An inline function has type checking, evaluates each argument exactly once, respects scope, and is debuggable. Macros are untyped and can surprise, but can do things functions cannot: stringification (#x), token pasting (a##b), generic code before C11 _Generic, and including __FILE__ and __LINE__ in logging macros.
Q37. What are include guards?
#ifndef INVENTORY_H
#define INVENTORY_H
struct Item { int id; int qty; };
int reserve(struct Item *it, int qty);
#endif /* INVENTORY_H */
They prevent a header's contents being processed twice when it is included through several paths, which would redefine types. #pragma once does the same, is widely supported, but is not part of the C standard.
Q38. How do you write a multi-statement macro safely?
Wrap it in do { ... } while (0) so it behaves like one statement and works correctly in an unbraced if/else:
#define LOG_ERR(msg) do { fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, msg); errors++; } while (0)
Structures, Unions and Bit-Fields: Questions 39-43
Q39. What is structure padding? Predict sizeof.
struct A { char c; int i; char d; }; /* 1 + 3 pad + 4 + 1 + 3 pad = 12 */
struct B { char c; char d; int i; }; /* 1 + 1 + 2 pad + 4 = 8 */
struct C { char c; double x; int i; }; /* 1 + 7 + 8 + 4 + 4 pad = 24 */
struct D { double x; int i; char c; }; /* 8 + 4 + 1 + 3 pad = 16 */
Each member is placed at an offset that is a multiple of its alignment, and the struct's total size is rounded up to a multiple of its largest member's alignment, so arrays of the struct keep every element aligned. These values were verified with GCC on x86, where int has 4-byte and double 8-byte alignment. Ordering members from largest to smallest alignment minimizes padding, which matters for large arrays of structs and for cache efficiency.
Q40. How do you remove padding, and should you?
With compiler extensions: __attribute__((packed)) in GCC/Clang or #pragma pack(1). Packed structs are used to match wire formats or hardware layouts. The costs: unaligned access is slower on some CPUs and faults on others, and taking a pointer to a packed member is risky. For network protocols, many teams serialize fields explicitly byte by byte instead, which also handles endianness.
Q41. Structure vs union?
A struct's members each have their own storage, so its size is at least the sum. A union's members share the same storage, so its size is that of the largest member (plus padding), and only one member is meaningful at a time. Uses: saving memory in tagged variants (a token that is either an int or a float, with a separate tag field), and viewing the same bytes in different ways (type punning through a union is permitted in C, unlike C++).
Q42. What are bit-fields?
struct Flags {
unsigned int ready : 1;
unsigned int error : 1;
unsigned int mode : 3; /* values 0..7 */
unsigned int reserved: 27;
};
Bit-fields pack small values into fewer bits. Their layout (bit order, padding, whether they straddle storage units) is implementation-defined, so they are unreliable for matching hardware registers across compilers. Embedded code often uses explicit masks and shifts instead. You cannot take the address of a bit-field.
Q43. Can you compare two structs with ==?
No, C does not define == for structs. You must compare member by member. memcmp is tempting but wrong in general, because padding bytes can contain garbage that differs between two logically equal structs. Assignment (s1 = s2) is allowed and copies all members.
Undefined, Unspecified and Implementation-Defined Behavior: Questions 44-47
Q44. What is the difference between undefined, unspecified and implementation-defined behavior?
- Undefined behavior (UB): the standard imposes no requirements. Anything can happen, and optimizers assume it never happens. Examples: signed integer overflow, out-of-bounds access, dereferencing NULL, use after free, unsequenced modifications like
i = i++. - Unspecified: one of several valid outcomes, not necessarily documented and possibly different each time. Example: the order in which function arguments are evaluated.
- Implementation-defined: the implementation picks and documents a behavior. Examples: whether char is signed, sizeof(int), right shift of negative numbers.
Q45. Why is signed overflow undefined but unsigned overflow is not?
Unsigned arithmetic is defined to wrap modulo 2n, so UINT_MAX + 1 == 0. Signed overflow is UB, which lets compilers optimize: for example, assuming i + 1 > i is always true for a signed i, which simplifies loop analysis. The consequence: an overflow check like if (a + b < a) on signed ints may be deleted by the optimizer. Check before the operation instead: if (b > 0 && a > INT_MAX - b), or use GCC/Clang's __builtin_add_overflow.
Q46. What are sequence points (sequencing), and which expressions are undefined?
C requires that between two sequence points (C11 calls them "sequenced before" relations), an object is modified at most once, and if modified, read only to compute its new value. So these are undefined: i = i++;, i = i++ + ++i;, a[i] = i++;, printf("%d %d", i, i++);. Sequence points include the end of a full expression (the semicolon), &&, ||, the comma operator, ?:, and a function call after its arguments are evaluated. The right interview answer to "what does i = i++ + ++i print?" is "it is undefined, so no answer is correct". Some MCQ tests expect a number anyway; mention UB and then give the common compiler's result if pressed.
Q47. What is the strict aliasing rule?
The compiler may assume that pointers to different types do not point to the same memory (with exceptions such as character types and compatible types). So reading a float through an int * is UB, and optimized code may produce surprising results. To reinterpret bytes safely, use memcpy into a variable of the other type (compilers optimize it away) or a union.
Strings and Small Coding Questions: Questions 48-50
Q48. Implement strlen and strcpy, and explain why strcpy is dangerous.
#include <stddef.h>
size_t my_strlen(const char *s) {
const char *p = s;
while (*p) p++;
return (size_t)(p - s);
}
char *my_strcpy(char *dst, const char *src) {
char *ret = dst;
while ((*dst++ = *src++) != '\0') { }
return ret;
}
strcpy has no idea how big dst is, so a long src overflows the buffer: the classic buffer overflow vulnerability. strncpy is not a full fix: it does not null-terminate if the source is too long, and pads with zeros otherwise. Safer choices are snprintf(dst, size, "%s", src), which always terminates and reports truncation, or platform functions like strlcpy where available.
Q49. Reverse a string in place.
#include <stdio.h>
#include <string.h>
void reverse(char *s) {
size_t n = strlen(s);
if (n < 2) return;
for (size_t i = 0, j = n - 1; i < j; i++, j--) {
char t = s[i];
s[i] = s[j];
s[j] = t;
}
}
int main(void) {
char word[] = "prepflix"; /* must be an array, not a pointer to a literal */
reverse(word);
printf("%s\n", word); /* xilfperp */
return 0;
}
Two details interviewers look for: the n < 2 guard (with size_t, n - 1 for an empty string would wrap to a huge number), and calling it on a modifiable array rather than a string literal.
Q50. How do you check whether a machine is little-endian or big-endian?
#include <stdint.h>
#include <stdio.h>
int main(void) {
uint32_t one = 1;
unsigned char first = *(unsigned char *)&one; /* char access is allowed by aliasing rules */
printf("%s-endian\n", first == 1 ? "little" : "big");
return 0;
}
In little-endian (x86, most ARM configurations), the least significant byte is stored at the lowest address, so the first byte is 1. Network byte order is big-endian, which is why sockets code uses htonl and ntohl. More bit-level questions are in our bit manipulation guide.
Tricky C Output Questions, Explained
Each output below was compiled with GCC and checked. Cover the answer, predict, then read the reasoning; the reasoning is what transfers to new questions.
Output 1: signed meets unsigned
unsigned int u = 1;
int i = -1;
if (i < u) printf("yes\n"); else printf("no\n");
Output: no. In the comparison, i is converted to unsigned int, becoming UINT_MAX (4294967295 for 32-bit int), which is greater than 1. GCC warns about this with -Wsign-compare.
Output 2: sizeof is unsigned
printf("%d\n", sizeof(int) > -1);
Output: 0. sizeof yields size_t, which is unsigned. -1 converts to SIZE_MAX, so 4 > SIZE_MAX is false. The same trap breaks loops like for (int i = 0; i < strlen(s) - 1; i++) when s is empty.
Output 3: pointer to the whole array
int a[5] = {1, 2, 3, 4, 5};
int *p = (int *)(&a + 1);
printf("%d %d\n", *(a + 1), *(p - 1));
Output: 2 5. a + 1 points to the second element. But &a has type "pointer to array of 5 ints", so &a + 1 jumps 20 bytes, just past the whole array. Stepping back one int lands on the last element.
Output 4: printf returns a count
printf("%d\n", printf("hello"));
Output: hello5. The inner printf runs first, prints "hello", and returns the number of characters printed, 5, which the outer call prints.
Output 5: macro without parentheses
#define SQUARE(x) x*x
printf("%d\n", SQUARE(2+3));
Output: 11. Expands to 2+3*2+3. See Q35.
Output 6: sizeof vs strlen
char s[] = "abc";
char *t = "hello";
printf("%zu %zu %zu %zu\n", sizeof(s), strlen(s), sizeof(t), strlen(t));
Output: 4 3 8 5 on a 64-bit build (4 3 4 5 on 32-bit). The array includes the terminating '\0'; sizeof(t) is the pointer size; strlen never counts the terminator.
Output 7: floating-point comparison
float f = 0.1;
if (f == 0.1) printf("equal\n"); else printf("not equal\n");
Output: not equal. 0.1 has no exact binary representation. The float stores a 24-bit-mantissa approximation; the literal 0.1 is a double with a 53-bit approximation. In the comparison f is promoted to double, and the two approximations differ. With 0.5 it would print "equal", because 0.5 is exactly representable. Compare floats with a tolerance instead.
Output 8: the stray semicolon
int k;
for (k = 0; k < 3; k++);
printf("%d\n", k);
Output: 3, printed once. The semicolon after the for is an empty loop body; the indented printf is not part of the loop. -Wall (via -Wmisleading-indentation in newer GCC) can flag this.
Output 9: char overflow
char c = 255;
printf("%d\n", c);
Output: -1 on GCC x86 where char is signed (255 does not fit, and the implementation-defined conversion gives -1); 255 where char is unsigned. Say both and explain why.
Output 10: switch fall-through
int x = 2;
switch (x) {
case 1: printf("one ");
case 2: printf("two ");
case 3: printf("three ");
default: printf("default\n");
}
Output: two three default. Without break, execution falls through every following case. Intentional fall-through should be marked with a comment or C23's [[fallthrough]] attribute.
malloc(strlen(s)) instead of strlen(s) + 1); p = realloc(p, n); using sizeof on an array parameter; comparing signed and unsigned values in loop conditions; and calling volatile a thread-safety tool.If your interview also covers C++ or operating systems, continue with our C++ interview questions and OS interview questions; memory layout, virtual memory and processes connect directly to what you learned here. For embedded careers, see the embedded systems career path, and for structure-level practice, the linked list problems guide is a natural next step in C.
Frequently Asked Questions
Strengthen Your Programming Fundamentals
Prepflix's programme builds the fundamentals interviewers probe, from memory and pointers to DSA, with mock technical rounds and feedback.
Explore the Prepflix Course →