The HTTP `Link` header standardized by RFC 8288 is how GitHub's API exposes pagination. Describe its syntax, which relation types are used for paging, and how a client should consume it correctly.
answer
- `<url>; rel="next"`, comma-separated
- next / prev / first / last — `prev`, not `previous`
- opaque URLs, never increment page
- no `next` = end of collection
- expose via Access-Control-Expose-Headers
basics
~20 sLink carries one or more comma-separated entries of the form <url>; rel="next". Paging uses rel values next, prev, first, last. Clients must parse the header and follow the URLs opaquely, never rebuild them by incrementing a page number.
solid answer
~50 sRFC 8288 defines `Link` as a list of *web links*: `<URI-Reference>; param=value`, entries separated by commas. The `rel` parameter names the relation. For pagination the registered relations are `next`, `prev` (spelled `prev`, not `previous`), `first`, `last`: ``` Link: <https://api.example.com/repos?page=3>; rel="next", <https://api.example.com/repos?page=50>; rel="last" ``` Rules a correct client follows: parse the header rather than assume ordering; treat each URL as **opaque** — do not extract `page` and increment it, because the server may switch to cursors; stop when no `rel="next"` is present (that is the end-of-collection signal, not an empty page); resolve relative URI references against the request URL; expect `last` to be absent when the server refuses to compute a total. Parsing is fiddly — URLs may contain commas and semicolons inside the angle brackets, so use a real parser. The payoff is that paging is visible to generic HTTP tooling and keeps the body a pure array.
code
http · 9 linesHTTP/1.1 200 OK
Content-Type: application/json
Link: <https://api.example.com/v1/repos?page=4&per_page=30&sort=stars>; rel="next",
<https://api.example.com/v1/repos?page=50&per_page=30&sort=stars>; rel="last",
<https://api.example.com/v1/repos?page=1&per_page=30&sort=stars>; rel="first",
<https://api.example.com/v1/repos?page=2&per_page=30&sort=stars>; rel="prev"
Access-Control-Expose-Headers: Link
[ { "id": 91 }, { "id": 92 } ]go deeper
Recall the syntax and the four paging relations, and that you follow the next URL rather than constructing it.
Explain opacity, the end-of-iteration rule, relative-reference resolution, and why last may be missing.
Cover server-side correctness: forwarded host/proto in generated URLs, carrying filters into every link, CORS exposure, and rate-limit interaction while paging.
Argue when transport-level links beat body links across an API estate — gateway rewriting, non-JSON collection media types, SDK generation, and how link opacity buys you the freedom to change the paging implementation later.
## What the header is RFC 8288 ("Web Linking", which obsoleted RFC 5988) defines a *typed link* between the current resource and another one, and serializes it in the `Link` HTTP header. The grammar is: ``` Link: <target-URI>; rel="relation"; title="...", <target-URI-2>; rel="other" ``` The target URI goes inside angle brackets, parameters follow after semicolons, and multiple links are separated by commas. It may also appear multiple times as separate header lines — semantically equivalent to one comma-joined line. ## The paging relations IANA maintains the link-relation registry. The four that matter for collections: - **`next`** — the following page. - **`prev`** — the preceding page. The registered token is `prev`; `previous` is a common but non-registered misspelling. - **`first`** — the first page. - **`last`** — the final page. Only computable if the server knows the total, so cursor-paginated APIs often omit it. GitHub's API popularized the pattern and is the canonical example: it emits `next`/`last` on the first page, `prev`/`first` on the last, and all four in between. GitHub also historically exposed a non-standard extension by adding relations for its own needs — extensions are legal because `rel` may hold a URI for private relation types. ## Consuming it correctly **Treat URLs as opaque.** The single most important rule. The whole point of returning a URL is that the client does not need to know whether paging is offset-based, cursor-based, or something else. A client that parses out `?page=3` and does `page + 1` breaks the day the server moves to cursors, and breaks immediately if the server adds a signed or expiring token to the URL. **Use `next` presence as the terminator.** The end of the collection is "no `rel="next"` link", not "page returned fewer than `per_page` items" and not "page returned zero items". Fetching one extra empty page to discover the end is a wasted round trip and, with cursor paging, may never terminate. **Parse, don't regex naively.** Target URIs legitimately contain commas (`,` in a filter value) and semicolons, and parameter values may be quoted. A split on `,` will corrupt those URLs. Most languages have a link-header parser; use it. **Resolve relative references.** RFC 8288 permits a relative URI reference; resolve it against the request URI (or `Content-Location` if present) before fetching. **Preserve credentials and headers.** The returned URL is just a path plus query — the client must re-send the same `Authorization` header, and honour the same rate-limit rules. ## Server-side responsibilities Emit absolute URLs where possible, and make sure they reflect the **client-visible** host and scheme, not the internal one — behind a load balancer this means honouring the forwarded host/proto configuration, otherwise you hand clients `http://10.0.3.4:8080/...`. Carry every filter and sort parameter of the original request into each link; the classic bug is a `next` link that drops `?status=open`, silently changing the result set mid-iteration. Set `Access-Control-Expose-Headers: Link` for browser clients, or the header is invisible to JavaScript despite being on the wire. ## Header vs body links Body `links` objects (JSON:API style) carry identical information and are easier for browser and SDK code to reach. `Link` keeps the body a pure array, is media-type agnostic (it works for CSV or binary collection exports where there is no body to put links in), and is visible to caches, proxies and generic HTTP clients. Neither is wrong. What is wrong is exposing paging *only* through documentation — "add `?page=N` until you get an empty response" — because that couples every client to the current implementation forever. ## Interview signal Stating the syntax gets you a pass. The signals of real experience are: `prev` not `previous`; opaque-URL discipline; `last` being expensive or absent; CORS exposure; and the forwarded-host bug in generated URLs.
- Why might a server emit `next` and `first` but never `last`?`last` requires knowing the total number of pages, which means an exact count of matching rows — expensive on large or filtered collections, and meaningless under cursor pagination where pages are defined relative to an anchor rather than by index. Omitting `last` is a deliberate choice to avoid that cost, and clients must not treat its absence as an error.
- A browser client says the `Link` header is missing, but curl shows it. What is happening?Cross-origin JavaScript can only read a small set of safelisted response headers. `Link` is not one of them, so the server must send `Access-Control-Expose-Headers: Link`. The header is genuinely on the wire — the browser is hiding it from the page's script.
saying these in an interview costs you the question
- Using `rel="previous"` instead of the registered `rel="prev"`
- Parsing out the `page` parameter and incrementing it instead of following the URL opaquely
- Treating an empty page rather than the absence of `rel="next"` as the end of iteration
- Splitting the header on commas without accounting for commas inside the bracketed URLs
- Generating links from the internal host/port so clients receive unreachable URLs