skip to content

How do you serialize a screen's filters, sort and page into an address so a reload rebuilds the same view?

level: middleimportance: should knowfreq 52%

answer

  1. text in, typed state out
  2. one spelling per type, chosen once
  3. omit anything equal to its default
  4. absent versus explicitly cleared
  5. parse then serialize yields the same string

basics

~20 s

Pick one short text spelling per value, omit anything equal to its default, emit keys in a fixed order, and make parsing the exact inverse, so a reload reproduces the screen and one state always produces one address.

solid answer

~40 s

Addresses carry text, so every typed value needs a chosen spelling and a chosen inverse. I fix a representation per type: numbers plain, booleans as an explicit word rather than a bare flag, dates in one unambiguous fixed format, a multi-select as a repeated key, a range as two keys. Then three policies do most of the work. **Omit defaults**, so the plain path is the canonical view and links stay short. **Decide absent versus empty explicitly** - a missing key means "use the default", an empty value means "the user cleared it". **Emit keys in a fixed order**, so one state always yields one address, usable as a key. The property I test is a round trip: parsing an address and re-serializing gives back the same address and the same screen.

code

pseudocode · 20 lines
pseudocode
defaults = { status: "open", sort: "-due", page: 1, tags: [] }

serialize(state):
  out = empty query, written in a fixed key order
  for key in [status, sort, page, tags]:
    value = state[key]
    if value equals defaults[key]: skip           # omit defaults
    if key is tags: for each tag in sorted(value): out.append("tag", tag)
    else: out.set(key, text of value)
  return out

parse(query):
  state = copy of defaults
  if query has "status": state.status = query.first("status")   # empty means cleared
  if query has "sort":   state.sort = known sort name or defaults.sort
  if query has "page":   state.page = whole number 1 or more, else defaults.page
  state.tags = sorted(query.all("tag"))
  return state

assert serialize(parse(serialize(s))) equals serialize(s)

go deeper

for a junior

Know that the address holds text only, so a number, a date or a set of choices needs a spelling going out and a parse coming back, and that a parameter left out should mean the default rather than a crash.

for a middle

Be able to state the policies and defend them: one representation per type, defaults omitted, fixed key order, absent versus explicitly cleared, and a round trip that returns the identical string.

for a senior

Demonstrate the operational angle - address churn from per-keystroke writes, practical length ceilings, malformed values from hand-edited links, and a property test that asserts the round trip.

for a principal

Treat parameter names and option spellings as a published vocabulary and decide how it evolves: aliases when reading, one spelling when writing, and a deliberate policy for state too large for an address.

## The property to aim at The goal is a **faithful round trip in both directions**. Serialize the screen's state, reload, parse what the address says, and the user is looking at the same view; and serializing that parsed state again produces an identical address. When both hold, a reload, a bookmark, a copied link and a Back navigation all rebuild one screen, and the address is usable as a cache key or a metrics dimension. When either fails you get drifting links that look different but mean the same thing, or one link meaning two things on two days. ## Choosing a spelling per type An address is text, so every value needs an encoding decision made once and applied everywhere. | Value | Spelling that holds up | Why | |---|---|---| | Number, page | plain digits, e.g. `page=3` | shortest, obvious, trivially parsed | | Boolean toggle | an explicit word, e.g. `archived=true` | a bare key is ambiguous once a third state appears | | One choice from a small set | the option's stable name, not its index | readable in the link, and reordering the options later does not change meaning | | Several choices | the key repeated, e.g. `tag=new&tag=sale` | standard parsing returns them all, with no delimiter to escape inside values | | Date or timestamp | one fixed unambiguous format | locale-dependent formats are re-read differently by different readers | | Range | two keys, a low and a high | either end can be omitted independently | | Free text | the value, correctly escaped | reserved characters must survive being pasted | Two things to avoid: encoding a **derived** value the other parameters already imply, and encoding the screen's internal state object wholesale - that couples the address to a data shape which will change while the links do not. ## Defaults: omit them Every parameter needs a declared default, and the serializer should **write nothing when the current value equals it**. Three benefits arrive together: the shortest form of the address is the canonical view, links stay short enough to read and paste, and two users who never touched the filters generate one address instead of two long equivalent ones. The parser is the mirror image: a missing key yields the default, never an error. The subtle part is **absent versus present-but-empty**, and it has to be decided rather than discovered: - *Absent* means "the default applies". - *Present but empty* means "the user explicitly cleared this" - which matters whenever a default is non-empty, for example a status filter that defaults to open and that the user wants to widen to everything. - If a parameter has no non-empty default, collapse the two and always omit, so no address carries a value-less key. ## Canonical form Beyond omitting defaults, pin down the rest of the spelling so one state has exactly one generated address: - **Fixed key order** on output, so the string is stable and comparable. - **Fixed value order** inside a repeated key, usually sorted, so selecting two tags in either sequence yields one address. - **Consistent escaping** of reserved characters, applied by one shared helper rather than at each call site. - **Case and vocabulary settled** - one spelling per option name, chosen once, because users now see it. ## Reading is the other half Everything in the address arrives from outside: an old link, a hand-edited parameter, a stranger's paste buffer. So parsing needs a defined outcome for missing, unknown, malformed and out-of-range values - typically the default, and for a value the screen cannot interpret, the default plus a visible indication that the requested view was not fully reproducible. Never let an unparsable parameter render a screen that silently ignores what the link asked for while looking authoritative. ## Practical limits - **Length.** Browsers, servers and intermediaries each impose their own practical ceilings, so a growing set of selections eventually belongs behind a short reference rather than in the address itself. - **Churn.** Writing the address on every keystroke of a search box produces a stream of near-identical addresses; commit on a debounce or on submit instead. - **Stability.** Parameter names and option spellings are a contract, because links already sent cannot be edited. Choose names you can live with, and when one must change, keep reading the old spelling while writing only the new one. ## A test worth writing One property test covers most of this: take a representative state, serialize it, parse it, serialize it again, and assert both the state and the string match. Then add cases for an empty address, an address with defaults written out explicitly, an unknown key, and one malformed value. Those five assertions catch nearly every serialization defect before a user ever pastes a link.

  • Why prefer an option's stable name over its numeric index in the address?
    Because the index is a position in a list that will be reordered or extended, while old links keep the old number and would silently select a different option. A name is readable in the link, survives reordering, and makes a stale or malformed value detectable so the parser can fall back deliberately.
  • A search box writes the address on every keystroke. What is the problem and the fix?
    It produces a burst of near-identical addresses and a data request per character. Keep the typed text in a local editing buffer and commit it into the address on a debounce or on submit, so one deliberate change of view produces one address.
  • How do you handle a parameter you must rename after links are already in circulation?
    Read both spellings and write only the new one. The parser accepts the old key as an alias so existing links keep working, while every address the application generates uses the new name - so the old spelling drains out of circulation instead of being supported indefinitely.

saying these in an interview costs you the question

  • Writing every parameter including defaults, so identical views get different addresses
  • Emitting keys in whatever order the state object happens to iterate
  • Encoding the screen's internal state object wholesale into one parameter
  • Treating a missing parameter as an error rather than as its default
  • Assuming address values arrive already typed and need no parsing decisions
  • Using an option's list index, which breaks when the list is reordered