By Quant Blueprint · 7 min read
C++ Quant Developer Interview: 5 Worked Cases
Prepare for C++ quant developer interviews with five original cases on ownership, lifetimes, a bounded queue, concurrency and performance measurement.
C++ quant developer interview preparation should connect language rules to the systems you would maintain. Can you identify an invalid lifetime, specify a queue's behavior and explain what a performance measurement actually measured?
The emphasis depends on the role. HRT's Core Development overview names C++, operating-system internals, CPU architecture and networking. That makes systems reasoning relevant for those engineering roles; it does not make every quant applicant a low-latency specialist. Start with your actual job description and recruiter instructions.
These five cases are original Quant Blueprint practice material, not reported employer questions. The runnable examples use C++17. Compile them with warnings enabled, such as clang++ -std=c++17 -Wall -Wextra -Wpedantic exercise.cpp, and explain your reasoning before reading each answer.
1. Who owns the bytes after a function returns?
A function constructs a local std::string and returns a std::string_view referring to it. The caller prints the view. Why might the code seem to work during a quick test, and how would you repair the interface?
Show answer and explanation
The view does not own the characters. When the local string is destroyed at function exit, the returned view no longer designates a live string's storage. Reading it has undefined behavior; an apparently correct output does not validate the lifetime.
Return an owning string when the caller needs its own result:
#include <cassert>
#include <string>
std::string make_symbol() {
return std::string("ALPHA");
}
int main() {
auto symbol = make_symbol();
assert(symbol == "ALPHA");
}
Do not add std::move to every return in an attempt to improve it. Understand value return and copy elision first. If you instead return a view of caller-owned input, document that the input must outlive every use of the view and that storage-changing mutations can invalidate it.
Follow-up: is a view into a vector element safe while the vector grows? Reallocation can invalidate references and pointers to the elements. Reserving capacity may avoid some reallocations, but it does not establish a general lifetime guarantee. Ask which operation mutates the backing storage and which object owns it.
2. Does this function borrow an object or take ownership?
You are designing three interfaces: inspect an existing configuration, accept a configuration that the function will own, and share a configuration whose lifetime genuinely has multiple owners. Which types express those contracts?
Show answer and explanation
For a required borrowed object, const Config& communicates inspection without ownership transfer. std::unique_ptr<Config> passed by value communicates transfer of unique ownership. A caller moves its pointer into the function and must account for losing that ownership. std::shared_ptr<const Config> can express shared ownership with read-only access through that handle when sharing is actually required. Other mutable aliases could still change the object; the handle alone does not establish global immutability.
These choices follow the C++ Core Guidelines' resource-management guidance, including RAII and explicit smart-pointer ownership. They are interface decisions, not interchangeable syntax preferences.
Ask what happens if the function throws halfway through setup. A resource managed by a completed automatic object's destructor is released during stack unwinding. A manually paired acquisition/release can miss the release on an exceptional path. RAII applies to locks, files and other resources as well as memory.
Follow-up: does shared_ptr make the pointed-to object safe to mutate concurrently? No. Shared ownership and synchronization of the object are separate responsibilities. If several threads change configuration fields, you still need a correct access protocol. Do not pay for shared ownership merely to avoid deciding who owns the object.
3. Implement a bounded queue with explicit full and empty behavior
Build a single-threaded FIFO queue for integers. Its capacity is fixed after construction. push returns false if full; pop returns an empty optional if empty. A failed operation must leave the existing queue unchanged. Reject zero capacity.
Before coding, describe what distinguishes a full queue from an empty queue after its indices wrap around.
Show answer and explanation
Use a head index and a count. The next insertion position is (head + count) % capacity; a separate count removes the ambiguity between full and empty.
#include <cassert>
#include <cstddef>
#include <optional>
#include <stdexcept>
#include <vector>
class IntQueue {
std::vector<int> data;
std::size_t head = 0;
std::size_t count = 0;
public:
explicit IntQueue(std::size_t capacity) : data(capacity) {
if (capacity == 0) throw std::invalid_argument("zero capacity");
}
bool push(int value) {
if (count == data.size()) return false;
data[(head + count) % data.size()] = value;
++count;
return true;
}
std::optional<int> pop() {
if (count == 0) return std::nullopt;
int value = data[head];
head = (head + 1) % data.size();
--count;
return value;
}
};
int main() {
IntQueue q(2);
assert(!q.pop());
assert(q.push(10));
assert(q.push(20));
assert(!q.push(99));
assert(q.pop().value() == 10);
assert(q.push(30));
assert(q.pop().value() == 20);
assert(q.pop().value() == 30);
assert(!q.pop());
IntQueue one(1);
assert(one.push(7));
assert(!one.push(8));
assert(one.pop().value() == 7);
bool rejected = false;
try { IntQueue invalid(0); }
catch (const std::invalid_argument&) { rejected = true; }
assert(rejected);
}
The invariants are count <= capacity and head < capacity. Each successful operation performs constant work, and the vector allocates its storage during construction. The example assumes an allocation small enough that index arithmetic cannot overflow. It is a practice implementation, not a claim of production suitability for arbitrary capacities or element types.
Follow-up: what changes for generic objects whose construction can throw? You now need to reason about object lifetime, occupied slots and exception guarantees. Do not replace int with an arbitrary type and assume the same argument is complete.
If you can write the queue but find the follow-ups harder to prioritize, book a free quant consultation to discuss your engineering background and target roles. The broader developer preparation guide helps connect language practice to implementation and systems work.
4. Can another thread use the queue safely?
Two threads call push and pop on the queue above. One developer proposes changing count to an atomic and leaving everything else unchanged. Is that a sufficient repair?
Show answer and explanation
No. The count, head and slot contents form a protocol. An atomic count alone does not establish safe publication of a completed element, protect every shared access or define when a slot can be reused. Concurrent access to the original queue is unsupported.
Start with a mutex-protected interface and specify blocking, shutdown and full-queue behavior. A specialized single-producer/single-consumer queue needs a different proof from a multi-producer/multi-consumer queue. The Core Guidelines' concurrency section emphasizes avoiding data races and minimizing shared writable state.
A smaller illustration is an independent shared counter:
#include <atomic>
#include <cassert>
#include <thread>
int main() {
std::atomic<int> count{0};
auto work = [&] {
for (int i = 0; i < 1000; ++i)
count.fetch_add(1, std::memory_order_relaxed);
};
std::thread a(work), b(work);
a.join();
b.join();
assert(count.load() == 2000);
}
Compile this example with thread support, for example adding -pthread. Relaxed increments suffice for this isolated count; they are not a ready-made message-publication protocol. The thread joins occur before the final check.
Follow-up: what if the count tells a reader that a separate data buffer is ready? You must reason about ordering and synchronization for the buffer too. Explain the required relationship before naming a memory-order flag. A program that passes a stress test can still contain a data race.
5. What would convince you that one implementation is faster?
Someone reports that replacing a linked structure with a contiguous container reduced processing time in one local run. How would you investigate before changing a trading or research service?
Show answer and explanation
Define the workload first: operation mix, active element count, access pattern, allocation behavior and producer/consumer arrangement. A container that improves sequential scans may have different insertion, deletion or address-stability costs.
Record the compiler, optimization flags, machine, dataset and timing boundary. Keep setup outside the timed region unless setup is part of the intended workload. Ensure the computation's result is consumed so the compiler cannot simply remove it. Repeat runs, account for warmup and inspect variation.
For latency, distinguish per-operation latency from total throughput. A fast average can conceal occasional slow allocations or cleanup. Report the distribution your application cares about, with sufficient observations to support tail estimates. Do not invent a universal nanosecond target or infer live performance from a tiny warm-cache loop.
Follow-up: an optimization improves typical latency but worsens rare pauses. The decision depends on the service's requirements, failure costs and measurement confidence. “Faster” needs a named metric and workload.
Make each mistake produce a new test
Keep a short record: original contract, broken assumption, failing example and corrected explanation. Practice one changed requirement after each successful implementation. That reveals whether you understand the design or only remember its shape.
For research-oriented coding, try the Python data tasks. For allocating preparation across topics, use the four-week study plan. Discuss your target role in a free consultation if you need help choosing the depth of coding, systems and interview preparation to pursue.
Practice the Reasoning Behind the Answer
Bring your target roles and interview timeline to a free quant consult. Discuss your preparation priorities and how Quant Blueprint’s lessons, practice and mock interviews can help.
Book a Free Quant ConsultYour goals · your preparation · your next steps
Not ready yet? Grab the free prep pack.
Get our quant interview prep resources delivered to your inbox.