Why C++ Depth Dominates HFT Interviews
For Hudson River Trading, Jump Trading, Citadel Securities, Tower Research, Radix, DRW and most other low-latency trading firms, C++ depth isn't optional - it's the dominant filter. The questions go well beyond "can you write C++" into specific topics that matter for nanosecond-sensitive systems: the memory model, lock-free programming, false sharing, branch prediction, the practical implications of modern C++ features, and the runtime behaviour of standard library containers.
This guide collects 20 worked examples from recent HRT, Jump, Citadel Securities, Tower, Radix and DRW engineering interviews. For broader context, see our C++ in quantitative finance and hardware acceleration for quant guides.
Section 1: Memory Model and Concurrency (Questions 1-7)
1. Memory ordering
What's the difference between memory_order_relaxed, memory_order_acquire, memory_order_release, and memory_order_seq_cst?
Answer: Relaxed: atomicity only; no ordering. Acquire: prevents subsequent operations from being reordered before the load. Release: prevents prior operations from being reordered after the store. Seq_cst: total global ordering across all atomic operations. Use the weakest sufficient. Acquire-release pairs are the standard for producer-consumer.
2. False sharing
Two threads each increment their own counter. The counters happen to be in the same cache line. What goes wrong, and how do you fix it?
Answer: Cache line ping-pong. Each write invalidates the other thread's cached copy, forcing memory fetches. Performance can be 10-100x slower than expected. Fix: pad to separate cache lines using alignas(64).
3. Volatile vs atomic
What's the difference, and when does volatile suffice?
Answer: volatile prevents the compiler from optimising away reads/writes (important for memory-mapped I/O). It does NOT provide atomicity or memory ordering across threads - that's std::atomic's job. In multithreaded code, prefer std::atomic. volatile is essentially obsolete except for embedded systems.
4. Lock-free SPSC queue
Implement a single-producer single-consumer ring buffer.
template <typename T, size_t N> class SPSCQueue { alignas(64) std::atomic<size_t> head{0}; alignas(64) std::atomic<size_t> tail{0}; T buffer[N]; public: bool push(const T& item) { size_t h = head.load(std::memory_order_relaxed); size_t next = (h + 1) % N; if (next == tail.load(std::memory_order_acquire)) return false; buffer[h] = item; head.store(next, std::memory_order_release); return true; } bool pop(T& item) { size_t t = tail.load(std::memory_order_relaxed); if (t == head.load(std::memory_order_acquire)) return false; item = buffer[t]; tail.store((t + 1) % N, std::memory_order_release); return true; } };
Key points: alignas(64) on head and tail; release-acquire pair between producer-consumer.
5. Compare-and-swap (CAS)
What does atomic::compare_exchange_strong do, and why is the "weak" variant sometimes preferred?
Answer: CAS atomically: if the current value equals expected, replace with desired and return true; otherwise update expected to current value and return false. Strong: guaranteed to succeed if value matches. Weak: may spuriously fail (return false even when value matches). Weak is faster on some architectures (ARM, PowerPC) - use weak in retry loops where spurious failure is harmless.
6. ABA problem
What's the ABA problem in lock-free programming, and how do you avoid it?
Answer: Thread A reads value V_A. Thread B changes to V_B then back to V_A. Thread A's CAS succeeds (sees V_A) but the underlying state is different. Mitigations: tagged pointers (combine value with version counter), hazard pointers, RCU, or epoch-based reclamation.
7. Hazard pointers
When would you use hazard pointers instead of garbage collection or reference counting in a lock-free data structure?
Answer: Hazard pointers are a way to safely reclaim memory in lock-free data structures. Each thread "publishes" the pointers it's currently using; reclamation can only happen when no thread has a hazard pointer to that memory. Used when reference counting is too slow (atomic increment/decrement on every access) and garbage collection isn't available.
Section 2: Modern C++ (Questions 8-14)
8. Move semantics
What does std::move actually do?
Answer: std::move is a cast to rvalue reference. It signals "this object can be moved from." Move constructors and move assignment operators take rvalue references and can pilfer resources from the source. This is critical for performance with types holding heap-allocated state (vectors, strings, unique_ptrs).
9. RAII
Explain RAII and give an example.
Answer: Resource Acquisition Is Initialisation: acquire a resource in the constructor; release in the destructor. The compiler guarantees destruction happens (even on exception). Example: std::lock_guard<std::mutex> acquires the mutex in constructor, releases in destructor. RAII is the foundation of exception-safe C++.
10. unique_ptr vs shared_ptr
When use which?
Answer: unique_ptr: single owner; can be moved. Zero overhead vs raw pointer. Use this 90% of the time. shared_ptr: multiple owners via reference counting. Atomic increment/decrement on every copy - significant overhead. Use only when ownership is genuinely shared (graph structures, observer patterns).
11. Constexpr
What's the difference between const and constexpr?
Answer: const means "this value won't change after initialisation" (runtime constant). constexpr means "this value must be computable at compile time." Constexpr enables compile-time computation. Modern C++ has constexpr functions, allowing complex computations to happen at compile time.
12. Template specialisation
What's the difference between full and partial specialisation?
Answer: Full specialisation specifies all template parameters: template<> class Container<int> {}. Partial specialisation specifies some parameters and constrains others: template<typename T> class Container<T*> {}. Class templates support partial specialisation; function templates do not (use overloading instead).
13. Lambdas
What's captured by reference vs by value in a lambda?
Answer: [=] captures all by value; [&] captures all by reference; [x, &y] captures x by value and y by reference. By value captures a copy at lambda creation; by reference captures the reference (so the lambda sees later changes). Be careful: capturing by reference can dangle if the lambda outlives the captured variable.
14. SFINAE
What is SFINAE, and what's the modern alternative?
Answer: Substitution Failure Is Not An Error - if template substitution fails, the compiler tries other overloads instead of erroring. Used historically for type-trait-based overload selection. Modern alternative (C++20): concepts and requires clauses, which are clearer and produce better error messages.
Section 3: Performance and Architecture (Questions 15-20)
15. Cache-friendly data layout
SoA vs AoS - when do you choose each?
Answer: Struct of Arrays (SoA): each field stored in a separate array. Best when you frequently access only a subset of fields - the unused fields don't pollute the cache. Common in numerical/scientific computing. Array of Structs (AoS): each object stored together. Best when you typically access all fields of an object together. For trading systems: AoS for individual order processing; SoA for batch analytics.
16. Branch prediction
You have a loop with an if that's true less than 1% of the time. How do you optimise?
Answer: Mark with [[unlikely]] (C++20) or __builtin_expect (GCC/Clang). Restructure to avoid the branch entirely if possible. Modern CPUs predict well, so always profile first - the optimisation may not be needed.
17. Inlining trade-offs
When does inlining hurt performance?
Answer: When the inlined code is large enough to cause instruction cache pressure, or when it disables important optimisations elsewhere. The general rule: inline small hot functions; don't inline large or rare functions. Profile-guided optimisation can make better decisions than the developer.
18. RAII with file handles
Write a class that opens a file and ensures it's always closed.
class File { FILE* fp; public: File(const char* path, const char* mode) : fp(fopen(path, mode)) { if (!fp) throw std::runtime_error("open failed"); } ~File() { if (fp) fclose(fp); } File(const File&) = delete; File& operator=(const File&) = delete; File(File&& o) noexcept : fp(o.fp) { o.fp = nullptr; } File& operator=(File&& o) noexcept { if (this != &o) { if (fp) fclose(fp); fp = o.fp; o.fp = nullptr; } return *this; } };
Note: copy disabled, move enabled. Modern alternative: std::unique_ptr<FILE, decltype(&fclose)>.
19. std::function vs function pointer
What's the cost difference?
Answer: Function pointer: zero-cost call through a pointer. std::function: type erasure with potential heap allocation; calls go through virtual dispatch. std::function is 5-10x slower than function pointer in tight loops. For latency-critical code, prefer function pointers, lambdas captured into auto, or templates.
20. Memory pool
Why use a custom memory pool instead of malloc on a hot path?
Answer: (1) Latency consistency - malloc has unpredictable tail latency from fragmentation, system calls, lock contention. (2) Cache locality - pool-allocated objects co-located. (3) No global synchronisation - thread-local pool avoids cross-thread contention. The trade-off: complexity and reduced flexibility. Standard pattern: pool of fixed-size blocks, free list for reuse.
How to Use This Guide
For low-latency trading firms (HRT, Jump, Tower, Radix, Citadel Securities, DRW), all 20 questions are essential. For Jane Street (uses OCaml primarily), the C++ depth is less directly tested but the underlying systems concepts are still relevant.
For broader prep:
- Quant interview questions hub
- Quant coding interview questions
- C++ in quantitative finance
- Hardware acceleration for quant
- Networking fundamentals for developers
For firm-specific interview content where C++ depth is heavily tested:
Practise the questions C++ Quant Interview Questions: 20 Real Examples 2026 actually asks
Reading about the interview is one thing - sitting one is another. Quantt's interactive coding tests are modelled on the same problem types that show up in firms like Jane Street, Citadel, Hudson River and Optiver. Run real Python in the browser, get instant feedback, and benchmark yourself against the bar.
Free to start - no credit card required