C aptitude questions test how well you understand the C language at a deep level — not just syntax, but behavior at the edges: undefined behavior, pointer arithmetic, operator precedence, and type conversion. These questions appear in technical interviews at companies hiring for systems programming, embedded software, and firmware roles.
This page gives you 50 C aptitude questions with answers and explanations — every code snippet on this page was compiled and run with gcc 13 (-Wall -Wextra), so the outputs are real, not guessed. Below the question bank you’ll find 31 classic question sets with hundreds more.
Jump to a Topic
- Pointers & Arrays (Q1–Q11)
- Operators & Expressions (Q12–Q19)
- Strings (Q20–Q26)
- Storage Classes & Scope (Q27–Q32)
- Type Conversions (Q33–Q39)
- Preprocessor (Q40–Q44)
- Structs, Unions & Memory (Q45–Q48)
- Control Flow (Q49–Q50)
- All 31 Classic Question Sets
Pointers & Arrays
Question 1 — Array indexing equivalences
#include <stdio.h>
int main(void) {
char s[] = "abc";
printf("%c %c %c\n", s[1], *(s+1), 1[s]);
return 0;
}
Answer: b b b. In C, a[i] is defined as *(a + i). Because addition is commutative, *(i + a) is identical, and i[a] is just another way to write it. All three expressions refer to the same byte.
Question 2 — Post-increment through a pointer
#include <stdio.h>
int main(void) {
int a[] = {1, 2, 3, 4, 5};
int *p = a;
printf("%d\n", *p++);
printf("%d\n", *p);
return 0;
}
Answer: 1 then 2. *p++ parses as *(p++): the ++ binds to the pointer, not the value. The first printf uses the old pointer (element 0), then p advances to element 1.
Question 3 — 2D array dereference
#include <stdio.h>
int main(void) {
int a[3][2] = {{1, 2}, {3, 4}, {5, 6}};
printf("%d\n", *(*(a + 1) + 1));
return 0;
}
Answer: 4. a + 1 points at row 1 ({3, 4}); dereferencing gives that row as an array, which decays to a pointer to its first element; + 1 then steps to the second element. So *(*(a+1)+1) is exactly a[1][1].
Question 4 — sizeof a function parameter
#include <stdio.h>
void print_size(int a[10]) {
printf("%lu\n", sizeof(a));
}
int main(void) {
int arr[10];
print_size(arr);
return 0;
}
Answer: 8 (on a 64-bit system), not 40. When an array is passed to a function, it decays to a pointer. Inside print_size, a is int *, not int[10], so sizeof(a) gives the pointer size.
Question 5 — sizeof array vs sizeof its first element’s address
#include <stdio.h>
int main(void) {
int a[10];
printf("%zu %zu\n", sizeof(a), sizeof(&a[0]));
return 0;
}
Answer: 40 8. Inside its own scope the array does not decay in a sizeof expression — sizeof(a) is the whole array (10 × 4 bytes). &a[0] is a plain int *, so 8 bytes. Compare with Question 4, where decay does happen at the function boundary.
Question 6 — Counting rows and columns with sizeof
#include <stdio.h>
int main(void) {
int a[2][3];
printf("%zu %zu\n", sizeof(a) / sizeof(a[0]),
sizeof(a[0]) / sizeof(a[0][0]));
return 0;
}
Answer: 2 3. sizeof(a) = 24, sizeof(a[0]) = one row = 12, sizeof(a[0][0]) = one int = 4. Whole ÷ row = number of rows; row ÷ element = number of columns. This is the standard idiom for array dimensions.
Question 7 — Is the array name the same as &a[0]?
#include <stdio.h>
int main(void) {
int a[5];
printf("%d\n", a == &a[0]);
return 0;
}
Answer: 1 (true). In this expression the array name decays to a pointer to its first element, so both sides are the same address. But they are not always interchangeable — see Question 5 (sizeof) and Question 9 (&a). Deeper dive: array name as pointer.
Question 8 — Pointer subtraction
#include <stdio.h>
int main(void) {
int a[10];
printf("%td\n", &a[7] - &a[2]);
return 0;
}
Answer: 5 — elements, not bytes (not 20). Pointer arithmetic always works in units of the pointed-to type. Deeper dive: pointer arithmetic in C.
Question 9 — The &a + 1 trick
#include <stdio.h>
int main(void) {
int a[5] = {1, 2, 3, 4, 5};
int *p = (int *)(&a + 1);
printf("%d\n", *(p - 1));
return 0;
}
Answer: 5. &a is a pointer to the whole array (int (*)[5]), so &a + 1 jumps past all 5 elements — one full array’s worth. Cast to int * and step back one int, and you land on the last element. (One-past-the-end is a legal address to compute; the same rules make some negative indexes legal too — see negative array index in C.)
Question 10 — Array of string pointers
#include <stdio.h>
int main(void) {
char *s[] = {"one", "two", "three"};
printf("%s\n", s[1] + 1);
return 0;
}
Answer: wo. s[1] is the pointer to "two"; + 1 advances it one char, so printing starts from 'w'.
Question 11 — Pointer to const
#include <stdio.h>
int main(void) {
const int x = 5;
int *p = (int *)&x;
*p = 10;
printf("%d\n", x);
return 0;
}
Answer: Undefined behavior. x is declared const. Casting away the const qualifier and writing through the pointer violates the C standard. Most compilers print 5 (the original value from read-only storage), but the result is not guaranteed.
Operators & Expressions
Question 12 — Division, multiplication, modulo
#include <stdio.h>
int main(void) {
printf("%d\n", 10 / 3 * 3 + 10 % 3);
return 0;
}
Answer: 10. /, *, % share one precedence level and associate left to right: 10/3 = 3 (integer division truncates), 3*3 = 9, 10%3 = 1, and 9 + 1 = 10. The identity (a/b)*b + a%b == a always holds for integers.
Question 13 — Shift vs addition precedence
#include <stdio.h>
int main(void) {
int x = 2;
printf("%d\n", x << 1 + 1);
return 0;
}
Answer: 8, not 5. Addition binds tighter than shift, so this is x << (1 + 1) = 2 << 2 = 8. This trap is real enough that gcc emits warning: suggest parentheses around ‘+’ inside ‘<<‘ for this exact line.
Question 14 — The a+++b puzzle
#include <stdio.h>
int main(void) {
int a = 1, b = 2;
int c = a+++b;
printf("%d %d %d\n", a, b, c);
return 0;
}
Answer: 2 2 3. The tokenizer is greedy (“maximal munch”): a+++b becomes a++ + b, never a + ++b. So c = 1 + 2 = 3, then a becomes 2.
Question 15 — Nested ternary
#include <stdio.h>
int main(void) {
printf("%d\n", 0 ? 1 : 0 ? 2 : 3);
return 0;
}
Answer: 3. The conditional operator is right-associative: this parses as 0 ? 1 : (0 ? 2 : 3). The first condition is false, the second is false, so the result is 3.
Question 16 — Bitwise NOT
#include <stdio.h>
int main(void) {
printf("%d\n", ~5);
return 0;
}
Answer: -6. ~ flips every bit. In two’s complement, ~n is always -(n + 1): flipping the bits of 5 gives the representation of −6. Handy corollary: ~0 is −1 (all bits set).
Question 17 — Comma operator
#include <stdio.h>
int main(void) {
printf("%d\n", (1, 2, 3));
return 0;
}
Answer: 3. Inside the parentheses, the comma operator evaluates left to right and yields the last value. Without the parentheses, the commas would separate printf’s arguments instead — a completely different meaning.
Question 18 — Short-circuit evaluation
#include <stdio.h>
int main(void) {
int i = 0;
int x = 0 && i++;
printf("%d %d\n", x, i);
return 0;
}
Answer: 0 0. && stops as soon as the result is known: the left side is 0, so i++ is never evaluated — i stays 0. Deeper dive: short-circuit evaluation.
Question 19 — i++ + ++i
#include <stdio.h>
int main(void) {
int i = 5;
int j = i++ + ++i;
printf("%d\n", j);
return 0;
}
Answer: Undefined behavior — i is modified twice with no sequence point between. Our gcc 13 build happens to print 12 and warns operation on ‘i’ may be undefined [-Wsequence-point], but any answer here is “correct”. In an interview, saying “undefined behavior” beats computing a number. Deeper dive: sequence points in C.
Strings
Question 20 — sizeof vs strlen
#include <stdio.h>
#include <string.h>
int main(void) {
printf("%zu %zu\n", sizeof("hello"), strlen("hello"));
return 0;
}
Answer: 6 5. sizeof counts the whole array including the terminating '\0'; strlen counts characters up to (not including) it. Deeper dive: sizeof vs strlen.
Question 21 — Arithmetic on a string literal
#include <stdio.h>
int main(void) {
printf("%s\n", "hello" + 2);
return 0;
}
Answer: llo. The literal decays to a pointer to its first character; + 2 skips two characters, and printf prints from there to the terminator.
Question 22 — Embedded null byte
#include <stdio.h>
#include <string.h>
int main(void) {
char s[] = "abc\0def";
printf("%zu %zu\n", strlen(s), sizeof(s));
return 0;
}
Answer: 3 8. strlen stops at the first '\0' (after “abc”). But the array really contains all 8 bytes: a-b-c-\0-d-e-f-\0 (the compiler appends a final terminator). “String length” and “array size” are different questions.
Question 23 — Modifying a string literal
#include <stdio.h>
int main(void) {
char *s = "hello";
s[0] = 'H';
printf("%s\n", s);
return 0;
}
Answer: Undefined behavior (usually a segfault). String literals are stored in read-only memory. char *s = "hello" makes s point into that read-only region. To get a modifiable copy, use char s[] = "hello" instead — see the next question, and the deeper dive: char array vs string literal.
Question 24 — Modifying a char array
#include <stdio.h>
int main(void) {
char s[] = "world";
char *p = s;
p[0] = 'W';
printf("%s\n", s);
return 0;
}
Answer: World — perfectly legal. char s[] = "world" copies the literal into a writable array on the stack. The one-character difference from Question 23 ([] vs *) is the difference between working code and a crash.
Question 25 — The array that exactly fits
#include <stdio.h>
int main(void) {
char s[5] = "hello";
printf("%s\n", s);
return 0;
}
Answer: Legal declaration, broken printf. C explicitly permits char s[5] = "hello" — the 5 characters fit and the '\0' is silently dropped. But now s is not a string, and printf("%s") reads past the array hunting for a terminator: undefined behavior. Our run printed hello� — “hello” followed by garbage. Size the array 6, or better, omit the size and let the compiler count.
Question 26 — Do equal literals share storage?
#include <stdio.h>
int main(void) {
char *a = "hi";
char *b = "hi";
printf("%d\n", a == b);
return 0;
}
Answer: Implementation-defined. The standard allows (but does not require) the compiler to pool identical literals. gcc pools them, so this prints 1 — but comparing strings with == compares addresses, never contents. Use strcmp.
Storage Classes & Scope
Question 27 — Static variable in a function
#include <stdio.h>
void counter(void) {
static int n = 0;
n++;
printf("%d\n", n);
}
int main(void) {
counter();
counter();
counter();
return 0;
}
Answer: 1, 2, 3. A static local variable is initialized only once (at program start) and retains its value between calls. It lives in the data segment, not the stack. Deeper dive: static local variables.
Question 28 — Variable shadowing
#include <stdio.h>
int x = 10;
int main(void) {
int x = 20;
{
int x = 30;
printf("%d\n", x);
}
printf("%d\n", x);
return 0;
}
Answer: 30 then 20. Each inner declaration shadows the outer one; the innermost visible x always wins, and when its block ends, the next one out becomes visible again. The global 10 is unreachable inside main — C has no ::. Deeper dive: variable shadowing.
Question 29 — Uninitialized globals
#include <stdio.h>
int g;
static int s;
int main(void) {
printf("%d %d\n", g, s);
return 0;
}
Answer: 0 0 — guaranteed. Variables with static storage duration (globals and statics) are zero-initialized before main runs. Only local automatic variables hold garbage when uninitialized.
Question 30 — Address of a register variable
#include <stdio.h>
int main(void) {
register int x = 5;
printf("%p\n", (void *)&x);
return 0;
}
Answer: Compile error: address of register variable ‘x’ requested. register asks the compiler to keep the variable out of memory, so it has no address to take. This is the one storage-class rule the compiler enforces with an error, not a suggestion.
Question 31 — Loop variable scope
#include <stdio.h>
int main(void) {
for (int i = 0; i < 3; i++);
printf("%d\n", i);
return 0;
}
Answer: Compile error: ‘i’ undeclared. A variable declared in the for header exists only inside the loop — and note the sneaky ; right after the for, which makes the loop body empty. Two traps in two lines.
Question 32 — Variable-length array
#include <stdio.h>
int main(void) {
int n = 5;
int a[n];
printf("%zu\n", sizeof(a));
return 0;
}
Answer: 20 — this compiles and runs. Many candidates say “error: array size must be constant”, but C99 introduced variable-length arrays. sizeof on a VLA is evaluated at runtime: 5 × 4 = 20. (VLAs became optional in C11 — avoid them in portable code.)
Type Conversions
Question 33 — Signed vs unsigned comparison
#include <stdio.h>
int main(void) {
if (-1 < 1U)
printf("yes\n");
else
printf("no\n");
return 0;
}
Answer: no. When a signed int meets an unsigned int, the signed value converts to unsigned: −1 becomes 4294967295 (UINT_MAX), which is not less than 1. gcc warns about this with -Wextra (-Wsign-compare) — one of the best reasons to compile with warnings on.
Question 34 — Float vs double comparison
#include <stdio.h>
int main(void) {
float f = 1.1f;
double d = 1.1;
if (f == d)
printf("equal\n");
else
printf("not equal\n");
return 0;
}
Answer: not equal. float stores 1.1 with 7 significant digits of precision; double stores it with 15–16. The two binary representations differ, so == is false. Rule: never compare floating-point values with ==.
Question 35 — char arithmetic
#include <stdio.h>
int main(void) {
char c = 'A';
printf("%d\n", c + 1);
return 0;
}
Answer: 66. 'A' is 65 in ASCII; in arithmetic, the char promotes to int, so c + 1 is the int 66. Print it with %c and you’d see B instead.
Question 36 — Float to int conversion
#include <stdio.h>
int main(void) {
printf("%d\n", (int)3.999);
return 0;
}
Answer: 3. Converting floating-point to integer truncates toward zero — it never rounds. (−3.999 would become −3 for the same reason.) Use round() from <math.h> when you want rounding.
Question 37 — Unsigned overflow
#include <stdio.h>
int main(void) {
unsigned char c = 255;
c++;
printf("%d\n", c);
return 0;
}
Answer: 0 — and this is well-defined. Unsigned arithmetic wraps modulo 2ⁿ by definition. Contrast with signed overflow (INT_MAX + 1), which is undefined behavior. Same operation, opposite standard guarantees.
Question 38 — Integer where a double is expected
#include <stdio.h>
int main(void) {
printf("%f\n", 5 / 2);
return 0;
}
Answer: Undefined behavior. Two separate bugs: 5 / 2 is integer division (2, not 2.5), and passing an int where %f expects a double makes printf read the wrong registers or stack slots — our run printed 0.000000. gcc’s -Wall flags it precisely: format ‘%f’ expects ‘double’, but argument has type ‘int’. The fix, 5 / 2.0, prints 2.500000.
Question 39 — Integer promotion inside sizeof
#include <stdio.h>
int main(void) {
char c = 1;
printf("%zu %zu\n", sizeof(c), sizeof(c + 1));
return 0;
}
Answer: 1 4. sizeof(c) is 1 byte, but in the expression c + 1 the char is promoted to int before the addition, so the expression’s type is int — 4 bytes. Arithmetic in C never happens in types narrower than int.
Preprocessor
Question 40 — Macro without parentheses
#include <stdio.h>
#define SQR(x) x*x
int main(void) {
printf("%d\n", SQR(1 + 2));
return 0;
}
Answer: 5, not 9. Macros are textual substitution: SQR(1 + 2) expands to 1 + 2*1 + 2, and precedence does the rest. Always parenthesize: #define SQR(x) ((x)*(x)). Deeper dive: macro parentheses.
Question 41 — Object-like macro trap
#include <stdio.h>
#define MAX 10+5
int main(void) {
printf("%d\n", MAX * 2);
return 0;
}
Answer: 20, not 30. MAX * 2 expands to 10+5 * 2 = 10 + 10. Same disease as Question 40, simpler carrier: define it as (10+5).
Question 42 — Macro argument evaluated twice
#include <stdio.h>
#define SQUARE(x) ((x) * (x))
int main(void) {
int i = 3;
int r = SQUARE(i++);
printf("%d %d\n", r, i);
return 0;
}
Answer: Undefined behavior — and this macro is fully parenthesized. Expansion gives ((i++) * (i++)): i modified twice, no sequence point (our gcc run printed 12 5 with a -Wsequence-point warning). Parentheses can’t fix double evaluation; never pass expressions with side effects to function-like macros.
Question 43 — Token pasting
#include <stdio.h>
#define CONCAT(a, b) a##b
int main(void) {
int xy = 10;
printf("%d\n", CONCAT(x, y));
return 0;
}
Answer: 10. The ## operator glues two tokens into one at preprocessing time: CONCAT(x, y) becomes the identifier xy, which resolves to the existing variable.
Question 44 — Stringification
#include <stdio.h>
#define STR(x) #x
int main(void) {
printf("%s\n", STR(hello world));
return 0;
}
Answer: hello world. The # operator turns a macro argument into a string literal — no quotes needed at the call site. This is how assert() prints the failing expression’s text.
Structs, Unions & Memory
Question 45 — Struct padding
#include <stdio.h>
int main(void) {
struct { char c; int i; } s;
printf("%zu\n", sizeof(s));
return 0;
}
Answer: 8, not 5. The int must sit on a 4-byte boundary, so the compiler inserts 3 padding bytes after the char. Member order matters: a struct’s size is not the sum of its members’ sizes.
Question 46 — Reading a union byte-by-byte
#include <stdio.h>
int main(void) {
union { int i; char c[4]; } u;
u.i = 0x41424344;
printf("%c\n", u.c[0]);
return 0;
}
Answer: Implementation-defined — it reveals your machine’s byte order. On a little-endian CPU (x86-64, most ARM) the lowest byte comes first, so c[0] is 0x44 = 'D' (which is what our run printed). On a big-endian machine it would be 'A'. Deeper dive: union endianness.
Question 47 — Struct assignment copies arrays
#include <stdio.h>
struct S { char name[10]; };
int main(void) {
struct S a = {"hello"}, b;
b = a;
a.name[0] = 'H';
printf("%s %s\n", a.name, b.name);
return 0;
}
Answer: Hello hello. Arrays can’t be assigned directly — but a struct containing an array can, and b = a deep-copies the whole thing. Changing a afterward doesn’t touch b. Deeper dive: struct initialization.
Question 48 — sizeof a malloc’d pointer
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = malloc(10 * sizeof(int));
printf("%zu\n", sizeof(p));
free(p);
return 0;
}
Answer: 8, not 40. sizeof sees only the pointer, never the block it points to — C gives you no way to ask “how big is this allocation?”. You must track allocated sizes yourself. Deeper dive: sizeof a pointer.
Control Flow
Question 49 — do-while always runs once
#include <stdio.h>
int main(void) {
int i = 10;
do {
printf("%d\n", i);
i++;
} while (i < 5);
return 0;
}
Answer: 10 — printed exactly once. A do-while tests its condition after the body, so the body always executes at least once, even when the condition starts out false.
Question 50 — switch fallthrough
#include <stdio.h>
int main(void) {
int x = 2;
switch (x) {
case 1: printf("one\n");
case 2: printf("two\n");
case 3: printf("three\n");
default: printf("default\n");
}
return 0;
}
Answer: two, three, default. Without break, execution falls through every following case. The switch jumps to case 2 and then just keeps going. Deeper dive: switch fallthrough.
All 31 Classic C Aptitude Question Sets
Want more? Each set below contains 5–10 questions in the same “predict the output or error” format with full explanations.
- Set 1 — Pointers, const, array indexing
- Set 2 — Type conversions, operators
- Set 3 — Strings, char arrays
- Set 4 — Preprocessor macros
- Set 5 — Storage classes, scope
- Set 6 — Pointers and arrays
- Set 7 — Operator precedence
- Set 8 — Recursion, functions
- Set 9 — Bitwise operators
- Set 10 — Mixed topics
- Set 11 — Structures, unions
- Set 12 — File I/O, pointers
- Set 13 — Dynamic memory
- Set 14 — Undefined behavior
- Set 15 — Type casting
- Set 16 — Strings and arrays
- Set 17 — Pointers to functions
- Set 18 — Mixed operators
- Set 19 — Preprocessor, macros
- Set 20 — Storage and linkage
- Set 21 — Arrays, sizeof
- Set 22 — Pointers and strings
- Set 23 — Mixed topics
- Set 24 — Operators, precedence
- Set 25 — Type conversions
- Set 26 — Pointers, arrays
- Set 27 — Functions, scope
- Set 28 — Strings, memory
- Set 29 — Mixed topics
- Set 30 — Advanced pointers
- Set 31 — Endianness & pointer arithmetic
How to Use These Questions
- For freshers: Work through the Pointers, Operators, and Strings sections first — these cover the core language features tested in campus placements and entry-level interviews.
- For experienced candidates: Focus on Type Conversions, Preprocessor, and Structs & Memory — plus the undefined-behavior questions (11, 19, 23, 25, 38, 42). Knowing why something is UB separates senior answers from guesses.
- Practice method: Predict the output before reading the answer. Write it down, then verify — ideally by compiling with
gcc -Wall -Wextralike we did. This builds the mental model faster than passive reading.
Related Resources
- C Quiz Explainers — 49 single-question deep dives with gcc/clang output
- K&R C Exercise Solutions — All 91
- GDB Tutorial — debug the programs you just predicted
As an Amazon Associate we earn from qualifying purchases.
Recommended Book
The questions on this page are grounded in The C Programming Language by Kernighan & Ritchie — the definitive reference for anyone preparing for a C interview. Also on Amazon.com.