In an HTTP API, how do you decide whether a value belongs in a path segment such as /orders/{id} or in the query string such as ?status=open?
answer
- path = identity, query = modifiers
- required → path, optional/default → query
- different resource vs different view
- no path explosion for optional filters
- no secrets in query — they are logged
basics
~20 sPath carries identity and hierarchy — what resource you are addressing; it is required and usually part of a single thing's URL. Query carries modifiers on a collection — filtering, sorting, pagination, field selection; these are optional and any combination is valid.
solid answer
~50 sThe rule of thumb: **path = identity, query = modifiers**. A path segment says *which resource* you mean, and it is normally required: `/customers/42/orders/9` walks a hierarchy down to one thing. Remove a segment and you are addressing something different (or nothing). A query parameter modifies how a collection is presented without changing which collection it is: `/orders?status=open&sort=-createdAt&page=2&pageSize=50`. These are optional, order-independent, and combinable — `/orders` alone must still be valid. Practical tests I apply: - Is it required for the request to make sense? Required and identifying → path. - Would omitting it give a sensible default? → query. - Does it produce a different resource, or a different view of the same one? Different view → query. - Is the combinatorial space large (many optional filters)? Query, or you get path explosion. Caveats: query strings appear in access logs and Referer headers, so no secrets there; and path segments cannot contain a raw `/`.
code
http · 3 linesGET /customers/42/orders?status=open&sort=-createdAt&page=2&pageSize=50 HTTP/1.1
Host: api.example.com
Accept: application/jsongo deeper
State the rule — identity in the path, filters/sorting/pagination in the query — and give one example of each.
Add the decision tests (required vs optional, different resource vs different view) and mention path explosion and length limits.
Bring in enforcement of hierarchical relationships, cache-key and logging implications, and when a query string outgrows the URL and becomes a POST search.
Set conventions across the API — standard names for paging/filtering, nesting depth limits, and rules about what may never appear in a URL — and reason about their effect on caching and gateway policy.
## The distinction A URL has a hierarchical part (the path) and a non-hierarchical part (the query). That is not just syntax; it maps cleanly onto two different jobs. The **path** identifies. `/customers/42/orders/9` reads as a containment walk: within customers, the one with id 42, within its orders, the one with id 9. Segments are ordered and structural — you cannot shuffle them, and each one narrows what you are addressing. Path values are effectively required: there is no such thing as omitting the id from `/orders/{id}` and still having the same endpoint. The **query** modifies. `?status=open&sort=-createdAt&page=2` does not change which collection you asked for; it changes which members are returned and how. Query parameters are unordered, optional, and independently combinable, which is exactly right for filters, search terms, pagination, sorting, field selection (`fields=id,total`), and expansion (`include=customer`). ## Practical decision tests 1. **Required or optional?** If the request is meaningless without the value, it is probably identity → path. If there is a sensible default, it is a modifier → query. 2. **Different resource or different view?** `/orders/9` and `/orders/10` are different things. `/orders?status=open` and `/orders?status=closed` are two views of one collection. 3. **Combinatorics.** Five optional filters as path segments would require you to define every ordering and every subset. As query parameters they compose for free. Any time you feel tempted to write `/orders/status/open/sort/date`, that is the signal. 4. **Cache and log shape.** Both path and query participate in cache keys, but query parameters are the conventional place for things that vary per request; a cache seeing `/orders` with different queries handles that natively. ## Hierarchy is a promise If you write `/customers/42/orders/9`, you are asserting that order 9 belongs to customer 42 — and the server must enforce it. A very common security bug is treating the parent segment as decoration: the handler looks up order 9 by id alone and ignores customer 42, so any authenticated customer can read any order by guessing the child id. Either enforce the relationship or do not model it in the path. A related question is depth. Deeply nested paths (`/a/{i}/b/{j}/c/{k}/d/{l}`) get brittle because every level must remain true forever. Common guidance is to stop at one level of nesting for reads and let the child be addressable directly (`/orders/9`), using nesting mainly for creation and listing within a parent (`/customers/42/orders`). ## Things that do *not* belong in either **Secrets.** Query strings are written to access logs, proxy logs, browser history, and — for HTML pages — historically to the `Referer` header of outbound links. API keys and tokens belong in the `Authorization` header, never in `?api_key=`. The same argument applies to path segments; they are logged just as thoroughly. **Large or structured payloads.** URLs have practical length limits (servers and proxies commonly cap the request line around 4–8 KB). A filter object with a hundred ids does not belong in a query string; that is the case for a POST-based search endpoint. **Content type and version, arguably.** Format is negotiated with `Accept`; whether the API version lives in the path is a separate contract decision, not a path-vs-query one. ## Quick reference - Path: ids, hierarchical parents, singleton sub-resources (`/users/42/avatar`). - Query: `filter`, `q`/search, `sort`, `page`/`cursor`, `limit`, `fields`, `include`, `lang` when it is a preference rather than a distinct resource. - Header: authentication, content negotiation, conditional requests, idempotency keys.
- Where would you put an API key, and why not in the query string?In the Authorization header. Query strings are recorded in server and proxy access logs, browser history, and analytics pipelines, so a key placed there leaks into many systems that are not treated as secret stores and are often retained for a long time. Headers are not logged by default in most stacks.
- A path like /customers/42/orders/9 exists. What must the server check that developers often forget?That order 9 actually belongs to customer 42. If the handler looks the order up by its own id and ignores the parent segment, any authenticated user can read another customer's orders by guessing ids — a classic broken-object-level-authorization bug. Model the hierarchy only if you enforce it.
saying these in an interview costs you the question
- Putting optional filters into path segments, causing path explosion
- Passing API keys or tokens as query parameters
- Nesting the path four or five levels deep and calling it more RESTful
- Treating the parent id in a nested path as decoration rather than an authorization check
- Assuming query strings have no practical length limit