A REST list endpoint accepts a `limit` (or `size`) query parameter. What should the API do when a client omits it, sends `limit=0`, sends `limit=100000`, or sends something non-numeric?
answer
- default 20-50, never unbounded
- max derived from latency + payload budget
- over max: clamp or 400 — but document it
- 0 / negative / non-numeric → 400
- echo page_size + has_more, never infer the end from a short page
basics
~20 sOmitted: apply a documented default (often 20-50) — never "everything". Over the maximum: either clamp to the max or return 400, but document which. Non-numeric or negative: 400. Echo the effective limit in the response so the client can see what actually applied.
solid answer
~60 sEvery list endpoint needs three documented numbers: **default**, **maximum**, and what happens outside them. - **Omitted** → the documented default (20-50 is typical). Never unbounded: an unpaginated collection endpoint is a latency and memory incident waiting for the collection to grow. - **Above the maximum** → pick one behaviour and document it. Clamping to the max is forgiving and common (Stripe caps at 100); rejecting with `400` is more honest because the client learns it did not get what it asked for. The failure mode to avoid is honouring it. - **`limit=0`** → either `400`, or a defined meaning such as "metadata only, no items" if you support a count-only mode. Undefined behaviour here is a common source of infinite client loops. - **Non-numeric or negative** → `400` with a message naming the parameter. Always return the effective page size in the response (`meta.page_size`), because a silently clamped limit otherwise looks to the client like the end of data. And the maximum should be derived from real response-size and latency budgets, not chosen because it is round.
code
http · 9 linesGET /v1/orders?limit=100000 HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json
{
"data": [ ... 100 items ... ],
"meta": { "page_size": 100, "requested_limit": 100000, "has_more": true }
}go deeper
State the default/maximum/validation rules and that unbounded responses are never acceptable.
Explain clamp-versus-400 and why the effective size plus an explicit has_more must be returned.
Derive the cap from latency and payload budgets, account for expansion cost, and treat the cap as a stability control not subject to per-customer exceptions.
Standardize parameter names, defaults and caps across the API estate, and set the policy for how caps are revisited as resources and clients grow.
## Why this small question is asked It is a cheap way to find out whether a candidate thinks about the contract's edges. The happy path is trivial; the interesting behaviour is at the boundaries, and the boundary bugs are the ones that page an on-call engineer. ## The default When the client sends no size, the server picks. The wrong answer is "return everything" — an endpoint that returns the whole collection works perfectly in development against fifty rows and takes the service down when a customer reaches five hundred thousand. Pagination is not an optimization to add later; unbounded reads become load-bearing in client code the moment they exist, and removing them is then a breaking change. Pick a default that keeps a typical response comfortably inside your latency and payload budget: 20-50 items is the common band. Document it, and treat it as part of the contract — clients will build UIs assuming it. ## The maximum The maximum exists because response size and query cost scale with it. Derive it from a real budget: a target p99 latency and a target maximum serialized response size, divided by the typical item size. A 100-field resource and a 5-field resource do not deserve the same cap. Two defensible behaviours above the cap: - **Clamp silently to the maximum.** Forgiving; the client still gets data. This is Stripe's style (`limit` is capped at 100). The risk is that a naive client asking for 1000 gets 100, sees fewer items than requested, concludes "that's the end", and stops — which is why you must return the effective size and a `has_more` flag or `next` link. - **Reject with `400`.** Explicit; the client learns immediately. Better for internal APIs and for clients you can fix. What is not defensible is honouring an arbitrary limit "just this once" for a big customer: the cap is a stability control, and per-caller exceptions are how a service discovers its worst-case response the hard way. ## Zero, negative, non-numeric `limit=0` must have a defined meaning. Two reasonable choices: `400 Bad Request`, or "return no items, only metadata" — useful when paired with an opt-in total count so a client can ask "how many match?" without transferring rows. If it is undefined and the server returns an empty page, a client looping until it gets an empty page terminates immediately and silently processes nothing; if the server instead returns the default page, the client's intent is quietly ignored. Negative or non-numeric values are straightforward `400`s. The one thing to avoid is a stack trace or an unhandled parse exception surfacing as a `500` — parameter validation belongs at the edge, and an invalid client input is not a server error. ## Reporting the effective values Echo the applied page size back: `meta.page_size`, or make it visible in the `self` link. Combine it with an explicit end-of-data signal — `has_more: false`, or the absence of a `rel="next"` link — so clients never have to infer termination from "I got fewer items than I asked for". That inference is wrong under clamping, wrong when a filter reduces a page, and wrong for cursor-based APIs that may legitimately return a short page with more data behind it. ## Related contract details worth mentioning **Naming.** `limit` (with `offset`/cursor) or `size`/`per_page` (with `page`) — pick one pair and use it across the API. Mixed vocabularies across endpoints are a persistent client annoyance. **Interaction with cursors.** If the client supplies a cursor, the page size may change between requests without breaking anything (unlike sort or filters, which invalidate the cursor). It is fine to allow it; just be clear. **Cost per item is not constant.** If items can be expanded with sub-resources, the effective cap should account for it — a page of 100 orders each with expanded line items may be far more expensive than 100 bare orders. Some APIs lower the maximum when expansions are requested. ## The compact answer "Documented default around 20-50, never unbounded; a documented maximum derived from a payload and latency budget; above the max either clamp or 400, but say which; 0/negative/non-numeric is a 400; and echo the effective page size plus an explicit `has_more` so a clamped page isn't mistaken for the end of the collection."
- Why is "fewer items than I requested" an unreliable end-of-collection signal?Because a short page has several other causes: the server clamped an oversized limit, post-query authorization filtering removed rows, or a cursor-based implementation returned a partial batch. The only reliable signals are the ones the server states explicitly — a `has_more` flag, or the presence or absence of a `rel="next"` link.
- How would you choose the maximum page size for a specific endpoint?Work backwards from budgets: a target p99 latency and a maximum acceptable serialized response size, divided by the measured average item size for that resource. Lower it further when the endpoint supports expansions that multiply per-item cost. It should be a measured number per resource, not one global constant applied to a 5-field and a 100-field resource alike.
saying these in an interview costs you the question
- Returning the entire collection when the size parameter is omitted
- Honouring arbitrarily large limits because "the client asked for it"
- Letting a non-numeric limit surface as a 500 instead of a 400
- Clamping silently with no `page_size` or `has_more` in the response, so clients read a short page as the end
- Using a single global maximum for every resource regardless of item size or expansions