← Hudson Interview Insights

Hudson·Software Engineer·Technical Phone Screen·Senior

SeniorPrefer not to say
Jun 2026

Summary

Rapid-fire C++ fundamentals screen for a software engineering role at Hudson, clearly aimed at low-latency / HFT-style work. Five topic areas back to back, all pushing on mechanism and cost rather than definitions. Felt more like a debugging session than a typical interview.

Questions Asked (10)

Q1

What does the inline keyword actually mean in C++, and what does it guarantee versus what it only hints at?

Technical Trade-offs
Author's notes

I started talking about inlining as a performance hint and the interviewer immediately pushed back asking what it actually guarantees at the language level.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

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.

1. Define inline as a request

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.

2. Explain the ODR guarantee

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.

3. Contrast with compiler optimizations

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.

4. Discuss inline variables (C++17)

Introduce inline variables, which allow a single definition of a variable across multiple translation units, useful for header-only libraries.

5. Summarize best practices

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.

Key Points to Mention

  • inline is a hint, not a guarantee, for inlining
  • The ODR allows multiple identical definitions of inline functions across TUs
  • Modern compilers ignore inline for optimization decisions
  • inline functions must be defined in every TU where they are odr-used
  • C++17 introduced inline variables for similar ODR benefits
  • inline is commonly used for functions defined in headers to avoid multiple definition errors

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q2

Walk through exactly why inlining a function can increase binary size, and describe a scenario where a non-inlined call gives better latency than an inlined one.

Technical Trade-offsSystem Design
Author's notes

The binary size part was fine, duplicating the function body at every call site obviously bloats the text segment.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

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.

1. Define inlining and its purpose

Briefly explain that inlining replaces a function call with the function's body to eliminate call overhead and enable further optimizations.

2. Explain binary size increase

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.

3. Introduce latency trade-offs

Discuss how inlining can sometimes increase latency due to instruction cache misses, increased register pressure, or preventing other optimizations like loop unrolling.

4. Describe a scenario where non-inlined call is better

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.

5. Conclude with trade-off summary

Summarize that inlining is not always beneficial and should be guided by profiling and understanding of the specific workload and architecture.

Key Points to Mention

  • Code duplication at call sites leads to larger binary size.
  • Inlining can increase instruction cache pressure, causing more cache misses.
  • Register pressure and stack usage may increase with inlining, potentially causing spills.
  • Inlining can inhibit other optimizations like loop unrolling or vectorization.
  • Non-inlined calls can keep hot code compact, improving instruction cache locality.
  • Compiler heuristics and profiling are essential to decide when to inline.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q3

What does the new keyword do under the hood, and can you describe what malloc is actually doing at the implementation level?

System DesignTechnical Trade-offs
Author's notes

Separated allocation from construction which landed well.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

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.

1. Define the scope and levels

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.

2. Explain `new` under the hood

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.

3. Explain `malloc` at the implementation level

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.

4. Compare and contrast

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.

5. Discuss trade-offs and practical implications

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.

Key Points to Mention

  • `new` calls `operator new` for allocation and then the constructor for initialization.
  • `malloc` returns uninitialized memory and requires manual size calculation and casting.
  • `malloc` often uses `brk`/`sbrk` or `mmap` system calls to obtain memory from the OS.
  • `new` is type-safe and can throw `std::bad_alloc` on failure; `malloc` returns NULL.
  • `new` can be overloaded per class or globally, allowing custom memory management.
  • Mixing `new`/`delete` with `malloc`/`free` leads to undefined behavior.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q4

Does the OS get involved every time you call new or malloc? How would you avoid touching the OS on a latency-sensitive hot path?

System DesignTechnical Trade-offs
Author's notes

No, most allocations are fast-path and stay in user space.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

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.

1. Clarify the common case

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.

2. Identify when the OS is involved

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.

3. Strategies to avoid OS on hot path

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.

4. Address page faults and other syscalls

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.

5. Consider trade-offs and alternatives

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.

Key Points to Mention

  • User-space allocator caching (free lists, bins) avoids OS calls for most allocations.
  • OS involvement occurs via brk/mmap when the heap needs to grow or for large allocations.
  • Page faults are a hidden OS cost even after memory is allocated; pre-faulting avoids them.
  • Pre-allocation, memory pools, and custom allocators (arena, slab) keep allocation in user space.
  • Avoiding dynamic allocation entirely on hot paths is the safest approach.
  • Alternative allocators like jemalloc or tcmalloc can provide more predictable latency.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q5

Roughly how much slower is heap allocation compared to stack allocation, and why?

Technical Trade-offsAlgorithms & Data Structures
Author's notes

Stack is basically a register increment, a few cycles, and the memory is almost certainly already in cache.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

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.

1. Give a rough estimate

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.

2. Explain stack allocation

Describe stack allocation as a simple adjustment of the stack pointer, which is a single CPU instruction, and deallocation is equally trivial.

3. Explain heap allocation

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.

4. Discuss factors affecting the ratio

Mention that the slowdown depends on allocator design (e.g., malloc vs. custom pool), allocation size, frequency, and whether the memory is already cached.

5. Conclude with practical implications

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.

Key Points to Mention

  • Stack allocation is O(1) pointer arithmetic; heap allocation involves complex algorithms and bookkeeping.
  • Heap allocation may require system calls (e.g., brk/mmap) when the heap needs to grow, adding significant overhead.
  • Thread safety: heap allocators often use locks or atomic operations, while stack is inherently thread-local.
  • Memory fragmentation and cache locality: heap allocations can lead to fragmentation and poorer cache performance.
  • Modern allocators (tcmalloc, jemalloc) use thread-local caches and size classes to reduce overhead.
  • The actual slowdown varies: for small, frequent allocations, it can be 100x; for large, infrequent ones, less.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q6

Compare templates and inheritance as tools for polymorphism. When would you choose one over the other?

Technical Trade-offsSystem Design
Author's notes

Framed it as compile-time versus runtime dispatch.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

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.

1. Define both mechanisms

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.

2. Compare key dimensions

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).

3. Identify use cases

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.

4. State decision criteria

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.

5. Acknowledge hybrid approaches

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.

Key Points to Mention

  • Compile-time vs. runtime polymorphism
  • Performance implications: virtual call overhead vs. code bloat
  • Flexibility and extensibility: open/closed principle, plugin architectures
  • Coupling and compile-time dependencies
  • Binary compatibility and ABI stability
  • Hybrid approaches like type erasure or policy-based design

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q7

What are the differences between std::map and std::unordered_map, including worst-case complexity for each?

Algorithms & Data StructuresTechnical Trade-offs
Author's notes

Red-black tree versus hash table, ordered versus unordered, O(log n) versus average O(1).

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

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.

1. Define underlying data structures

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.

2. Compare ordering and iteration

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.

3. Analyze time complexities

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.

4. Discuss memory and performance trade-offs

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.

5. Provide use-case recommendations

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.

Key Points to Mention

  • std::map is a balanced BST (usually red-black tree) with O(log n) worst-case operations.
  • std::unordered_map is a hash table with average O(1) and worst-case O(n) operations.
  • std::map maintains sorted order; std::unordered_map does not.
  • Hash collisions can degrade std::unordered_map performance to O(n).
  • std::map supports range queries and ordered iteration; std::unordered_map does not.
  • Memory overhead: std::map typically has higher per-element overhead due to tree nodes; std::unordered_map may have overhead from buckets and rehashing.

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q8

How does an unordered_map actually locate the right bucket for a given key?

Algorithms & Data StructuresSystem Design
Author's notes

Hash the key, mod by bucket count, walk the chain at that bucket comparing for equality.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

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.

1. Hash Function

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.

2. Bucket Index Calculation

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.

3. Collision Resolution

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.

4. Load Factor and Rehashing

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.

5. Lookup Process

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.

Key Points to Mention

  • Hash function (std::hash) and its role in generating a hash code
  • Bucket index computation: modulo or bitwise AND with bucket count
  • Collision handling via separate chaining (linked lists or trees in some implementations)
  • Load factor and automatic rehashing when threshold is exceeded
  • Average O(1) time complexity for lookup, worst-case O(n) with many collisions
  • Custom hash functions and their impact on performance

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q9

What is a segmentation fault and what is actually happening at the hardware and OS level when one occurs?

Technical Trade-offsRoot Cause Analysis
Author's notes

The MMU raises a fault when the process touches a virtual page it has no permission to access or that isn't mapped.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

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.

1. Define the fault

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.

2. Hardware detection

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.

3. OS handling

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.

4. Common causes and debugging

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.

Key Points to Mention

  • MMU and virtual memory translation
  • Page fault exception and kernel trap
  • SIGSEGV signal delivery
  • Common causes: null pointer, buffer overflow, use-after-free
  • Core dump and debugging tools (gdb, valgrind)
  • Difference between segmentation fault and other signals like SIGBUS

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.

Q10

If you bind a reference to a dereferenced null pointer, when exactly does the crash happen and why?

Technical Trade-offsRoot Cause Analysis
Author's notes

This one is subtle and I almost got it wrong.

Create a free account to read the full note

AI HintsAI Generated

Suggested Approach

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.

1. Define the scenario

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.

2. Explain the undefined behavior

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.

3. Discuss when a crash might occur

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.

4. Highlight compiler optimizations

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.

5. Conclude with practical implications

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.

Key Points to Mention

  • Undefined behavior in C++ standard
  • References are not pointers; binding may not generate code
  • Crash may occur at point of use, not binding
  • Compiler optimizations can assume references are non-null
  • Platform and compiler-dependent behavior
  • Use of sanitizers (e.g., UBSan) to catch such bugs

AI-generated suggestions, not part of the candidate's original notes. May be inaccurate — verify before relying on them.