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 4: Concurrency

Drill: concurrency

Interview drill · 16 min

Concurrency questions reward precision. Name the shared state, name the synchronisation, name the ordering, and say how you would prove it with a tool.

Prompt 1: The short count

The interviewer says

Two threads do ++count on a shared int and the total comes out low. Explain and fix.

Before you answer

Question · choose every correct option

Which statements are correct? Choose all that apply.

Answers that lose the point

  • Use volatile. Still a race, still lost increments.
  • Put a mutex around it. Correct, but heavier than needed for a counter.

A strong answer

++count is three steps, load, add, store, and two threads can both load the same value, so one increment is lost. In C++ it is also a data race, which is undefined behaviour, so the damage is not limited to the count.

For a pure counter I would use std::atomic<uint64_t> with fetch_add and relaxed ordering, since nothing else is published through it. On a hot path I would go further and give each thread its own counter on its own cache line, summed when read, so the threads never contend.

Prompt 2: The flag that never flips

The interviewer says

A worker loops while(!stop) and another thread sets stop = true. At -O2 the worker never exits. Why?

Before you answer

Question · choose every correct option

What is going on? Choose all that apply.

Answers that lose the point

  • The CPU cache is stale. Coherence protocols keep caches consistent; the compiler is the culprit.

A strong answer

The flag is a plain bool shared between threads, which is a data race and undefined behaviour. With no synchronisation, the optimiser may assume nothing else changes stop inside the loop and read it once, so the loop never ends.

Making it std::atomic<bool> forces a real load on every iteration. If the flag also signals that some data is ready, the store should be release and the load acquire; for a pure stop request, relaxed would do, though I would default to the stronger orders unless profiling says otherwise.

Prompt 3: Busy days are slow

The interviewer says

Strategy and gateway threads share a mutex-protected book. Latency is fine when quiet and bad when busy. What do you investigate?

Before you answer

Question · choose every correct option

What belongs in the answer? Choose all that apply.

Answers that lose the point

  • Use a faster mutex. The cost is waiting, not the lock instruction.
  • Add more threads. More threads, more contention.

A strong answer

That pattern points at contention. An uncontended mutex costs a couple of atomics, but a contended one puts the waiting thread to sleep in the kernel, and waking it takes microseconds with a long tail. Busier days mean more overlap.

I would measure how long the lock is held and look for slow work inside it: logging, allocation, I/O, computations that could run on a copy. Shortening the critical section helps immediately. The structural fix is to give the book a single owning thread and have the other thread send it requests and receive snapshots through lock-free queues, so there is no shared lock.

Prompt 4: Publishing a quote

The interviewer says

The feed thread writes a quote into a buffer and sets a flag; the strategy thread spins on the flag and reads the quote. Which memory orders?

Before you answer

Question · choose every correct option

Which are correct? Choose all that apply.

Answers that lose the point

  • seq_cst everywhere. Correct and defensible, but you should be able to say why release-acquire suffices.
  • Relaxed, it's x86. The compiler is still free to reorder; the code becomes non-portable.

A strong answer

Release on the store of the flag and acquire on the load. The release guarantees that the write of the quote is visible no later than the flag, and the acquire guarantees the strategy reads the quote only after seeing the flag. Together they synchronise, so seeing the flag means seeing the quote.

On x86 both compile to ordinary moves and the orders only restrict the compiler; on ARM they emit the needed barriers. seq_cst would also be correct but is stronger than this pattern requires, and on x86 a seq_cst store costs a fence.

Prompt 5: The fastest handoff

The interviewer says

Design the queue from the feed thread to the strategy thread. One producer, one consumer, minimum latency.

Before you answer

Question · choose every correct option

Which design choices are right? Choose all that apply.

Answers that lose the point

  • A mutex and a condition variable. Correct, but every handoff can go through the kernel.
  • A lock-free MPMC queue from a library. Works, but pays for competing producers that do not exist here.

A strong answer

A single-producer single-consumer ring over a preallocated array with a power-of-two capacity. The producer writes a slot and stores the tail with release; the consumer loads the tail with acquire, reads the slot, and stores the head with release, which the producer reads with acquire before reusing the slot. No locks, no compare-and-swap, no allocation.

For speed I would put head and tail on separate cache lines, cache each side's view of the other's index, construct large messages in place, and run the consumer on an isolated core polling the ring rather than sleeping. When the ring is full, a feed producer drops and flags a gap rather than blocking.

After the drill

Every concurrency answer has the same skeleton: who writes, who reads, what orders the accesses, and what happens under contention. Module 5 goes underneath all of this to the caches and cores that make the costs what they are.