Skip to content

API design & contracts in a Senior interview

An API is a promise other teams build on, your own frontend included. Our questions test whether you can keep it while the system underneath changes, and the second version is where that gets hard.

An axis of our Senior backend and Senior fullstack targets.

The check comes with every pass and covers all the competencies of the target you choose: 8 for Senior backend (about 16 minutes) and 10 for Senior fullstack (about 20 minutes). It reports each one as Ready, Borderline or a Gap for this level. It never predicts a pass.

Patterns our questions are designed to test at Senior

These are the distinctions the questions on this axis are written to separate — our judgement from writing them and having them reviewed, not a measurement of candidates. We have run no study, so nothing here is a statistic and none of it is phrased as one.

  1. 1.

    Compatibility judged against the schema rather than the callers

    Adding an optional field is safe. Making it required, narrowing a type, changing a default or tightening validation are not — and whether they break anything depends on what clients actually send, which is a question you have to be able to answer.

  2. 2.

    Errors designed last

    A caller has to handle every failure mode you expose, not only the success shape. Which errors are retryable, which are permanent, whether the same request may be sent again, and how a client tells the three apart is part of the contract.

  3. 3.

    Idempotency delegated to the transport

    HTTP says which methods ought to be idempotent. It does not make your handler so. Where the key comes from, how long it is remembered, and what happens when the same key arrives with a different body are API decisions with an operational cost.

  4. 4.

    Pagination that breaks while the data moves

    Offset paging over a table being written to skips and repeats rows. Cursor paging is the version that stays correct under concurrent writes, and the trade it makes, no jumping to page forty, is worth saying out loud.

What a strong answer sounds like: A strong answer talks about existing callers by what they actually send, not by what the specification permits.

How the check measures it

3 questions drawn on this axis alone — 2 medium and 1 hard for a Senior target, because the bar is the level. Hard counts for one and a half times a medium, which is what makes the verdict relative:

Medium rightHard rightVerdict
2 of 21 of 1Ready
1 of 21 of 1Ready
2 of 20 of 1Borderline
0 of 21 of 1Borderline
1 of 20 of 1Gap
0 of 20 of 1Gap

Three questions is coarse and the report says so beside every verdict. It is enough to separate this axis from the others in your target, which is what the check is for — there is no total, no percentage and no single number anywhere in the report.

What there is to practise

122 questions on this axis
94 medium, 28 hard. Easy is not drawn for a Senior target.

Every answer cites the source it was checked against.

Drawn from Networking & APIs (41), TypeScript & Node (17), AWS (16), Go (15), System Design (13).

Practised in the System design round
Design a system to requirements and defend the trade-offs: storage, messaging, caching, capacity and cost.

Practice is arranged by interview round rather than by topic, so you can work through it one round at a time.

Questions here most often also test reliability & failure modes, technical judgment: decisions and trade-offs, networking and code quality & design. Few questions on this axis test it alone, which is why the check reports every competency separately rather than averaging them.

One of them, in full

A question from the free demo, not written for this page. A demo quiz opens with it; the answer, the explanation and the source come when you submit — no account needed.

API design & contractshard

A TypeScript (strict) profile service on Node 24 applies partial updates:

interface Profile { name: string; phone?: string }
type ProfilePatch = Partial<Profile>;

// body.name and body.phone are string | undefined
const patch: ProfilePatch = { name: body.name, phone: body.phone };
await store.save({ ...current, ...patch }); // save(p: Profile)

A mobile client that sends only {"phone": "…"} leaves the stored profile with no name, though Profile says it always has one, and the build is clean. The team wants any code that can put undefined into a patch field to fail to compile. Which change does that?

Answer it on the demo

One axis is not the loop

This axis is one of 8 in our Senior backend target and one of 10 in Senior fullstack, and being strong here says nothing about the others. The check covers all of them in one sitting and reports each separately, so what comes back is a profile rather than a score.