skip to content

How does a framework bind a repeated query parameter or a single delimited value into a collection handler argument?

level: middleimportance: should knowfreq 48%

answer

  1. repeat the key or delimit one value
  2. neither spelling is defined by the grammar
  3. element converter runs per element
  4. a delimiter collides with values containing it
  5. unbounded element counts need a cap

basics

~20 s

A query string expresses several values by repeating the key or by delimiting one value. Neither spelling is defined by the URL grammar, so the framework picks a convention and runs the element converter on every element.

solid answer

~40 s

There are two wire spellings for "more than one": **repetition** (`tag=a&tag=b`) and a **delimiter inside one value** (`tag=a,b`). The URL grammar defines neither, so each framework chooses which it understands, and some understand both. When the declared parameter is a collection, the binder gathers the occurrences (or splits the delimited text), then runs the **element type's converter on every element**, so one bad element normally fails the whole binding. The mismatched cases are where bugs live: a **scalar** parameter facing a repeated key silently keeps one occurrence — first or last, depending on the framework — rather than failing; a collection facing one occurrence yields a one-element collection; and an absent key yields an empty collection or an absent marker depending on the declaration.

code

http · 3 lines
http
GET /articles?tag=red&tag=blue HTTP/1.1
GET /articles?tag=red,blue HTTP/1.1
GET /articles?tag= HTTP/1.1

go deeper

for a junior

Know both spellings — the key repeated, and one value with separators inside it — and that the framework decides which one it reads for a collection parameter.

for a middle

Explain that the element type's converter runs on each element, that binding is all-or-nothing, and what a single occurrence, an absent key and an empty value each produce.

for a senior

Bring the operational edges: a repeated key silently collapsing into a scalar, separators inside values, and element counts a caller can raise without limit.

for a principal

Make it a service-wide convention — one spelling, one empty-value rule, one documented cap — so that clients and generated documentation agree across every endpoint.

## Two ways to say "more than one" A query string is a flat sequence of key/value pairs. It has no native list type, so multiplicity is expressed by convention, and there are two in common use: - **Repetition** — `?tag=red&tag=blue`. The key appears more than once; order is the order it appeared. - **Delimited value** — `?tag=red,blue`. One occurrence whose text encodes several values, usually comma-separated, sometimes with another separator. A third convention appears in some ecosystems: a bracket or other suffix on the key to mark it as a list. None of the three is mandated by the URL grammar or by HTTP. The server side decides what it understands, and the client has to match — which is exactly why a list parameter is worth documenting explicitly rather than assuming. ## What the binder does with each 1. Collect every raw occurrence of the key, in arrival order. 2. If the framework supports delimiters and the declaration asks for it, split each occurrence on the separator. 3. Look up the converter for the **element type**, not for the collection type. 4. Run it on every element. 5. If any element fails, binding normally fails and the request is answered as a client error; a list converts all-or-nothing rather than quietly dropping the bad element. 6. Assemble the results into the declared collection shape. Step 3 is the part worth saying out loud: a collection parameter is really a declaration of *element type plus multiplicity*, and the element converter is the same one a scalar of that type would use. ## The mismatch cases | Wire | Declared | Usual result | |---|---|---| | Key repeated | collection | every occurrence, in order | | Key repeated | scalar | one occurrence kept — first or last, framework-dependent — usually without an error | | Key once | collection | a one-element collection | | Key absent | collection | an empty collection, or an absent marker, depending on the declaration | | Key present, empty text | collection | one empty element, or an empty collection — this is the case frameworks most disagree on | The second row is the quiet one. A handler that expects a single value keeps working when a client starts sending two, and which of the two it keeps may differ between a service and its own test harness. When one value has to mean one value, the parameter should be checked, not assumed. ## Why a delimiter is ambiguous and repetition is not If values may themselves contain the separator, a delimited list cannot be parsed unambiguously: `tag=red,blue` and a single tag literally named `red,blue` produce the same text unless the client percent-encodes the separator inside the element. Repetition has no such problem, because the boundary is structural rather than lexical. That makes repetition the safer default for free-text values, and a delimiter acceptable mainly for closed vocabularies — identifiers, enumerated members, numbers — where the separator cannot legally appear in an element. Round-tripping is the practical test: if a client can take the values it received, put them back in a request, and get the same values, the encoding is sound. ## Operational edges - **Length has no natural bound.** Nothing stops a caller sending the key ten thousand times, or one value with ten thousand elements. Every element is converted, and the resulting collection is held in memory before the handler starts, so an element cap (and a request-size cap) belongs in the configuration, with a client error when it is exceeded. - **Duplicates survive.** Binding does not de-duplicate unless the declared collection shape is a set. If the handler treats the list as a set, say so in the declaration rather than downstream. - **Order is the arrival order**, not a sorted order, and clients should not rely on the server re-ordering. - **An empty element is not nothing.** `?tag=red,,blue` normally yields three elements, one of them empty, which will then fail conversion for any non-text element type. - **Trailing separators** behave the same way, and are a common artefact of client code that joins values with a separator inside a loop. ## What to specify For any list parameter, write down three things: which spelling the server accepts, what an absent key means versus an empty one, and the maximum number of elements. Those three answers remove almost every surprise in this area, and none of them can be inferred by a client from the parameter's name.

  • A scalar parameter receives two occurrences of its key. Why is that rarely an error?
    Because binders resolve multiplicity before conversion and most simply take one occurrence — the first or the last — rather than treating extra values as malformed. It is a defensible reading of a loose convention, but it means a client bug goes unnoticed, so a parameter that must be singular should be checked explicitly.
  • When is a delimited list a worse choice than repeating the key?
    Whenever an element can legally contain the separator. Free text, names and search terms all can, and the ambiguity is only removable by percent-encoding the separator inside each element, which clients frequently forget. Repetition puts the boundary in the structure instead of in the characters, so it round-trips safely.
  • What limits should a list parameter carry?
    A maximum element count enforced by the binder, on top of the overall limit on request line and header size. Every element is converted and held in memory before the handler runs, so an unbounded list is work a caller can request for free; exceeding the cap should be a client error naming the limit.

saying these in an interview costs you the question

  • Assuming the URL grammar defines how a list is encoded
  • Expecting a repeated key to fail against a scalar parameter
  • Using a comma-delimited list for values that may contain commas
  • Believing the binder de-duplicates or sorts collection elements
  • Accepting an unbounded number of elements from a caller