A strong answer
A strong candidate frames Functions versus macros: what is the difference? around the exact memory layout, compiler semantics, and hardware execution model covered in Reusing Code: Functions and Parameters. Explain how functions differ from preprocessor macros in type safety, evaluation, and debugging.
Suppose your program needs to calculate the area of a circle or check whether a user's input lies within a valid range in four different places. If you copy and paste the same calculation four times across main, three problems appear immediately:
- Maintenance bugs: When you later realize
3.14is not accurate enough and switch to3.141592653589793, you must hunt down every copy. Miss just one copy and two parts of your program will disagree. - Unreadable
main: A 1,000-linemainfunction where every low-level step is written inline becomes a wall of text where you cannot see the high-level story of the program. - No isolated testing: You cannot easily test one piece of logic on edge cases (
0, negative numbers, large inputs) when it is tangled inside the middle of a giant loop.
In a production interview, always connect the specification rule to its concrete runtime consequence: state what lives on the stack versus the heap or static storage segment, how the compiler or operating system validates alignments, lifetimes, and error codes, and what happens under adversarial or boundary inputs (such as zero-length buffers, integer overflow, concurrent mutation, or partial I/O). Backing your explanation with a small trace of register/memory state transitions immediately separates senior systems engineers from candidates who only memorized syntax.
What the interviewer is listening for
- Mastery of core invariant: Define a function with a return type, name, and parameters.
- Mastery of core invariant: Call a function and pass arguments to it.
- Mastery of core invariant: Return a value from a function using the return statement.
- Hardware and ABI awareness: Explaining how sizes, alignments, calling conventions, or kernel boundaries behave on real 64-bit targets rather than relying on implementation-defined assumptions.
- Defensive engineering: Naming the exact compiler flags (
-Wall -Wextra -Werror), static analyzers, or runtime sanitizers (-fsanitize=address,undefined,thread) that verify correctness in CI.
Common mistakes
- Explaining only surface syntax without tracing the underlying memory addresses, ownership lifetime, or kernel/user-space state transitions.
- Assuming undefined behavior (such as signed overflow, uninitialized reads, strict-aliasing violations, or dangling pointers) will "just work" because it passed an unoptimized
-O0debug build. - Ignoring return codes, partial reads/writes, alignment padding, or cleanup paths when an allocation or syscall fails midway through a function.