Coding & Engineering Craft: TS, Rust, Python on the Hot Path
Medium-hard problems with a systems flavor - and using AI tools the way Cursor expects
Pick and sharpen your language
After this you can choose a primary language and know where each fits at Cursor.
The coding screens for Model Routing & Inference are language-agnostic in theory and very opinionated in practice. You pick the language; the interviewer watches whether you actually live in it.
Cursor runs a small, polyglot stack and the inference path touches all three of its working languages. The JD names TypeScript, Rust or Python, but those are not interchangeable here. Each one owns a part of the request path and a strong candidate can say which and why without hedging.
Interactive diagram. Tab through its regions; each focused region shows its detail in the panel below.
Same role, three languages - each lives on a different part of the request path.
- Language
- TypeScript
- Where it lives on this team
- Inference gateway, app/control-plane logic, most service glue
- What the screen probes
- Type modeling under strict mode, async correctness, discriminated unions for provider responses
- Language
- Rust
- Where it lives on this team
- Latency-critical proxy paths where every microsecond and allocation counts
- What the screen probes
- Ownership,
Result/Option, async with tokio, avoiding copies on the hot path
- Language
- Python
- Where it lives on this team
- Eval harnesses, offline analysis, scripting around routing experiments
- What the screen probes
- Clean typed code, async I/O and knowing when the GIL makes it the wrong tool
| Language | Where it lives on this team | What the screen probes |
|---|---|---|
| TypeScript | Inference gateway, app/control-plane logic, most service glue | Type modeling under strict mode, async correctness, discriminated unions for provider responses |
| Rust | Latency-critical proxy paths where every microsecond and allocation counts | Ownership, Result/Option, async with tokio, avoiding copies on the hot path |
| Python | Eval harnesses, offline analysis, scripting around routing experiments | Clean typed code, async I/O and knowing when the GIL makes it the wrong tool |
Same role, three languages, three different jobs on the request path.
Choose the one you can think aloud in. The screens are timed and conversational, so the cost of a second-favorite language shows up as hesitation: pausing to recall syntax instead of reasoning about the failure mode. Pick where your reflexes are fastest, then be genuinely conversant in the other two.
TypeScript at depththe default for gateway work
If you pick TypeScript, the bar is not “I write React.” It is modeling a provider abstraction in the type system so that an impossible state cannot compile. The single most reusable pattern for an inference gateway is a discriminated union over provider outcomes plus an exhaustive switch that the compiler forces you to keep complete.
type ProviderResult<T> =
| { kind: "ok"; value: T; tokensIn: number; tokensOut: number }
| { kind: "rateLimited"; retryAfterMs: number }
| { kind: "timeout" }
| { kind: "upstreamError"; status: number; retryable: boolean };
function classify<T>(r: ProviderResult<T>): "retry" | "failover" | "return" {
switch (r.kind) {
case "ok": return "return";
case "rateLimited": return "failover";
case "timeout": return "retry";
case "upstreamError":return r.retryable ? "retry" : "failover";
// no default: adding a new variant becomes a compile error here.
}
}Omitting default on an exhaustive switch turns “we added a new provider outcome and forgot to handle it” from a 2am incident into a red squiggle. Narrating that tradeoff out loud is worth more than the code itself - it shows you reach for the type system to prevent classes of bugs, not just to annotate.
Rust and Python in their lanesknow the edges
Reach for it when GC pauses or per-request allocations would blow a p99 budget.
Be ready to talk ownership, borrowing and ? on Result - and to admit when its complexity isn't worth it.
Great for routing experiments, offline analysis and eval scripts.
The GIL makes it a poor fit for a CPU-bound, high-concurrency serving path - say so unprompted.
When you state your language, anchor it to the path: “I'll use TypeScript - it's where the gateway logic lives and I want the provider union modeled so a new model is a config change, not a code change.” That single sentence shows you know the codebase shape, not just the syntax.
Takeaway. Pick the language you can reason aloud in; at Cursor TS owns the gateway, Rust the latency-critical paths and Python the eval tooling - and you should be able to say which and why.
Self-check
QIn TypeScript, why deliberately omit a default branch when switching over a discriminated union of provider outcomes?
Systems-flavored coding problems
After this you can solve the kinds of problems this team actually asks.
The screen is “medium-hard with a systems flavor,” which is a specific genre. The problems sit close to what the inference platform does every second: consuming streams, throttling traffic and surviving a flaky upstream.
Generic algorithm puzzles still appear, but the distinctive ones are recognizably from this domain. A candidate who runs the product and the request path sees the editor connection immediately; someone who only drilled abstract puzzles tends to miss the partial-chunk and failure cases that are the whole point.
Interactive diagram. Tab through its regions; each focused region shows its detail in the panel below.
Correctness and failure handling first, then complexity, then tradeoffs - in that order.
- Likely prompt
- Consume a token/SSE stream, transform it, emit downstream
- Core skill
- Streaming, partial-chunk buffering, backpressure
- Why this team asks it
- Every chat and Tab response is a streamed token feed through the gateway
- Likely prompt
- Implement a token-bucket rate limiter
- Core skill
- Time math, capacity refill, concurrency safety
- Why this team asks it
- Admission control protects providers from traffic spikes
- Likely prompt
- Retry-with-backoff-and-jitter wrapper
- Core skill
- Exponential backoff, jitter, retry budgets
- Why this team asks it
- Failover and transient-error handling across providers
- Likely prompt
- Context windowing over a buffer / token budget
- Core skill
- Sliding window, ranking, fitting a budget
- Why this team asks it
- Building prompts that fit a model's context limit
| Likely prompt | Core skill | Why this team asks it |
|---|---|---|
| Consume a token/SSE stream, transform it, emit downstream | Streaming, partial-chunk buffering, backpressure | Every chat and Tab response is a streamed token feed through the gateway |
| Implement a token-bucket rate limiter | Time math, capacity refill, concurrency safety | Admission control protects providers from traffic spikes |
| Retry-with-backoff-and-jitter wrapper | Exponential backoff, jitter, retry budgets | Failover and transient-error handling across providers |
| Context windowing over a buffer / token budget | Sliding window, ranking, fitting a budget | Building prompts that fit a model's context limit |
Different prompts, all adjacent to the real inference path.
Worked example: a stream transformerpartial chunks are the trap
The classic version hands you a byte stream of newline-delimited JSON events and asks you to parse and re-emit them. The failure everyone forgets is that a chunk can split a line in half. Buffer the remainder and only emit on a complete delimiter.
async function* parseLines(
chunks: AsyncIterable<Uint8Array>,
): AsyncGenerator<string> {
const decoder = new TextDecoder();
let buf = "";
for await (const chunk of chunks) {
buf += decoder.decode(chunk, { stream: true });
let nl: number;
while ((nl = buf.indexOf("\n")) !== -1) {
const line = buf.slice(0, nl);
buf = buf.slice(nl + 1);
if (line.trim()) yield line;
}
}
// flush a trailing line with no terminating newline
if (buf.trim()) yield buf;
}- 1Restate as a contract. Write the typed signature and the constraint sentence (chunk sizes are arbitrary; lines can split) before any logic.
- 2Brute-force first. Get a correct version that handles whole lines, then introduce the partial-chunk buffer.
- 3Name the failure cases out loud. Split delimiter, empty chunk, no trailing newline, stream that errors mid-flight.
- 4Then complexity and tradeoffs. O(n) over bytes, constant extra memory beyond one pending line; mention what changes if a single line can be huge.
Do not assume one chunk equals one logical message. That single assumption is the most common silent failure in streaming questions and on the real path it corrupts a user's Tab completion mid-token. Call out the partial-chunk case in the first minute, even before you code it.
For any of these, lead with correctness and failure handling, then complexity, then tradeoffs - in that order. Saying “let me get a correct version that buffers partial chunks, then we'll talk throughput” signals the production-owner instinct this team hires for.
Takeaway. Expect stream-parsing, rate-limiting and retry problems drawn from the real inference path; the partial-chunk and failure cases are the grade, not the happy path.
Self-check
Concurrency, streaming and resilience patterns
After this you can implement the building blocks of a resilient request path.
These five primitives are the reusable parts of an inference gateway. Interviewers ask for one in code and then probe whether you understand how it composes with the others under load.
You will not build the whole platform in a screen. You will be asked to implement one piece cleanly and reason about its interactions: what happens when a retry fires inside a hedge or when a timeout cancels a request that already mutated state.
- Primitive
- Timeout + cancellation
- What it does
- Bound every upstream call; abort downstream work when the client disconnects
- The trap interviewers probe
- Leaking a hung connection or not propagating the cancel - work keeps burning GPU after nobody's waiting
- Primitive
- Backoff + jitter
- What it does
- Space out retries so failures don't synchronize into a storm
- The trap interviewers probe
- Forgetting jitter, so all clients retry on the same tick and re-DDoS the recovering provider
- Primitive
- Hedged request
- What it does
- Send a backup after a delay; take the first success, cancel the loser
- The trap interviewers probe
- Unbounded duplicate cost - hedging too eagerly doubles real provider spend
- Primitive
- Bounded concurrency
- What it does
- Cap in-flight work with a semaphore/queue; apply backpressure
- The trap interviewers probe
- Unbounded fan-out that melts the fleet instead of shedding load
- Primitive
- Idempotency key
- What it does
- Make a retried/hedged call safe to execute more than once
- The trap interviewers probe
- Double-charging or double-applying a side effect when two attempts both land
| Primitive | What it does | The trap interviewers probe |
|---|---|---|
| Timeout + cancellation | Bound every upstream call; abort downstream work when the client disconnects | Leaking a hung connection or not propagating the cancel - work keeps burning GPU after nobody's waiting |
| Backoff + jitter | Space out retries so failures don't synchronize into a storm | Forgetting jitter, so all clients retry on the same tick and re-DDoS the recovering provider |
| Hedged request | Send a backup after a delay; take the first success, cancel the loser | Unbounded duplicate cost - hedging too eagerly doubles real provider spend |
| Bounded concurrency | Cap in-flight work with a semaphore/queue; apply backpressure | Unbounded fan-out that melts the fleet instead of shedding load |
| Idempotency key | Make a retried/hedged call safe to execute more than once | Double-charging or double-applying a side effect when two attempts both land |
Each is small alone; the interview is in how they interact.
Backoff with jitter and a budgetthe storm-avoidance primitive
async function withRetry<T>(
fn: (signal: AbortSignal) => Promise<T>,
opts: { maxAttempts: number; baseMs: number; capMs: number; signal: AbortSignal },
): Promise<T> {
let attempt = 0;
for (;;) {
try {
return await fn(opts.signal);
} catch (err) {
attempt++;
if (attempt >= opts.maxAttempts || opts.signal.aborted) throw err;
const ceil = Math.min(opts.capMs, opts.baseMs * 2 ** attempt);
const delay = Math.random() * ceil; // full jitter
await sleep(delay, opts.signal);
}
}
}Two details earn the credit. Full jitter (random() * ceil, not ceil + random()) is what actually de-synchronizes a thundering herd. A bounded maxAttempts is the retry budget that stops a dead provider from amplifying load forever.
Hedging without doubling the billlatency vs cost, made explicit
- 1Fire the primary. Start the request and a timer for the hedge delay (often near the p95 of normal latency).
- 2Hedge only on the tail. If the primary hasn't returned by the delay, launch a second attempt - ideally to a different provider.
- 3Take the first, cancel the rest. Resolve on the first success and abort the loser so it stops consuming tokens and GPU.
- 4Cap the duplicate cost. Hedge a bounded fraction of traffic; if hedge rate climbs, that's a signal the primary is unhealthy, not a reason to hedge everything.
Retries and hedges both mean a request can execute twice. An idempotency key per logical request lets the server dedupe, so two attempts that both land don't double-apply a side effect. For pure read-like inference calls this is cheap; the moment a request writes (billing, a stored completion) it is mandatory.
Cancellation has to propagate all the way down. If the client disconnects but you only resolve the top-level promise, the upstream provider call keeps running and keeps billing. Thread one AbortSignal from the edge through every await and demonstrate that in the code.
Takeaway. Timeouts, jittered backoff with a budget, capped hedging, bounded concurrency and idempotency keys are the gateway's building blocks - and the interview is in how they compose, not each in isolation.
Self-check
QWhy is request hedging a double-edged tool and how do you keep it from blowing up cost?
The AI-authenticity bar
After this you can use AI tools in the interview the way Cursor rewards.
Cursor lets you use AI tools in the coding screens. That permission is also the test. They are watching whether you drive the model or the model drives you.
The stated bar is blunt: candidates must understand every line, reject poor suggestions and explain their debugging decisions. Pasting raw model output you can't defend is treated as an instant rejection. The interviewer is hiring a senior IC who owns correctness, not someone who outsources it.
Interactive diagram. Tab through its regions; each focused region shows its detail in the panel below.
Same tools, opposite signals - the difference is who owns correctness.
“I'll let Tab draft the retry loop, but I'm going to reject its first pass - it used ceil + random(), which is partial jitter. I want full jitter here so retries actually de-synchronize. Let me fix that and explain why.”
That narration does three jobs at once: it shows you read the suggestion, it shows you have a correctness opinion and it shows the specific domain knowledge (jitter strategy) that the tool doesn't supply. Silence while you accept a completion reads as the opposite.
Run a real Cursor-native workflowfluency, not a demo
Use it for boilerplate and obvious next lines.
Reject and retype when it guesses wrong - out loud.
Targeted inline edits and quick refactors.
Scope the instruction tightly so the diff is reviewable.
Multi-file scaffolding when the task spans files.
Read every generated change before you accept it.
Mention you'd encode project conventions here.
Shows you use the product as a system, not a toy.
Treat the model as a fast junior pair. It produces volume; you supply judgment, architecture and the tradeoff calls it can't make. You stay accountable for every line that ships, exactly as you would reviewing a teammate's PR.
- 1Frame the contract yourself. State the signature, constraints and failure cases before invoking any AI - you set the target, not the model.
- 2Generate in small slices. Ask for one function or one edit, not the whole solution, so each diff is reviewable.
- 3Audit out loud. For each suggestion say accept-or-reject and the reason; fix anything you wouldn't merge.
- 4Verify, don't trust. Run it, trace an edge case and narrate the debugging - never claim it works because the model wrote it.
The fastest fail is accepting a slick completion you can't explain when the interviewer asks “why this?” Practice a clean AI-assisted solve out loud beforehand so the narrate-and-reject rhythm is automatic under time pressure. If a suggestion would take longer to verify than to write, write it.
Takeaway. AI tools are allowed and graded: narrate every accept/reject, understand every line and treat the model as a fast junior pair while you own correctness - pasting unexplained output is an instant fail.
Self-check
QCursor allows AI tools in the coding screen. What separates a passing candidate from a failing one when using them?
Communication and review-readiness
After this you can present code that reads like production-owned work.
On a sub-100-person team where you ship senior IC from week one, the code you write in the screen is a sample of the code you'd put up for review. They read it that way.
The difference between a strong and a weak finish is rarely the algorithm. It's whether you talked in tradeoffs, named edge cases before they had to ask and left the file in a state a teammate could merge.
Talk in tradeoffs, not just answersthe senior signal
- State complexity and the failure modes: “O(n) over tokens, but it buffers one full line, so a pathological no-newline stream grows unbounded - I'd cap it.”
- Name what you'd test before they ask: empty stream, partial chunk, provider 500, timeout, oversized input.
- Call out what you're deferring and why, so a deferral reads as judgment rather than a gap.
Leave it reviewablethe merge test
Clear names, small functions, one responsibility each.
No dead scaffolding or commented-out experiments left behind.
tmp, data2, half-finished branches everywhere.
A giant function the interviewer has to decode.
“I didn't get the priority-queue admission control wired up. The happy path and the rate limiter are correct and tested in my head against empty and burst inputs. Next I'd add a QoS class so Tab requests jump chat under load, since Tab has the sub-100ms budget.”
Honesty about what's unfinished is a senior tell, not a confession. It shows you know what “done” means and can prioritize the remaining work - which is exactly the gray-area judgment the role is screened for.
- 1Increment small and correct. Land a working slice, confirm it, then extend - never a big half-working sketch.
- 2Verify each slice. Trace one real edge case aloud before moving on, so correctness is demonstrated, not asserted.
- 3Tidy before time's up. Rename the rushed variable, delete the scratch branch, so the final read is clean.
- 4Close with next steps. State what you'd do with another hour; it leaves the interviewer with your roadmap, not your gaps.
Reserve the last two minutes to clean up and summarize: what works, what you tested it against, what you'd build next. A tight close converts a partly-finished problem into a clear signal of how you'd operate in production - the exact thing this team is buying.
Takeaway. Code in the screen is a review sample: talk in tradeoffs, surface edge cases before you're asked, keep it mergeable and be honest about what's unfinished plus what you'd do next.