I started talking about inlining as a performance hint and the interviewer immediately pushed back asking what it actually guarantees at the language level.
Start by clarifying that inline is a request, not a command, and that its meaning has evolved across C++ standards. Then distinguish between the compiler's optimization hint (which it may ignore) and the language's ODR-related guarantees (which it must honor). Use concrete examples to illustrate both aspects.
Pro tip: Mention that modern compilers largely ignore inline for optimization and that the keyword's real value today is enabling multiple definitions across translation units without violating the ODR. This shows you understand both historical context and current best practices.
Explain that inline is a hint to the compiler to expand the function body at the call site, but the compiler is free to ignore it. Emphasize that it does not guarantee inlining.
Describe how inline allows a function or variable to be defined in multiple translation units as long as all definitions are identical. This is a language guarantee, not a hint.
Note that modern compilers perform inlining based on their own cost models, often ignoring the inline keyword. Mention that inline is not needed for optimization.
Introduce inline variables, which allow a single definition of a variable across multiple translation units, useful for header-only libraries.
Conclude that inline should be used primarily for ODR purposes (e.g., functions defined in headers) and not as an optimization directive. Mention that compilers handle inlining automatically.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.
The binary size part was fine, duplicating the function body at every call site obviously bloats the text segment.
Start by explaining the mechanics of inlining and how it duplicates code at each call site, leading to binary bloat. Then, describe a scenario where inlining hurts latency, such as increased instruction cache pressure or inhibited compiler optimizations, and contrast it with a non-inlined call that avoids these issues.
Pro tip: Mention that inlining is a trade-off between speed and size, and that modern compilers use heuristics; sometimes forcing inlining can backfire, so profiling is essential.
Briefly explain that inlining replaces a function call with the function's body to eliminate call overhead and enable further optimizations.
Describe how inlining duplicates code at each call site, increasing the total code size, especially for functions called in many places or with large bodies.
Discuss how inlining can sometimes increase latency due to instruction cache misses, increased register pressure, or preventing other optimizations like loop unrolling.
Provide a concrete example, such as a large function called in a hot loop where inlining causes instruction cache thrashing, while a non-inlined call keeps the hot loop compact and cache-friendly.
Summarize that inlining is not always beneficial and should be guided by profiling and understanding of the specific workload and architecture.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.
Separated allocation from construction which landed well.
Start by clarifying that `new` and `malloc` operate at different levels: `new` is a language-level operator that combines memory allocation and object construction, while `malloc` is a C library function that only allocates raw memory. Then, walk through the typical implementation of each, highlighting the layers from high-level semantics down to system calls, and discuss trade-offs like performance, control, and safety.
Pro tip: Mention that `new` typically calls `operator new` (which often uses `malloc` under the hood) and then invokes the constructor, and that `malloc` may use `brk`/`sbrk` or `mmap` for large allocations—showing you understand the full stack from language to OS.
Clarify that `new` is a C++ operator with language semantics, while `malloc` is a C library function for raw memory allocation. Emphasize that they serve different purposes and operate at different abstraction layers.
Describe the two-step process: first, it calls `operator new` (which by default uses `malloc`) to allocate memory; second, it invokes the constructor to initialize the object. Mention that `new[]` handles arrays and may include overhead for bookkeeping.
Discuss that `malloc` manages a heap using data structures like free lists or bins, and may use system calls like `brk`/`sbrk` for small allocations or `mmap` for large ones. Highlight that it returns uninitialized memory and requires manual size calculation.
Contrast the two: `new` is type-safe, calls constructors, and throws exceptions on failure; `malloc` returns `void*`, requires explicit casting, and returns NULL on failure. Note that `new` can be overloaded, while `malloc` cannot.
Talk about performance (e.g., `new` may have overhead due to constructors), control (e.g., custom allocators with `operator new`), and safety (e.g., RAII vs manual memory management). Mention that mixing `new`/`delete` with `malloc`/`free` is undefined behavior.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.
No, most allocations are fast-path and stay in user space.
Start by clarifying that not every allocation hits the OS—most are served from the allocator's user-space cache, and only when the cache is exhausted does the allocator request more memory via brk/mmap. Then explain how to avoid OS involvement on a hot path by pre-allocating memory, using custom allocators or pools, and avoiding operations that trigger syscalls.
Pro tip: Mention that even with a custom allocator, you must consider page faults: pre-faulting memory (e.g., touching pages or using MAP_POPULATE) avoids faults on the hot path. Also, be aware that glibc's malloc may call madvise or trim, so consider using a different allocator like jemalloc or tcmalloc for predictable latency.
Explain that malloc/new typically uses a user-space allocator (e.g., glibc malloc) that maintains free lists and only requests memory from the OS when it needs more. So most calls do not involve the OS.
Describe the conditions: when the allocator's heap is exhausted, it calls brk or mmap to get more memory; also, large allocations may directly use mmap. Additionally, page faults can occur on first touch even if memory was already allocated.
List techniques: pre-allocate all needed memory at startup, use object pools or custom allocators (e.g., arena, slab), avoid dynamic allocation in hot loops, and reuse buffers.
Mention that even with pre-allocation, first access may cause page faults. Pre-fault memory by touching pages or using MAP_POPULATE. Also avoid syscalls like madvise or mlock on the hot path.
Discuss trade-offs: pre-allocation increases memory usage and startup time; custom allocators add complexity. Mention alternative allocators (jemalloc, tcmalloc) that may have better latency characteristics.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.
Stack is basically a register increment, a few cycles, and the memory is almost certainly already in cache.
Start by giving a rough order-of-magnitude estimate (e.g., heap allocation is typically 10-100x slower than stack allocation) and then explain the fundamental reasons: stack allocation is just a pointer bump, while heap allocation involves finding free memory, bookkeeping, and potential synchronization. Emphasize that the exact ratio depends on the allocator, workload, and hardware, so avoid absolute numbers.
Pro tip: Mention that modern allocators (like tcmalloc or jemalloc) and thread-local caches can significantly reduce heap allocation overhead, but the stack remains faster due to its simplicity. Also note that the performance difference matters most in hot loops, so profile before optimizing.
State that heap allocation is typically 10 to 100 times slower than stack allocation, but acknowledge that this varies widely based on implementation and usage patterns.
Describe stack allocation as a simple adjustment of the stack pointer, which is a single CPU instruction, and deallocation is equally trivial.
Outline the steps in heap allocation: searching for a free block (first-fit, best-fit, etc.), updating metadata, handling fragmentation, and potentially locking for thread safety.
Mention that the slowdown depends on allocator design (e.g., malloc vs. custom pool), allocation size, frequency, and whether the memory is already cached.
Summarize that while stack is faster, heap is necessary for dynamic lifetime and large data; recommend measuring and using techniques like object pools or arena allocation when performance is critical.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.
Framed it as compile-time versus runtime dispatch.
Start by defining both mechanisms and their polymorphism styles: templates provide compile-time (static) polymorphism, while inheritance provides runtime (dynamic) polymorphism. Then compare them across key dimensions like performance, flexibility, and coupling, and give concrete criteria for choosing one over the other based on the problem's constraints.
Pro tip: Mention that modern C++ often combines both: use templates for generic algorithms and inheritance for runtime extensibility, and consider type erasure as a bridge. This shows you think in terms of trade-offs rather than dogma.
Briefly explain that templates enable static polymorphism by generating code at compile time for each type, while inheritance enables dynamic polymorphism through virtual functions and base class interfaces.
Contrast them on performance (templates avoid virtual call overhead but may bloat code), flexibility (inheritance allows runtime substitution), and coupling (templates require compile-time knowledge of types, inheritance allows binary compatibility).
Give examples: templates for generic containers/algorithms (e.g., std::sort), inheritance for plugin architectures or GUI event handlers where types are unknown until runtime.
Explain that you choose templates when performance and compile-time type safety are critical and the set of types is known, and inheritance when you need runtime polymorphism, extensibility, or a stable ABI.
Mention that the two can be combined, e.g., templates for policy-based design or type erasure (like std::function) to get runtime flexibility with template-based implementation.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.
Red-black tree versus hash table, ordered versus unordered, O(log n) versus average O(1).
Start by defining both containers and their underlying data structures, then compare their performance characteristics, including average and worst-case complexities. Emphasize that std::map guarantees O(log n) worst-case for operations, while std::unordered_map offers average O(1) but worst-case O(n). Finally, discuss when to choose each based on ordering, performance, and hash quality.
Pro tip: Mention that std::unordered_map's worst-case O(n) can be mitigated by using a good hash function and that C++ standard doesn't mandate a specific implementation, but typically uses chaining. Also, note that std::map's ordered nature enables range queries and ordered iteration, which unordered_map lacks.
Explain that std::map is typically implemented as a balanced binary search tree (e.g., red-black tree), while std::unordered_map uses a hash table.
Highlight that std::map maintains elements in sorted order by key, allowing ordered iteration and range queries, whereas std::unordered_map has no defined order.
State that std::map provides O(log n) worst-case for insertion, deletion, and lookup; std::unordered_map provides average O(1) but worst-case O(n) for these operations.
Mention that std::map has higher memory overhead per element due to tree nodes, while std::unordered_map may have overhead from hash buckets and potential rehashing.
Conclude that std::map is preferable when ordered data or guaranteed worst-case performance is needed; std::unordered_map is better for fast average-case access when order doesn't matter.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.
Hash the key, mod by bucket count, walk the chain at that bucket comparing for equality.
Start by explaining that unordered_map uses a hash function to convert the key into an integer, then applies modulo or bitwise AND with the bucket count to determine the bucket index. Emphasize that this is the core mechanism, and then discuss how collisions are handled (e.g., separate chaining) and the role of the load factor in resizing.
Pro tip: Mention that the bucket index is typically computed as hash(key) % bucket_count, but implementations often use a prime number of buckets or power-of-two with bitmasking to optimize modulo operations. This shows awareness of real-world implementation details.
Explain that unordered_map applies a hash function to the key, producing a size_t hash value. Mention that the default hash is std::hash, but custom hash functions can be provided.
Describe how the hash value is mapped to a bucket index, typically using modulo by the number of buckets (or bitwise AND if bucket count is a power of two). This determines the target bucket.
Explain that multiple keys may map to the same bucket (collision). Most implementations use separate chaining, where each bucket holds a linked list (or similar structure) of key-value pairs.
Discuss that unordered_map maintains a load factor (elements/buckets). When it exceeds a threshold (e.g., 1.0), the map rehashes: it increases the number of buckets and reinserts all elements, recomputing bucket indices.
Summarize the full lookup: hash the key, compute bucket index, then search the bucket's chain for the matching key (using equality comparison). Mention average O(1) time complexity.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.
The MMU raises a fault when the process touches a virtual page it has no permission to access or that isn't mapped.
Start by defining a segmentation fault as a hardware-detected error triggered when a process accesses memory it doesn't have permission to access. Then walk through the hardware and OS-level sequence: MMU translation, page fault exception, kernel trap, and signal delivery. Finally, connect this to common programming causes like null pointer dereference or buffer overflow.
Pro tip: Emphasize that a segfault is a symptom, not the root cause—demonstrating you can trace from the crash back to the faulty code shows strong debugging maturity.
Explain that a segmentation fault occurs when a program tries to access a memory segment it is not allowed to, such as writing to read-only memory or dereferencing a null pointer.
Describe how the CPU's memory management unit (MMU) translates virtual addresses to physical addresses and raises a page fault exception when the access violates permissions or the page is not present.
Detail how the kernel's page fault handler checks if the access is valid; if not, it sends a SIGSEGV signal to the offending process, often terminating it and possibly generating a core dump.
Mention typical causes like null pointer dereference, buffer overflow, use-after-free, and stack overflow, and briefly note tools like gdb or valgrind for diagnosis.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.
This one is subtle and I almost got it wrong.
Clarify that the question is about undefined behavior in C/C++, not a guaranteed crash. Explain that binding a reference to a dereferenced null pointer is UB, and the crash may occur at the point of dereference, at the point of use, or never, depending on compiler optimizations and platform. Emphasize that the standard imposes no requirements, so the behavior is unpredictable.
Pro tip: Mention that compilers often optimize based on the assumption that references are never null, so the crash might not happen where you expect—or might be optimized away entirely. This shows deep understanding of UB and compiler behavior.
State that binding a reference to *nullptr is undefined behavior in C++. The reference itself is not an object; it's an alias, so the act of binding may not generate code.
Emphasize that the C++ standard does not specify what happens. The program is ill-formed, no diagnostic required, and the compiler may assume it never happens.
Describe that a crash typically happens when the reference is used to access memory (read/write), but it could also happen at the point of binding if the compiler emits a load. It may also never crash if the reference is unused or optimized out.
Explain that compilers may exploit UB to optimize code, e.g., assuming the reference is valid, leading to unexpected behavior or removal of null checks.
Summarize that relying on a specific crash point is dangerous; code should never dereference null pointers, and tools like sanitizers can help detect such issues.
AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.