Aptitude Prep Handbook — Section 7: Programming (C and
Python)
Output prediction, trick questions, and the concepts they actually test
Aarav — Personal Placement Prep Handbook
July 2026
Table of Contents
How to use this section
Programming questions in placement tests are rarely about writing code. They are about
predicting what a short snippet prints — which means they test whether you
understand the language’s model of memory, evaluation and types, not whether you can
implement an algorithm.
The good news is that the trick pool is small. Perhaps thirty distinct gotchas account for the
great majority of output-prediction questions in C and Python combined. This section is
organised around them.
Everything here was compiled and run. Every C snippet was built with gcc 11.4 on 64-
bit Linux and executed; every Python snippet was run on CPython 3.10. The outputs
shown are the actual outputs, not what I expected them to be — and in two cases that
distinction mattered, which I have flagged in the text.
How to use it properly: read the code, write down your prediction, then look at the
output. A snippet whose output surprises you is worth more than ten that do not.
One caution about C. Some classic “trick questions” that circulate in question banks rely
on undefined behaviour — code for which the C standard specifies no result at all. Such
questions have no correct answer; different compilers legitimately print different things.
Chapter 9 covers these explicitly, because knowing that a question is unanswerable is itself
the right answer in an interview.
Part A — C
1. Data types, sizes and integer arithmetic
Concepts tested
sizeof, integer versus floating division, type promotion, and the difference between the size
of an array and the size of a pointer.
Verified snippet
#include <stdio.h>
int main(void){
printf("sizeof int=%zu char=%zu double=%zu ptr=%zu\n",
sizeof(int), sizeof(char), sizeof(double), sizeof(int*));
int a = 7, b = 2;
printf("7/2=%d 7%%2=%d 7/2.0=%.2f (float)7/2=%.2f\n",
a/b, a%b, a/2.0, (float)a/b);
printf("5/2=%d -5/2=%d 5%%2=%d -5%%2=%d\n", 5/2, -5/2, 5%2, -5%2);
return 0;
}
Actual output:
sizeof int=4 char=1 double=8 ptr=8
7/2=3 7%2=1 7/2.0=3.50 (float)7/2=3.50
5/2=2 -5/2=-2 5%2=1 -5%2=-1
What to take away
• Integer ÷ integer = integer. 7/2 is 3, not 3.5. To get a real quotient, at least one
operand must be floating: 7/2.0 or (float)a/b.
• sizeof(char) is 1 by definition; sizeof(int) is 4 on almost all modern platforms but
is not guaranteed by the standard.
• Pointer size follows the architecture, not the pointee. On 64-bit, every pointer is 8
bytes whether it points to a char or a double.
• In C, integer division truncates toward zero, so -5/2 is -2 and -5%2 is -1. (Contrast
Python — see Chapter 15. This is the most commonly confused cross-language
difference.)
Traps
• Writing float avg = sum/count; where both are int — the division happens in integer
arithmetic before the assignment, so the cast comes too late.
• Assuming sizeof(int) is 2. It was on 16-bit systems, and old question banks still say so.
• sizeof returns size_t, printed with %zu, not %d.
2. Operators, precedence and increment
Verified snippet
int i = 5;
printf("i++ prints %d then i=%d\n", i++, i); /* see the note below */
int j = 5;
printf("++j prints %d then j=%d\n", ++j, j);
int x = 5, y = 10;
printf("x&y=%d x|y=%d x^y=%d x<<1=%d y>>1=%d\n", x&y, x|y, x^y, x<<1,
y>>1);
printf("1 && 0 || 1 = %d ; !0 = %d\n", (1 && 0 || 1), !0);
Actual output:
i++ prints 5 then i=6
++j prints 6 then j=6
x&y=0 x|y=15 x^y=15 x<<1=10 y>>1=5
1 && 0 || 1 = 1 ; !0 = 1
What to take away
• Post-increment i++ yields the old value and then increments. Pre-increment ++i
increments first and yields the new value.
• Bitwise arithmetic: 5 is 0101 and 10 is 1010, so they share no bits — 5 & 10 = 0
and 5 | 10 = 15. Left shift by 1 doubles; right shift by 1 halves (for non-negative
values).
• && binds tighter than ||, so 1 && 0 || 1 is (1 && 0) || 1 = 1.
An honest note on the first line
The snippet above passes both i++ and i to the same printf call. The order in which
function arguments are evaluated is unspecified in C, so although this build printed 5
then 6, another compiler could legitimately print 5 then 5. I have shown it because this
exact construction appears in question banks — but the correct answer to such a question
is “unspecified”, and Chapter 9 explains why. When the increment is on its own line, the
behaviour is completely well defined.
Traps
• Precedence of && over ||, and of arithmetic over comparison.
• = versus == — if (x = 5) assigns and is always true.
• & and | are bitwise; && and || are logical with short-circuit evaluation. In f() && g(),
g() is never called if f() returns 0.
3. Control flow
Verified snippet
int k = 0, total = 0;
for (k = 0; k < 5; k++){
if (k == 2) continue;
if (k == 4) break;
total += k;
}
printf("total=%d k=%d\n", total, k);
int n = 0;
while (n++ < 3) { }
printf("after while(n++ < 3): n=%d\n", n);
int m = 0, iter = 0;
do { m += 2; iter++; } while (m < 5);
printf("do-while: m=%d iterations=%d\n", m, iter);
Actual output:
total=4 k=4
after while(n++ < 3): n=4
do-while: m=6 iterations=3
What to take away
• continue skips to the next iteration (k = 2 contributes nothing); break exits the
loop entirely (so k = 4 is never added, and k retains 4 after the loop). Total = 0 + 1 + 3
= 4.
• while (n++ < 3) compares first, then increments — so the test fails when n is 3, but
n has already been incremented to 4 by then.
• A do…while body always runs at least once, and the test is at the end. Here m goes
2, 4, 6; the loop exits after the third iteration because 6 is not less than 5.
Traps
• break inside nested loops exits only the innermost one. A favourite MCQ.
• A stray semicolon: for (i=0; i<5; i++); has an empty body, and the block that follows
runs once.
• Dangling else binds to the nearest unmatched if, regardless of indentation.
4. Arrays and pointers
Verified snippet
int arr[5] = {10,20,30,40,50};
int *p = arr;
printf("arr[2]=%d *(p+2)=%d p[2]=%d 2[arr]=%d\n", arr[2], *(p+2), p[2], 2[arr]);
printf("sizeof(arr)=%zu sizeof(p)=%zu count=%zu\n",
sizeof(arr), sizeof(p), sizeof(arr)/sizeof(arr[0]));
int a2[] = {1,2,3,4,5};
int *ptr = a2 + 1;
printf("*ptr=%d *(ptr+2)=%d ptr[-1]=%d\n", *ptr, *(ptr+2), ptr[-1]);
Actual output:
arr[2]=30 *(p+2)=30 p[2]=30 2[arr]=30
sizeof(arr)=20 sizeof(p)=8 count=5
*ptr=2 *(ptr+2)=4 ptr[-1]=1
What to take away
• a[i] is defined as *(a + i). Since addition commutes, 2[arr] is legal C and means the
same as arr[2]. This is a genuine interview question, and the reason is worth knowing
rather than memorising.
• Pointer arithmetic scales by the pointee’s size. p + 2 advances 8 bytes for an int*,
not 2 — the compiler does the multiplication for you.
• sizeof(array) is the whole array (20 bytes); sizeof(pointer) is 8. The idiom
sizeof(arr)/sizeof(arr[0]) gives the element count — but only in the scope where
the array was declared.
• Negative indices are legal when the pointer is not at the start: ptr[-1] reads the
element before it.
Traps
• An array “decays” to a pointer when passed to a function, so sizeof inside the
function gives the pointer size, and the element-count idiom silently returns garbage.
You must pass the length separately.
• C performs no bounds checking. Reading arr[10] compiles cleanly and reads
whatever is in memory.
• int *p, q; declares one pointer and one plain int, not two pointers.
5. Strings
Verified snippet
char s[] = "hello";
printf("strlen=%zu sizeof=%zu\n", strlen(s), sizeof(s));
char *t = "abcdef";
printf("t[3]=%c *(t+3)=%c\n", t[3], *(t+3));
Actual output:
strlen=5 sizeof=6
t[3]=d *(t+3)=d
What to take away
• strlen counts characters up to the null terminator (5); sizeof includes it (6).
This one-character difference is the single most-tested fact about C strings.
• Every string literal carries an invisible '\0' at the end. Forgetting to allocate room for it
is the classic buffer overflow.
• char s[] = "hello" creates a modifiable copy; char *t = "abcdef" points at a string
literal, which is read-only — writing through t is undefined behaviour and typically
crashes.
Traps
• Comparing strings with == compares pointers, not contents. Use strcmp, which
returns 0 when equal (not 1 — another standard trap).
• strcpy does not check the destination size.
• char c = 'A' is one character; "A" is a two-byte array.
6. Functions, scope and storage classes
Verified snippet
int counter(void){ static int n = 0; n++; return n; }
int plain(void) { int n = 0; n++; return n; }
int a = counter(); int b = counter(); int c = counter();
printf("static across calls: %d %d %d\n", a, b, c);
int p = plain(), q = plain(), r = plain();
printf("auto across calls: %d %d %d\n", p, q, r);
void byval(int x){ x = 99; }
void byptr(int *x){ *x = 99; }
int v = 1;
byval(v); printf("after byval v=%d\n", v);
byptr(&v); printf("after byptr v=%d\n", v);
Actual output:
static across calls: 1 2 3
auto across calls: 1 1 1
after byval v=1
after byptr v=99
What to take away
• A static local variable is initialised once and retains its value between calls. An
ordinary (automatic) local is recreated each call.
• C is strictly pass-by-value. byval receives a copy, so the caller’s variable is
untouched. To modify a caller’s variable you must pass its address — which is itself
passed by value, but the address lets you reach the original.
Storage classes summary:
Class
auto (default local)
static (local)
static (global)
extern
register
Traps
• Uninitialised locals contain garbage, not zero. Uninitialised globals and statics are
zero-initialised.
• static at file scope means “not visible to other files” — a completely different meaning
from static on a local.
• Returning a pointer to a local variable is a dangling pointer; the memory is reclaimed
on return.
7. Structures, unions and padding
Verified snippet
struct S { char c; int i; char d; };
union U { char c; int i; double d; };
printf("sizeof struct=%zu sizeof union=%zu\n", sizeof(struct S), sizeof(union U));
Actual output:
sizeof struct=12 sizeof union=8
What to take away
• A struct’s size is not the sum of its members. char + int + char is 6 bytes of data,
but the compiler inserts padding so that each member sits at a properly aligned
address, and pads the whole struct to a multiple of its largest member’s alignment.
Here: 1 byte for c, 3 bytes padding, 4 for i, 1 for d, 3 more padding = 12.
• Reordering members changes the size. Declaring {int i; char c; char d;} would
give 8 bytes instead of 12 — a real and often-asked optimisation.
• A union’s size is that of its largest member (8, for the double), because all members
share the same storage. Writing one member and reading another is how unions are
(ab)used for type punning.
Traps
• Padding is implementation-defined; the exact number can differ across compilers and
architectures. The principle is what is tested.
• All union members overlap — assigning u.i then reading u.c does not give you back an
independent value.
• A struct can be assigned wholesale (s1 = s2) but not compared with ==.
8. The preprocessor
Verified snippet
#define SQ(x) x*x
printf("SQ(3)=%d SQ(1+2)=%d\n", SQ(3), SQ(1+2));
Actual output:
SQ(3)=9 SQ(1+2)=5
What to take away
• A macro is textual substitution, not a function call. SQ(1+2) expands to 1+2*1+2,
which by precedence is 1 + 2 + 2 = 5, not 9.
• The fix is to parenthesise everything: #define SQ(x) ((x)*(x)). Even then, SQ(i++)
increments twice, because the argument is substituted twice — which is why inline
functions are preferred in modern C.
• Macros have no type checking and no scope; they are expanded before compilation
proper begins.
Traps
• A macro with a trailing semicolon inside it breaks if/else blocks.
• #define N 5+1 then int a = N*2; gives 7, not 12.
9. Undefined, unspecified and implementation-defined behaviour
This chapter exists because a substantial share of circulating “C trick questions” are, strictly
speaking, invalid — and knowing that is a real advantage in an interview.
Category Meaning Example
Undefined behaviour The standard imposes no i = i++; · arr[10] on a 5-
requirement whatsoever. element array ·
Anything may happen. dereferencing NULL
Unspecified behaviour The standard offers several Order of evaluation of
valid options and does not function arguments
say which.
Implementation-defined The implementation must sizeof(int) · whether char
choose and document a is signed
behaviour.
The classics that have no correct answer:
int i = 5;
i = i++; /* undefined - i modified twice without a sequence point */
printf("%d %d", i++, ++i); /* unspecified argument evaluation order */
a[i] = i++; /* undefined */
Question banks confidently supply answers for all three. Those answers describe what one
particular compiler did on one particular day. If you meet this in an interview, saying “that
expression is undefined behaviour, so the standard does not define a result” is the
strongest possible answer — considerably better than reciting the number the question
expects.
In a multiple-choice test with no “undefined” option, pick the answer the question-setter
most likely intends (usually left-to-right evaluation) and move on. It is not worth the time.
Traps
• Do not confuse undefined with unspecified. Unspecified behaviour has a small set of
valid outcomes; undefined has none at all, and can legitimately crash or corrupt
unrelated data.
• Signed integer overflow is undefined in C. Unsigned overflow is defined and wraps
around.
• “It worked on my machine” is not evidence that code is defined.
Part B — Python
10. The object model: mutability and references
Concepts tested
Python variables are names bound to objects, not boxes holding values. Assignment binds
a name; it never copies. Whether a change is visible elsewhere depends entirely on
whether the object is mutable.
Immutable Mutable
int, float, bool, str, tuple, frozenset list, dict, set, most custom objects
Verified snippet
a = [1, 2, 3]
b=a # binds the same object
c = a[:] # makes a copy
[Link](4)
print(a) # what does a show?
print(c)
def mod(l, n):
[Link](4)
n += 1
return n
L = [1, 2, 3]; N = 10
res = mod(L, N)
print(L, N, res)
Actual output:
[1, 2, 3, 4]
[1, 2, 3]
[1, 2, 3, 4] 10 11
What to take away
• b = a creates a second name for the same list, so appending through b is visible
through a. c = a[:] creates a new list, which is unaffected.
• Python’s argument passing is often described as “pass by object reference”. The
practical rule: mutating an argument in place is visible to the caller; rebinding a
parameter name is not. [Link](4) mutates; n += 1 rebinds n to a new integer,
leaving the caller’s N at 10.
Traps
• = never copies. To copy a list use list(a), a[:], or [Link](a).
• += on a list mutates in place (equivalent to extend); += on a tuple or int rebinds. The
same operator behaves differently by type.
11. Copying: shallow versus deep, and the [[0]*3]*2 trap
Verified snippet
import copy
m = [[1, 2], [3, 4]]
sh = [Link](m) # shallow
dp = [Link](m) # deep
m[0][0] = 99
print(sh)
print(dp)
grid = [[0]*3]*2
grid[0][0] = 1
print(grid)
grid2 = [[0]*3 for _ in range(2)]
grid2[0][0] = 1
print(grid2)
Actual output:
[[99, 2], [3, 4]]
[[1, 2], [3, 4]]
[[1, 0, 0], [1, 0, 0]]
[[1, 0, 0], [0, 0, 0]]
What to take away
• A shallow copy duplicates the outer container but shares the inner objects.
Modifying m[0][0] is visible through sh but not through dp.
• [[0]*3]*2 creates one inner list and stores it twice. Setting grid[0][0] appears to
change both rows because there is only one row object, referenced twice. This is
probably the most common Python bug in beginner code and an extremely frequent
test question.
• The comprehension [[0]*3 for _ in range(2)] evaluates the inner expression each
iteration, producing genuinely independent rows.
• Note [0]*3 itself is fine — integers are immutable, so sharing them is harmless. The
problem only arises when the repeated element is mutable.
12. Default arguments and closures
Verified snippet
def add(item, bag=[]):
[Link](item)
return bag
print(add(1)); print(add(2)); print(add(3))
fs = [lambda: i for i in range(3)]
print([f() for f in fs])
gs = [lambda i=i: i for i in range(3)]
print([g() for g in gs])
Actual output:
[1]
[1, 2]
[1, 2, 3]
[2, 2, 2]
[0, 1, 2]
What to take away
• A default argument is evaluated once, when the function is defined — not on each
call. The same list object is reused, so it accumulates across calls. The fix is the
standard idiom:
def add(item, bag=None):
if bag is None:
bag = []
[Link](item)
return bag
• Closures capture the variable, not its value. All three lambdas refer to the same i,
which is 2 by the time they are called — hence [2, 2, 2]. Binding it as a default
argument (lambda i=i: i) captures the value at definition time and gives [0, 1, 2].
Traps
• The mutable-default trap applies to lists, dicts and sets — never to None, numbers or
strings.
• The late-binding trap appears whenever functions are created in a loop, including with
[Link] misuse and in callback registration.
13. Slicing and indexing
Verified snippet
s = "abcdef"
print(s[1:4], s[::-1], s[-2:], repr(s[10:20]))
print(list(range(0, 10, 3)))
print(list(enumerate(['a', 'b'], start=1)))
Actual output:
bcd fedcba ef ''
[0, 3, 6, 9]
[(1, 'a'), (2, 'b')]
What to take away
• Slices are half-open: s[1:4] includes index 1 and excludes index 4, giving three
characters.
• s[::-1] reverses — the idiomatic reversal for strings, lists and tuples.
• Out-of-range slicing returns an empty result rather than raising. s[10:20] gives ''.
Out-of-range indexing (s[10]) does raise IndexError. This asymmetry is regularly
tested.
• range(start, stop, step) also excludes the stop value.
14. Numbers: division, float equality and identity
Verified snippet
print(7//2, -7//2, 7 % 3, -7 % 3)
print(1 < 2 < 3, 3 > 2 > 1, 1 < 2 > 3)
print(bool([]), bool("0"), bool(0.0))
Actual output:
3 -4 1 2
True True False
False True False
What to take away
• Python’s // floors (rounds toward negative infinity); C’s / truncates toward zero.
So -7//2 is -4 in Python but -5/2 is -2 in C. Correspondingly, Python’s % always takes
the sign of the divisor — -7 % 3 is 2, whereas C’s -7 % 3 is -1. If you work in both
languages, this is the difference most likely to bite you.
• Chained comparisons work as mathematical notation: 1 < 2 < 3 means 1 < 2
and 2 < 3. So 1 < 2 > 3 is True and False = False.
• Truthiness: empty containers, 0, 0.0, None and '' are falsy. The string "0" is non-
empty and therefore truthy — a favourite trap.
The is versus == question, stated accurately
This is where most published explanations are wrong, so it is worth getting right. CPython
caches small integers (−5 to 256), so identity comparisons on them tend to succeed.
Verified on CPython 3.10:
print(int("256") is 256) # True
print(int("257") is 257) # False
But the folklore version — writing a = 257; b = 257; a is b — prints True when both
lines are in the same script, because the compiler folds equal constants within one code
object into a single object. It prints False only when the two assignments are compiled
separately (as in an interactive session).
The practical lesson is not the caching detail. It is this: use == to compare values and
is only to compare identity (in practice, almost always is None). Any question whose
answer depends on integer caching is testing an implementation detail, not the language.
15. Collections: lists, tuples, dicts and sets
Verified snippet
t = ([1, 2], 3)
t[0].append(9)
print(t)
try:
t[0] += [10]
except TypeError as e:
print("raised:", e)
print(t)
d = {'b': 2, 'a': 1, 'c': 3}
print(list([Link]()))
print(sorted([Link](), key=lambda kv: -kv[1]))
data = [('b', 2), ('a', 2), ('c', 1)]
print(sorted(data, key=lambda x: x[1]))
lst = [3, 1, 2]
print([Link](), lst)
Actual output:
([1, 2, 9], 3)
raised: 'tuple' object does not support item assignment
([1, 2, 9, 10], 3)
['b', 'a', 'c']
[('c', 3), ('b', 2), ('a', 1)]
[('c', 1), ('b', 2), ('a', 2)]
None [1, 2, 3]
What to take away
• A tuple’s immutability is shallow. The tuple guarantees its slots never rebind, but a
mutable object in a slot can still be changed — t[0].append(9) works fine.
• The t[0] += [10] case is the subtlest thing in this section. It raises TypeError and
yet the list is modified, as the third line of output proves. This is because += on a list
first calls __iadd__, which mutates in place and succeeds, and then attempts to rebind
t[0], which fails. The exception comes after the mutation. It is a genuinely surprising
result and an excellent interview question.
• Dictionaries preserve insertion order (guaranteed since Python 3.7) — ['b', 'a', 'c'],
not sorted order.
• Python’s sort is stable: equal keys retain their original relative order, so ('b', 2) still
precedes ('a', 2).
• [Link]() sorts in place and returns None; sorted(list) returns a new list. Writing
x = [Link]() is a classic bug that silently binds None.
16. Functions and scope
Concepts tested
The LEGB lookup rule — a name is resolved in the order Local -> Enclosing -> Global ->
Built-in.
x = "global"
def outer():
x = "enclosing"
def inner():
print(x) # finds the enclosing x
inner()
outer()
• Assigning to a name anywhere in a function makes it local for the whole function,
which produces UnboundLocalError if you read it before assigning.
• global x rebinds a module-level name; nonlocal x rebinds the nearest enclosing
function’s name.
Argument forms: positional, keyword, *args (extra positionals as a tuple), **kwargs
(extra keywords as a dict). Order in a definition: positional, *args, keyword-only,
**kwargs.
Traps
• UnboundLocalError arises from an assignment later in the function than the read —
the compiler decides scope statically, before any line runs.
• Mutating a global list does not require global; only rebinding the name does.
17. Object orientation
Verified snippet
class C:
shared = [] # class attribute
def __init__(self):
[Link] = [] # instance attribute
c1 = C(); c2 = C()
[Link]('x')
[Link]('y')
print([Link], [Link])
class A:
def who(self): return "A"
class B(A):
def who(self): return "B"
class D(A):
def who(self): return "D"
class E(B, D): pass
print(E().who())
print([k.__name__ for k in E.__mro__])
Actual output:
['x'] []
B
['E', 'B', 'D', 'A', 'object']
What to take away
• A mutable class attribute is shared by every instance. [Link]('x') is
visible through c2, because there is one list on the class. Instance attributes created in
__init__ are per-object. This is the object-oriented version of the mutable-default trap,
and it is asked constantly.
• Note the asymmetry: [Link] = ['z'] would not affect c2, because assignment
creates a new instance attribute that shadows the class one. Mutation affects
everyone; rebinding affects only that instance.
• Method resolution order in multiple inheritance follows the C3 linearisation, which
for the classic diamond gives E -> B -> D -> A -> object. So E().who() finds B’s
method first.
Common dunder methods worth recognising: __init__ (initialiser, not constructor —
that is __new__), __str__ (readable) versus __repr__ (unambiguous), __len__, __eq__,
__call__.
Traps
• __init__ does not create the object; it initialises one already created by __new__.
• Defining __eq__ without __hash__ makes instances unhashable.
• Python has no true private members; a leading double underscore triggers name
mangling, not access control.
18. Exceptions
Verified snippet
def f():
try:
return "try"
finally:
print("finally runs before the return is delivered")
print(f())
def g():
try:
return "try"
finally:
return "finally"
print(g())
Actual output:
finally runs before the return is delivered
try
finally
What to take away
• finally always executes — including when the try block returns. The return value is
computed first, then finally runs, then the value is delivered.
• A return inside finally overrides the one in try, discarding it entirely. (It also
swallows any in-flight exception, which is why this is considered bad practice.)
• The full form is try / except / else / finally, where else runs only if no exception
was raised. That clause is widely forgotten and appears in MCQs.
Traps
• except: bare catches everything including KeyboardInterrupt; prefer except
Exception:.
• Multiple except clauses are checked in order, so a broad exception class listed first
shadows the more specific ones below it.
19. Generators and iterators
Verified snippet
gen = (i*i for i in range(4))
print(list(gen))
print(list(gen))
Actual output:
[0, 1, 4, 9]
[]
What to take away
• A generator is exhausted after one pass. The second list(gen) gets nothing, because
the generator has already been consumed. Only a list comprehension [i*i for i in
range(4)] can be iterated repeatedly.
• Generators are lazy — they compute values on demand and hold only one at a time,
which is why they are used for large or infinite sequences.
• A function containing yield returns a generator when called; the body does not
execute until the first next().
Traps
• Passing a generator to two consumers means the second gets nothing.
• len() does not work on a generator.
• A generator expression in a function call needs no extra parentheses: sum(i*i for i in
range(4)) is valid.
20. Cross-language comparison table
Worth knowing cold if your test covers both languages.
Behaviour
-7 / 2 (integer)
-7 % 3
Array bounds
String mutability
Assignment semantics
Argument passing
Integer size
Variable declaration
Memory management
Uninitialised variable
Section 7 revision checklist
# Must-recall item
1 C: integer ÷ integer is integer; cast before
dividing
2 C: sizeof(array) vs sizeof(pointer); arrays
decay in function calls
3 C: a[i] means *(a+i), so 2[arr] is legal
4 C: strlen excludes the null terminator,
sizeof includes it
5 C: strcmp returns 0 when strings are equal
6 C: static locals persist between calls and
default to 0
7 C: pass by value; pass an address to modify
a caller’s variable
8 C: struct size includes padding; union size
is its largest member
9 C: macros are text substitution —
parenthesise everything
10 C: i = i++ and printf("%d %d", i++, +
+i) are undefined/unspecified
11 C: break exits only the innermost loop
12 Python: = binds a name, never copies
13 Python: mutating an argument is visible to
the caller; rebinding is not
14 Python: [[0]*3]*2 shares one inner list
15 Python: default arguments are evaluated
once at definition
16 Python: closures capture the variable, not
the value
17 Python: slices are half-open; out-of-range
slicing is safe, indexing is not
18 Python: // floors and % takes the divisor’s
sign
19 Python: "0" is truthy
20 Python: tuples are shallowly immutable;
t[0] += [x] mutates and raises
21 Python: dicts preserve insertion order
(3.7+); sort is stable
22 Python: [Link]() returns None
23 Python: mutable class attributes are shared
across instances
24 Python: MRO for a diamond is E, B, D, A,
object
25 Python: finally always runs; a return in it
overrides the try
26 Python: a generator can be consumed only
once
How to prepare this section
Do not read this section — run it. Every snippet above is short enough to retype in under
a minute. Open a terminal, write your prediction on paper, run the code, and compare. The
snippets where you were wrong are your revision list, and there will typically be only five
or six of them.
Priority order if time is short:
1. Python mutability and copying (Chapters 10–12) — the single most-tested cluster in
modern assessments.
2. C pointers, arrays and strings (Chapters 4–5) — the most-tested cluster in IT-
services and core-engineering papers.
3. Python collections and scope (Chapters 13–16).
4. C structs, macros and storage classes (Chapters 6–8).
5. OOP, exceptions and generators (Chapters 17–19) — more common in interviews
than in written tests.
On question banks generally: treat published answers to C questions involving multiple
side effects in one expression with suspicion (Chapter 9). Where a bank’s answer conflicts
with what your compiler produces, the honest conclusion is usually that the question is ill-
posed rather than that either of you is wrong.