skip to content

Pagination, Filtering, and Field Selection

How a REST contract lets clients page, filter, sort, and shape large collections without melting the backend or breaking under concurrent writes. Interviewers dig here because naive offset paging and ad-hoc filter params are where real APIs degrade first.

part ofAPI stylesoverview, primer and where to startread it →
on this pageshow

questions

13

Design the query parameter that lets a client of a REST list endpoint sort by several fields at once, in either direction — for example newest first, then by name ascending. What syntax would you choose and why?

level: juniorimportance: must knowfreq 58%

basics

~10 s

Use one comma-separated sort parameter where order matters and a leading - means descending: sort=-created_at,name. Whitelist the allowed field names, define a default sort, and always append a unique tiebreaker such as the id.

open as a page

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?

level: juniorimportance: must knowfreq 52%

basics

~20 s

Omitted: 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.

open as a page

Plain `field=value` query parameters can only express equality. Compare the common ways a REST API expresses richer filter operators — bracket suffixes like `created_at[gte]=`, right-hand-side prefixes like `created_at=gte:`, and full expression languages such as RSQL/FIQL or OData `$filter` — and say which you'd pick.

level: middleimportance: must knowfreq 52%

basics

~20 s

Bracket suffixes (created_at[gte]=2024-01-01) and RHS prefixes (created_at=gte:2024-01-01) add per-field operators while keeping ordinary query parsing; RSQL/FIQL and OData $filter add a real expression language with AND/OR/grouping, at the cost of a parser, a validator, and unbounded query complexity. Start with the simple forms.

open as a page

Compare paginating a REST collection with `page`/`size` (or `offset`/`limit`) parameters against paginating with an opaque cursor token such as Stripe's `starting_after` or Google's `pageToken`. What does each give up, and when would you choose each?

level: middleimportance: must knowfreq 72%

basics

~20 s

Offset paging is simple and supports jumping to any page, but gets slower the deeper you go and duplicates or skips rows when the collection changes between requests. Cursor paging anchors on the last item seen, so it stays cheap and stable, but only supports next/prev — no page numbers, no totals.

open as a page

Your list endpoint forwards query-string filter parameters into the data layer, and clients may name any field and any operator. What goes wrong in production, and how do you constrain the surface without crippling the API?

level: seniorimportance: must knowfreq 50%

basics

~20 s

Arbitrary filters leak internal and unauthorized fields, enable expensive unindexed scans, and can inject query fragments. Constrain with an explicit per-resource field×operator whitelist, typed value coercion, cost caps, and 400 on anything not allowed — never silent ignore.

open as a page

A client of your JSON HTTP API complains that each item in a collection response carries far more data than the screen needs. Explain what a `fields` query parameter (sparse fieldsets) does about that, and how the JSON:API `fields[type]=` form differs from Google's partial-response `fields=` syntax.

level: juniorimportance: should knowfreq 45%

basics

~20 s

A fields query parameter lets the caller list the attributes it wants, and the server returns only those. JSON:API scopes it per resource type (fields[article]=title,body); Google's partial-response syntax describes a path through the response body (fields=items(id,name)), so it can reach nested objects.

open as a page

Some HTTP APIs let a caller write something like `?expand[]=customer` so that a related object arrives inline instead of as a bare id. Explain what such an expansion parameter is for, and what constraints you would put on it when designing the API.

level: middleimportance: should knowfreq 40%

basics

~20 s

Expansion inlines a related resource that would otherwise be just an id, so the caller avoids a second round trip. Constrain it: an explicit allow-list of expandable paths, a maximum depth, no expansion of unbounded collections, and permission checks on the expanded object.

open as a page

You are specifying the cursor token for a public list API. What should the token encode, how long should it stay valid, and what should the endpoint do when a client sends a cursor whose anchor record has since been deleted, or sends it alongside a different sort or filter?

level: seniorimportance: should knowfreq 42%

basics

~20 s

Encode the sort keys of the anchor row plus a fingerprint of the sort and filters, and sign it. Keep it valid as long as practical and document any expiry. A deleted anchor should still work if the token carries sort values rather than just an id. Mismatched sort or filters must be rejected with 400, not silently applied.

open as a page

Your team is considering adding caller-controlled field selection and relation expansion to a high-traffic public JSON API. What are the practical limits and operational risks of that kind of over-fetch control, and how would you contain them?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Caller-controlled shaping fragments caches, makes response shapes untestable combinatorially, lets clients drive unbounded backend cost, and turns field-level authorization into a per-request concern. Contain it with allow-lists, depth and length caps, cost-weighted rate limits, selector-aware cache keys, and a small set of named presets for hot paths.

open as a page

You are setting the filtering grammar for a public API that many teams will extend over years, and clients keep asking for richer queries. How do you decide between a small fixed set of filter parameters and a general query language, and how do you leave room to evolve without a breaking change?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Decide on evidence, not aesthetics: adopt a general language only when many clients genuinely need OR and grouping. Start with a bounded field×operator grammar, keep it additive, reserve syntax up front, and offer a separate search endpoint as the escape hatch for queries the URL should not carry.

open as a page