Skip to content
Work Practice Products Coding interviews Machine learning coding Quant developer HFT course Blog Method Why me About Enquire Book a call

Quant developer HFT course · Module 2: Safe C++

Drill: lifetime and ownership

Interview drill · 15 min

Each prompt is a piece of code an interviewer might show you, followed by "what's wrong?". Find the bug, name the rule it breaks, and say how you would fix it and how you would catch it in future.

Prompt 1: The helpful accessor

The interviewer says

const Level& top(const Instrument& i) { auto book = snapshot(i); return book.levels[0]; } What is wrong with this?

Before you answer

Question · choose every correct option

Which statements are correct? Choose all that apply.

Answers that lose the point

  • It returns a reference, which is faster. Fast and wrong. The interviewer asked what is wrong.
  • It might work. It might, in a test. Undefined behaviour that passes tests is the problem, not the defence.

A strong answer

It returns a reference into a local. The book is destroyed at the closing brace, so the caller gets a reference to memory that is no longer a Level. It may pass tests because the stack has not been reused yet, then fail in production.

A level is a price and a quantity, so I would return it by value. If the caller needs to refer to the live level, the function should take the book by reference from an owner that outlives the call, not build its own copy. I would expect AddressSanitizer's stack-use-after-return checks, or GCC's dangling-reference warning, to catch this in CI.

Prompt 2: The acknowledgement callback

The interviewer says

A function creates an order as a local, sends it, and registers [&](const Ack& a) { order.status = a.status; } to run when the exchange acknowledges. Problems?

Before you answer

Question · choose every correct option

Which are real problems? Choose all that apply.

Answers that lose the point

  • Change [&] to [=]. Copies the order, so the update goes to the copy and is lost, and in a member function it still captures this.
  • Make order static. Removes the dangling and creates shared state across all orders.

A strong answer

The lambda is deferred, so it outlives the function and its reference to the local order dangles. I would have the router own the order in a table, capture the order's id by value, and look it up when the acknowledgement arrives, handling the case where it has been removed.

If the callback runs on the gateway's thread, any shared state it touches needs to be atomic or handed back to the owning thread through a queue. And because it uses members, it captures this, so the router must outlive the gateway's callbacks; I would make that explicit in the shutdown order or use a weak_ptr.

Prompt 3: Shared pointers everywhere

The interviewer says

Every order is a std::shared_ptr<Order>, copied to the risk checker, the gateway and the logger. Is that a problem?

Before you answer

Question · choose every correct option

Which statements belong in the answer? Choose all that apply.

Answers that lose the point

  • shared_ptr is slow, use raw pointers. Raw pointers lose the ownership information without fixing the design.
  • It's fine, shared_ptr is thread-safe. The count is thread-safe; the object is not, and the counting is what costs.

A strong answer

Ownership here is not really shared: the order manager owns orders and the other components use them briefly. Copying shared_ptrs costs an atomic increment and decrement per hand-off, on counts several threads touch, plus an allocation per order, and whoever drops the last copy runs the destructor at an unpredictable point.

I would keep orders in a pool owned by the manager, pass references for synchronous use, and pass handles with a generation check for anything that refers to the order later. The logger would receive a copy of the few fields it needs in a fixed-size record, not the order.

Prompt 4: The convenient view

The interviewer says

std::string_view venue() const { return std::string(venue_code_) + "-" + region_; } Comment.

Before you answer

Question · choose every correct option

What is true about this function? Choose all that apply.

Answers that lose the point

  • string_view is faster, so this is good. Fast access to freed memory is not an optimisation.

A strong answer

It returns a view of a temporary. The concatenated string is destroyed when the return statement finishes, so the caller's view points at freed heap memory. Short strings in the small buffer might even appear to work, which hides it.

If the function builds a new string, it has to return std::string. Better still for a hot path, store the combined venue identifier once at construction and return a view of that member, which lives as long as the object.

Prompt 5: Pointers into a vector

The interviewer says

A book stores orders in a std::vector<Order> and keeps an unordered_map<OrderId, Order*>. Cancels sometimes corrupt memory after a busy period. Why?

Before you answer

Question · choose every correct option

Which are correct? Choose all that apply.

Answers that lose the point

  • Use a std::list instead. Stable addresses, at the cost of an allocation and scattered nodes per order.
  • Call reserve. Helps until the busy period exceeds the reserve, and does nothing for erases.

A strong answer

The pointers are into the vector's buffer. When a burst of new orders exceeds the capacity, push_back allocates a new buffer and moves the orders, so every pointer in the map refers to freed memory; erasing from the middle moves later orders too.

I would store orders in a preallocated pool that never moves, map exchange ids to handles made of a slot index and a generation, and check the generation on every lookup so a stale reference is detected rather than followed.

After the drill

Every bug here is the same bug: something that refers to an object outliving the object, or the object moving. The questions to ask in any code review are who owns this, how long does it live, and can it move. Module 3 turns to the containers themselves and what they cost.