← Back to home

Optiver Career Kickstarter: Tech

Technical Interview Prep Guide

75-min Zoom · Code Review + Deployment + CS Fundamentals · Personalised for Gennaro

What to Expect in 75 Minutes

The Format (confirmed from Optiver)

  • ~10 min: Intro + warm-up (who you are, why Optiver, why tech in trading)
  • ~25 min: Code snippet review — read code, find bugs/smells, suggest improvements
  • ~15 min: Simple deployment plan for what you just reviewed
  • ~15 min: CS fundamentals discussion — architecture, concurrency, networking, memory
  • ~10 min: Behavioral + your questions

Optiver's Key Signal: HOW you think, not WHAT you know

  • Ask clarifying questions before diving in — show you understand constraints
  • Think out loud at all times — silence is penalized
  • Engage with hints — if interviewer nudges you, pick it up and run with it
  • Communicate trade-offs explicitly — "I chose X over Y because..."
  • It's collaborative, not adversarial — treat it like pair programming

Your Strongest Angles (use these!)

  • V2X intern: TTL caching, data adapters, real-time dashboards → real production systems
  • ETH Big Data + Probabilistic AI → you understand performance and uncertainty
  • TORCS KD-Tree project → you reason about time complexity and algorithmic choices
  • Algorithms Lab at ETH → competitive-level DSA, you're current on this
  • Head of Engineering @ ETH Entrepreneur Club → ownership, architecture decisions

Code Review — What Optiver Is Testing

They'll show you a short snippet (Python, Java, or C++ — pick your strongest). You need to do a structured review, not just spot typos.

🐛Correctness & Edge Cases

  • Off-by-one errors in loops
  • Null/None pointer dereferences
  • Integer overflow (critical in trading: prices, quantities)
  • Empty collection handling
  • Concurrency bugs: race conditions, non-atomic operations
  • Exception handling — are errors swallowed silently?

Performance & Complexity

  • Unnecessary O(n²) where O(n log n) or O(n) is possible
  • Repeated work inside loops that could be cached
  • Wrong data structure choice (e.g. list.contains() vs set.contains())
  • Memory allocations in hot paths (critical for low-latency)
  • String concatenation in loops (use StringBuilder/join)
  • Locking granularity — too coarse = contention, too fine = complex

🏗️Code Quality & Design

  • Single Responsibility Principle — is the function doing too much?
  • Magic numbers/strings — should be named constants
  • Variable names — are they descriptive?
  • Dead code or unreachable branches
  • Testability — is this easily unit-testable?
  • Missing input validation

💬How to Structure Your Answer

  • Step 1: "Let me read through this first..." (take 30-60s, read fully)
  • Step 2: "At a high level, this code is doing X..." (show you understood it)
  • Step 3: "I see a few issues, let me go through them by severity..."
  • Step 4: Prioritize — critical (correctness) → performance → style
  • Step 5: "Here's how I'd fix the most important one..." (write the fix)
  • Step 6: "Are there specific constraints I should consider?"
🔬 Practice Example (Python)
def get_prices(order_ids):
    prices = []
    for id in order_ids:
        if id in prices:   # BUG 1
            continue
        price = db.query(f"SELECT price FROM orders WHERE id={id}")  # BUG 2, 3
        prices.append(price)
    return prices

→ BUG 1: 'if id in prices' checks a list of prices, not ids — logic error

→ BUG 2: SQL injection via f-string — use parameterized queries

→ BUG 3: N queries to DB in a loop — should batch: WHERE id IN (...)

→ IMPROVEMENT: No error handling if DB query fails

→ IMPROVEMENT: No type hints, unclear what 'prices' returns

Practice — Review These Snippets

Read each snippet as if the interviewer just showed it to you. Write your review in the box, then reveal the answer to compare.

EXERCISE 1 MEDIUM
Review this order processing function. Find all bugs, performance issues, and design problems.
import threading

class OrderBook:
    def __init__(self):
        self.orders = {}
        self.total_volume = 0

    def add_order(self, order_id, price, quantity):
        self.orders[order_id] = {"price": price, "qty": quantity}
        self.total_volume += price * quantity

    def remove_order(self, order_id):
        order = self.orders[order_id]
        self.total_volume -= order["price"] * order["qty"]
        del self.orders[order_id]

    def get_best_bid(self):
        best = 0
        for oid in self.orders:
            if self.orders[oid]["price"] > best:
                best = self.orders[oid]["price"]
        return best

Model Answer

  • Thread safety: No locking on shared state — add_order/remove_order will race. With multiple threads (common in trading), self.orders and self.total_volume can get corrupted. Need a mutex or use lock-free structures.
  • KeyError: remove_order doesn't check if order_id exists — will throw KeyError on unknown ID. Add a check or use .pop() with default.
  • get_best_bid is O(n): Scanning all orders every call is too slow for a hot path. Use a sorted structure (heap or sorted map by price) for O(1) best bid.
  • No side distinction: Orders have no buy/sell side — get_best_bid mixes bids and asks. An order book needs separate bid/ask sides.
  • Float precision: price * quantity with floats will accumulate rounding errors. Use Decimal or integer cents.
  • total_volume inconsistency: If remove_order throws (KeyError), total_volume won't be decremented but the operation is aborted — state stays consistent by accident, but fragile.
EXERCISE 2 HARD
This C++ function processes market data ticks. Find the bugs and performance problems.
struct Tick {
    int instrument_id;
    double price;
    int volume;
    char exchange[4];
};

void process_ticks(std::vector<Tick> ticks) {
    std::map<int, double> vwap;
    std::map<int, int> total_vol;

    for (int i = 0; i <= ticks.size(); i++) {
        Tick t = ticks[i];
        std::string ex(t.exchange);
        if (ex == "XAMS" || ex == "XLON") {
            vwap[t.instrument_id] += t.price * t.volume;
            total_vol[t.instrument_id] += t.volume;
        }
    }
    for (auto& [id, sum] : vwap) {
        vwap[id] = sum / total_vol[id];
    }
}

Model Answer

  • Off-by-one (crash): i <= ticks.size() should be i < ticks.size() — accesses one past the end, undefined behaviour.
  • Pass by value: std::vector<Tick> ticks copies the entire vector on call. Must be const std::vector<Tick>&.
  • String allocation in hot loop: std::string ex(t.exchange) allocates on the heap each iteration. Use strncmp or std::string_view.
  • Tick copied per iteration: Tick t = ticks[i] copies the struct. Use const Tick& t.
  • Division by zero: If total_vol[id] is 0 (can happen if volume is 0 on all ticks for an instrument), division crashes.
  • Struct padding: exchange is char[4] after an int — likely no padding issue, but worth noting the layout. If exchange were char[3], padding would waste a byte.
  • Double precision: Accumulating price*volume in doubles will lose precision for large volumes. Consider fixed-point.
  • No return value: Computes VWAP but doesn't return or store it anywhere — dead computation.
EXERCISE 3 MEDIUM
This Java class caches instrument prices. Find threading issues and design problems.
public class PriceCache {
    private HashMap<String, Double> cache = new HashMap<>();
    private long lastUpdate = System.currentTimeMillis();

    public Double getPrice(String instrument) {
        if (System.currentTimeMillis() - lastUpdate > 5000) {
            refreshAll();
        }
        return cache.get(instrument);
    }

    public void updatePrice(String instrument, double price) {
        cache.put(instrument, price);
        lastUpdate = System.currentTimeMillis();
    }

    private void refreshAll() {
        cache.clear();
        // ... reload from source
        lastUpdate = System.currentTimeMillis();
    }
}

Model Answer

  • Not thread-safe: HashMap is not synchronized. Concurrent getPrice + updatePrice causes ConcurrentModificationException or corrupted state. Use ConcurrentHashMap or synchronize.
  • Race on lastUpdate: long reads/writes are NOT atomic on 32-bit JVMs. Use AtomicLong or volatile.
  • TOCTOU in getPrice: Between checking the timestamp and reading cache, another thread could call refreshAll() which calls cache.clear() — getPrice returns null for a valid instrument.
  • refreshAll() is destructive: cache.clear() wipes everything before reload. During the reload window, all gets return null. Use swap pattern: build new map, then atomically replace reference.
  • Every read can trigger refresh: If the source is slow, every reader thread that sees an expired cache will trigger refreshAll() simultaneously. Need a "refreshing" flag or scheduled refresh.
  • Returning Double (boxed): Autoboxing/unboxing overhead on every call. In a hot path, matters. Also, cache.get() returns null for missing keys — no distinction between "no data" and "price is unknown".
EXERCISE 4 EASY
This Python function finds matching trades. What's wrong?
def find_matching_trades(trades, target_value):
    results = []
    for i in range(len(trades)):
        for j in range(len(trades)):
            if trades[i]["value"] + trades[j]["value"] == target_value:
                results.append((trades[i], trades[j]))
    return results

Model Answer

  • Pairs with itself: j starts from 0, so a trade can be paired with itself when i == j. Should start j from i+1.
  • Duplicate pairs: Will find (A,B) and (B,A) — returns duplicates. Use j = i+1 to fix both issues.
  • O(n²) complexity: Classic two-sum problem. Use a hash set: for each trade, check if (target - trade.value) is in the set. O(n) time.
  • Float comparison: Using == on trade values (likely floats) is dangerous. Use abs(a + b - target) < epsilon.
  • No input validation: If trades is empty or items don't have "value" key, this crashes.

Simple Deployment Plan — Framework

After code review, they'll ask: "How would you deploy this to production?" This is NOT a complex SRE question — it's testing whether you think beyond just writing code.

💡 Key phrase to use: "In a trading environment, I'd prioritize zero-downtime and fast rollback over complex features in the deploy pipeline."

1. Pre-deployment

  • Code review + tests passing (unit, integration)
  • Feature flags / toggles to control rollout
  • Config management — no hardcoded secrets, use env vars or secrets manager
  • Build artifact versioned and stored (Docker image, JAR, wheel)
  • Staging environment validation — does it work in a prod-like setup?

🔄2. Deployment Strategy

  • Blue/green deploy: spin up new version, switch traffic, keep old as fallback
  • Canary release: route 5-10% of traffic to new version, watch metrics
  • Rolling update: replace instances one-by-one (good for stateless services)
  • Key question to ask: "Is this service stateful or stateless?"
  • For trading systems: zero-downtime is non-negotiable → prefer blue/green

📊3. Observability

  • Logging: structured logs with request IDs for traceability
  • Metrics: latency (p50/p99), error rate, throughput — not just uptime
  • Alerts: define thresholds BEFORE deploying, not after
  • Health checks: readiness (is it ready for traffic?) vs liveness (is it alive?)
  • At Optiver scale: microsecond-level latency monitoring matters

↩️4. Rollback Plan

  • ALWAYS have a rollback plan — mention this proactively
  • Blue/green: flip traffic back in seconds
  • Database migrations: always write backward-compatible migrations first
  • Define rollback triggers: error rate > X%, latency > Y ms
  • Post-mortem process: what went wrong, how to prevent it

Practice — Deployment Scenarios

The interviewer describes a system and asks "How would you deploy this?" Think through your answer, then reveal the model response.

SCENARIO 1 MEDIUM
Scenario: You've written a new pricing service that calculates fair value for options. It reads market data from a message queue, computes Greeks, and writes results to a shared database that traders' dashboards read from. How would you deploy this to production?

Model Answer

Pre-deployment:

  • Unit tests on pricing formulas + integration tests with sample market data
  • Validate outputs against existing pricing engine in staging (shadow mode)
  • Feature flag to switch between old and new engine per-instrument

Deployment strategy:

  • Blue/green — critical for trading: can't have stale prices reaching dashboards
  • Run new engine in shadow mode first: consume same market data, compute prices, compare to old engine output but don't write to DB
  • Once shadow results match within tolerance, switch writes to new engine

Observability:

  • Latency of computation (p50/p99) — traders need fresh prices
  • Compare new prices to old prices — alert if delta exceeds threshold
  • Queue consumer lag — if falling behind, market data is stale
  • DB write latency and error rate

Rollback:

  • Feature flag: flip back to old engine instantly
  • Blue/green: keep old instance running for 24h after switch
  • Trigger: if price deviation > X bps or latency > Y ms, auto-rollback
SCENARIO 2 HARD
Scenario: You need to deploy a schema change to the production database that adds a new column "risk_score" (NOT NULL with default) to the "positions" table, which has 50 million rows and is queried by 12 different services in real-time. How do you handle this?

Model Answer

Key insight: Never deploy schema changes and code changes together.

Phase 1 — Expand:

  • Add column as NULLABLE (not NOT NULL) with DEFAULT — this avoids locking the table
  • On Postgres, adding a column with a default is instant (metadata-only since PG11)
  • All 12 services still work — they ignore the new column

Phase 2 — Migrate:

  • Backfill risk_score values in batches (not one UPDATE for 50M rows — that locks the table)
  • Deploy services one-by-one to start writing risk_score on new inserts

Phase 3 — Contract:

  • Once all rows have risk_score and all services write it, add NOT NULL constraint
  • This is the "expand-and-contract" migration pattern

Rollback:

  • At any phase, rolling back is safe — old services don't read the column
  • Mention: "I'd never alter a column type in-place on a table this hot — always add new, migrate, drop old"
SCENARIO 3 EASY
Scenario: You've built a simple REST API that returns historical trade data. It's a read-only, stateless service. How do you deploy it?

Model Answer

  • Strategy: Rolling update — stateless service, so instances can be replaced one-by-one with zero downtime. Simplest approach that works.
  • Pre-deploy: Run API tests against staging, load test to confirm latency SLAs, Docker image tagged and pushed to registry.
  • Health checks: Readiness probe (can serve requests?) and liveness probe (process alive?). Load balancer stops sending traffic to unready instances.
  • Observability: Request latency, error rate (4xx/5xx), and throughput. Set alerts before deploy.
  • Rollback: Kubernetes-style: if new pods fail readiness check, rollout stops automatically. Manual rollback = redeploy previous image tag.
  • Bonus: Since it's read-only, you could also use canary (route 10% of traffic first) at almost no risk.

CS Fundamentals — Optiver's Favourite Topics

🧠 Memory Management HIGH
  • Stack vs Heap: stack is fast (push/pop), heap is dynamic but slower (malloc/free)
  • Cache hierarchy: L1 (~4 cycles) → L2 (~12) → L3 (~40) → RAM (~200) → Disk
  • Cache locality: accessing contiguous memory is MUCH faster (array vs linked list)
  • Struct padding: compiler adds padding for alignment — know how to minimize it
  • Example: struct { char a; int b; char c; } = 12 bytes due to padding
  • Memory leak vs dangling pointer vs buffer overflow
  • GC languages (Java/Python) vs manual (C++) trade-offs for latency
SAMPLE Q&A
Q: Why is iterating an array faster than iterating a linked list?
A: Cache locality — array elements are contiguous in memory, so the CPU prefetches them efficiently. Linked list nodes are scattered, causing cache misses each hop.
🔀 Concurrency HIGH
  • Thread vs process: threads share memory (fast comms, race risks), processes don't
  • Race condition: two threads read-modify-write shared state without sync
  • Mutex: mutual exclusion lock — only one thread at a time
  • Deadlock: Thread A holds lock X waiting for Y, Thread B holds Y waiting for X
  • Atomic operations: hardware-guaranteed read-modify-write (lock-free)
  • Memory ordering: volatile (Java), std::atomic, memory barriers
  • Producer-consumer: queue + condition variable pattern
  • At Optiver: lock-free data structures used extensively for low latency
SAMPLE Q&A
Q: What's the difference between a mutex and a spinlock?
A: A mutex puts the thread to sleep when blocked (OS context switch, expensive). A spinlock busy-waits in a loop (wastes CPU but avoids context switch overhead — better when lock held very briefly, as in HFT).
💻 Computer Architecture HIGH
  • CPU pipeline: fetch → decode → execute → writeback — branch misprediction flushes it
  • Branch prediction: CPUs predict which branch is taken; misprediction = ~15 cycle penalty
  • SIMD: process multiple data with one instruction (vectorization)
  • False sharing: two threads modify different fields in the same cache line → cache invalidations
  • NUMA: Non-Uniform Memory Access — memory closer to some CPUs than others
  • Context switch: OS saves/restores thread state — expensive (~microseconds)
SAMPLE Q&A
Q: Why can reordering if/else branches improve performance?
A: Branch predictors are biased toward "not taken" for forward branches. If the likely path is in the else, swapping them helps the predictor, reducing pipeline flushes.
🌐 Networking MEDIUM
  • TCP vs UDP: TCP = reliable ordered, UDP = fast unreliable. Trading often uses UDP multicast for market data
  • Latency sources: propagation (~5µs/km), serialization, queuing, OS kernel
  • Kernel bypass (DPDK, RDMA): skip OS stack entirely for sub-microsecond latency
  • TCP Nagle's algorithm: buffers small packets — disable with TCP_NODELAY in trading
  • Load balancing: round-robin, least connections, consistent hashing
  • Connection pooling: avoid reconnect overhead in hot paths
SAMPLE Q&A
Q: Why might a trading system use UDP instead of TCP?
A: UDP is connectionless, no acknowledgement overhead, lower latency. Market data is broadcast — if you miss a packet, you retransmit by requesting a snapshot, not per-packet ACK.
📐 Data Structures & Algorithms MEDIUM
  • Hash map: O(1) avg get/put, O(n) worst case with collisions — know open addressing vs chaining
  • Binary search tree: O(log n) but unbalanced degrades to O(n) → use Red-Black tree
  • Priority queue (heap): O(log n) insert, O(1) peek — used for order books
  • Order book: usually implemented as sorted map (price → queue of orders)
  • Bloom filter: probabilistic set membership, no false negatives, small false positive rate
  • Know when to use each and their space complexity too
SAMPLE Q&A
Q: How would you implement an order book efficiently?
A: A sorted map (e.g. TreeMap/std::map) keyed by price, each mapping to a deque of orders at that price. This gives O(log n) insert/cancel, O(1) best bid/ask peek.
🖥️ Operating Systems MEDIUM
  • Process scheduling: preemptive (OS interrupts) vs cooperative
  • Virtual memory + paging: address translation via page table, TLB caches it
  • System calls: crossing user/kernel boundary is expensive (~microseconds)
  • Signals: async notifications to processes (SIGTERM, SIGKILL)
  • File descriptors: abstract handle for I/O — sockets are file descriptors
  • mmap: map file/device into memory address space — faster than read/write for large files
SAMPLE Q&A
Q: Why avoid system calls in a hot loop?
A: System calls cause a user→kernel mode transition, saving registers and potentially causing context switches. In µs-sensitive systems, this overhead is too high.

Practice — Rapid-Fire Flashcards

The interviewer asks short CS questions. Think of your answer, then click to reveal. Practice explaining out loud.

What is false sharing and why does it matter in multi-threaded systems? click to reveal
False sharing occurs when two threads modify different variables that happen to live on the same cache line (typically 64 bytes). The CPU's cache coherency protocol forces the line to bounce between cores on every write, even though the threads aren't logically sharing data. Fix: pad structs so each thread's hot data is on its own cache line (alignas(64) in C++).
Explain the difference between virtual memory and physical memory. click to reveal
Each process gets its own virtual address space — a contiguous illusion. The OS + MMU translate virtual addresses to physical RAM addresses via page tables (cached in the TLB). This gives isolation (process A can't read process B's memory), lets you allocate more memory than physically available (paging to disk), and simplifies memory management. TLB misses on hot paths matter for latency — use huge pages (2MB) to reduce page table entries.
Why would you choose UDP over TCP for market data distribution? click to reveal
UDP multicast lets one sender broadcast to many receivers simultaneously without maintaining per-receiver state. TCP requires a separate connection per receiver (doesn't scale) and adds overhead: 3-way handshake, ACKs, retransmission, congestion control. For market data, if you miss a packet, you request a snapshot (full state reset) rather than waiting for per-packet retransmission — this is faster recovery. Also: disable Nagle's algorithm (TCP_NODELAY) if you do use TCP.
What is a deadlock? What are the four conditions and how do you prevent it? click to reveal
A deadlock is when two or more threads are forever blocked waiting for each other. Four necessary conditions (Coffman conditions): 1) Mutual exclusion — resource held exclusively. 2) Hold and wait — thread holds one lock while waiting for another. 3) No preemption — locks can't be forcibly taken. 4) Circular wait — A waits for B, B waits for A. Break any one to prevent deadlock. Most common fix: lock ordering — always acquire locks in the same global order. Alternative: use try-lock with timeout.
What's the difference between stack and heap allocation? When does it matter? click to reveal
Stack: Automatic, LIFO. Allocation is just a pointer bump (~1 cycle). Deallocation is automatic when function returns. Fixed size. Heap: Dynamic (malloc/new). Allocator must find a free block, may need OS syscall (sbrk/mmap). Deallocation is manual (or GC). It matters in hot paths: heap allocation in a tight loop creates GC pressure (Java/Python) or fragmentation (C++). In HFT, preallocate everything and use object pools. Stack variables also have better cache locality — they're nearby in memory.
How does a CPU branch predictor work, and why does it matter for performance? click to reveal
Modern CPUs are pipelined — they start executing the next instruction before the current one finishes. At a branch (if/else), the CPU must predict which path to take before it knows the condition result. If it guesses right, no penalty. If wrong (misprediction), the pipeline is flushed — ~15 cycle penalty. Branch predictors use history tables to learn patterns. For performance: make the common case predictable, sort data before branching on it (see: sorted vs unsorted array benchmark), or use branchless code (conditional moves, arithmetic).
What is kernel bypass and why is it used in trading? click to reveal
Normal network I/O goes through the OS kernel: syscall → kernel network stack → interrupt → copy to user space. Each step adds microseconds. Kernel bypass (DPDK, Solarflare OpenOnload, RDMA) maps the NIC directly into user-space memory, so packets go directly from NIC to application buffer — no syscalls, no copies, no interrupts. This achieves sub-microsecond latency. Trade-off: you lose OS protections, need dedicated NICs, and must handle protocol parsing yourself. Standard in HFT.
What's the difference between a process and a thread? When would you use each? click to reveal
A process has its own virtual address space, file descriptors, and OS resources — processes are isolated. A thread shares the parent process's memory and resources — cheaper to create, faster to context-switch, but requires explicit synchronization (mutexes, atomics). Use processes for isolation (crash in one doesn't kill others, security boundaries). Use threads when you need shared memory communication and low overhead. In trading: often one process per strategy/component for fault isolation, with threads inside for parallelism.
How does a hash map work internally? What happens with collisions? click to reveal
A hash map stores key-value pairs in an array of buckets. The key is hashed to an index: bucket = hash(key) % capacity. Collision handling: Chaining — each bucket is a linked list of entries (simple but cache-unfriendly). Open addressing — on collision, probe the next slot (linear/quadratic/double hashing). Open addressing has better cache locality. Average O(1) get/put, but worst case O(n) if all keys hash to the same bucket. Load factor matters: rehash when >0.75 full. Java's HashMap switches from chaining to a red-black tree at 8 collisions per bucket.
Explain the L1/L2/L3 cache hierarchy. Why does cache locality matter so much? click to reveal
CPUs have multiple cache levels: L1 (~32KB, ~4 cycles) → L2 (~256KB, ~12 cycles) → L3 (~8-30MB, ~40 cycles, shared across cores) → RAM (~200 cycles). Each miss cascades to the next level. Data is loaded in cache lines (64 bytes) — so accessing array[i] also loads array[i+1] through array[i+15] (for ints). This is why arrays are faster than linked lists: sequential access gets prefetched. In trading: keeping hot data in L1/L2 is critical. Profile with perf stat to count cache misses.

Behavioral Prep — STAR Stories from YOUR Experience

Optiver assesses: intellectual curiosity, ownership, collaboration, and resilience. Here are ready-made stories from your background.

Q: Tell me about a challenging technical problem you solved.
SITUATION

During my thesis internship at MinervaS building the V2X co-pilot module

TASK

I had to integrate heterogeneous traffic and weather data streams with different update frequencies and formats into a real-time advisory system

ACTION

I designed a spatio-temporal filtering pipeline with TTL-based caching to normalize staleness across streams. I introduced a fuzzy logic layer to handle uncertainty in sensor data rather than hard thresholds

RESULT

The system could provide explainable, real-time speed advice for heavy vehicles with latency under the operational requirement. It became the core of my Bachelor thesis (110/110 cum laude)

Q: Describe a time you had to learn something quickly.
SITUATION

Starting at ETH Zürich, my first semester included Big Data and Probabilistic AI simultaneously

TASK

Both courses demanded graduate-level understanding of distributed systems and Bayesian inference — well beyond my BSc curriculum

ACTION

I built a structured study schedule, connected concepts between courses (uncertainty in ML ↔ uncertainty in distributed systems), and focused on implementation to solidify theory

RESULT

Scored 5.5/6 in both courses — top tier at ETH — while also starting as Head of Engineering at the Entrepreneur Club

Q: Tell me about a time you had to make a design decision with trade-offs.
SITUATION

In the TORCS autonomous driving project, I had to choose a nearest-neighbor search approach for behavioral cloning

TASK

Simple linear scan was too slow for real-time decisions during simulated driving

ACTION

I implemented a KD-Tree structure, which reduced nearest-neighbor search from O(n) to O(log n). I also added dynamic-k selection and out-of-distribution detection so the system would fall back to rule-based safety mechanisms when the KD-Tree returned unreliable neighbors

RESULT

The driver could navigate the track in real time with robust safety fallbacks — a deliberate design choice to prioritize reliability over raw speed

Q: Why Optiver, and why tech in trading specifically?
Be specific — mention their build-and-own culture, the direct feedback loop from code to market impact, and low-latency systems. Reference ETH courses (Big Data, Probabilistic AI) as showing your interest in performance-critical systems.

Quick Tips

→ Keep each STAR story under 2 minutes when spoken aloud

→ End with what you LEARNED, not just what happened

→ Optiver values intellectual honesty — "it didn't fully work because X" is fine if you learned from it

→ Prepare 2-3 questions about engineering culture, team structure, and what they're building

Questions to Ask — Show Intellectual Curiosity

Asking good questions is evaluated. These show you've researched Optiver and are genuinely curious — not just going through the motions.

"What does the day-to-day feedback loop look like between engineers and traders? How quickly does a software change show up in measurable trading outcomes?"
Why this works: Shows you care about impact and Optiver's unique build-and-own culture
"What's the biggest systems challenge the team is working on right now — is it latency, reliability, or scale?"
Why this works: Shows technical curiosity and that you've internalized what Optiver's engineering actually does
"How does the team handle the trade-off between moving fast on new features vs. the reliability requirements of mission-critical trading infrastructure?"
Why this works: Directly relevant to Optiver's culture of "build-and-own" and post-mortems
"For someone joining through Career Kickstarter, what would a great first week look like technically?"
Why this works: Shows forward-thinking and genuine interest in the program, not just the interview
"How much exposure do engineers get to the actual trading logic and strategies vs. pure infrastructure work?"
Why this works: Optiver engineers often work close to the trading side — this shows you're excited by that