skip to content

URL as Application State

Deciding what the address bar owns — the view, the selected entity, filters, sort, page — and serializing it so a reload or a shared link rebuilds the screen. Interviewers use it to test state design.

on this pageshow

questions

5

In a client-rendered application, which parts of a screen's state belong in the URL and which must not?

level: juniorimportance: must knowfreq 78%

answer

  1. shareable, bookmarkable, reachable with Back
  2. would a colleague's copy be correct?
  3. view, entity, filters, sort, page
  4. addresses are logged and leak via referrers
  5. identifier in, fetched record out

basics

~20 s

The URL should carry state a user could reasonably share, bookmark or reach again with the Back button: the view, the selected entity, filters, sort and page. Transient interface state, secrets and large payloads stay in memory.

solid answer

~50 s

The address bar is the only application state a user can see, copy, send to somebody else and return to, so it should hold everything that defines *where the user is*: the view, the identifier of the entity the screen is about, and the refinements of that view such as a search term, filters, sort and page. The test I apply is "if a colleague opens this link on another machine, should they see the same screen?" Transient interface state fails that test - hover, focus, an open menu, scroll offset, unsaved draft text. Secrets fail it for a different reason: addresses are copied into chat, kept in shared history and recorded in access logs, so tokens and personal data never go there. Large payloads fail it too - put the identifier or the query in the URL and fetch the data from it.

go deeper

for a junior

Remember the short list: the view, the selected item, and the filters, sort and page that shape it go in the address; hover, scroll, drafts and anything secret stay out. Say why: links and the Back button.

for a middle

Explain the test rather than reciting a list. Walk through a borderline case - an open drawer, a selected set - and argue it from whether Back should undo it and whether a stranger's copy of the screen is still correct.

for a senior

Show the operational reasons: addresses reach access logs, referrers and shared history, so secrets are excluded by policy; and every address value is untrusted input that needs a defined default and fallback.

for a principal

Frame it as a contract with a cost. Address state buys shareability, deep links and support reproducibility, and charges you stability of parameter names, a default for every one, and more distinct addresses to observe.

## What the address bar actually is Everything a screen holds - values in component state, a store, a cache of fetched records - is invisible and dies with the tab. The **URL is the exception**: it is the one piece of application state the user can see, select, copy, paste into a message, bookmark, and walk back through with the Back button. That is why state design starts here. The question is never "can I fit this into the address?" but "is this part of *where the user is*?" One practical test settles most cases: **if a second person opens this link, on another machine, in a fresh tab, what should they see?** Whatever must be identical for the link to be worth sending belongs in the address. Whatever would be meaningless, surprising or unsafe to reproduce does not. ## What belongs - **The view** - which screen of the application this is. The coarsest piece, and almost always carried by the path. - **The selected entity** - the record, document, conversation or board the screen is about, referenced by a short stable identifier rather than by its contents. - **Refinements of that view** - the search term, the active filters, the sort field and direction, the page or cursor, the selected tab of a tabbed region. - **Destination-like overlays** - a detail drawer or dialog the user would expect to link to, and to dismiss with Back. If it is a place, it is address state. - **A display mode that changes what the screen means** - list versus board versus calendar, or the date range of a report. ## What does not belong | State | Why it does not belong | Where it lives instead | |---|---|---| | Hover, focus, scroll offset, an open menu | Reproducing it from a link is meaningless or jarring | transient component state | | Unsaved draft text in a form | Users would leak half-written content by sharing the link | per-tab storage, keyed by the form | | Tokens, one-time codes, personal identifiers | Addresses are logged, shared and autocompleted | request headers or cookies, never the address | | A whole fetched record or result set | Enormous, unreadable, and stale the moment it is copied | fetch it from the identifier or the query | | Values derived from other URL state | Two sources of one truth drift apart | recompute from what is already there | ## Why secrets are a hard rule, not a preference An address leaves the application constantly and in ways the application does not control. It is pasted into tickets and chats, kept in browser history on shared machines, offered by autocomplete to the next person who types in the box, sent as a referrer to third-party assets on the page, and written verbatim into server and proxy access logs where it is retained far longer than any session. Anything whose disclosure matters must travel where those systems do not record it. ## Deciding the borderline cases 1. **Should Back undo it?** If yes, it is address state - that is exactly what the history stack gives you for free. 2. **Is a stranger's copy of this screen still correct?** A filter reproduces faithfully; a half-typed comment does not. 3. **Is the value small, stable and meaningful?** Short identifiers and named options survive being read aloud; serialized object graphs do not. 4. **Can a shorter value reproduce it?** Prefer the identifier over the record, the query over the results. ## The cost side of the decision Putting state in the address is not free, which is why the answer is a subset and not "everything". - It becomes a **contract**. Old links exist indefinitely in other people's messages, so a parameter you rename or drop breaks screens you cannot see. - Every value in it is **untrusted input**: it arrives from a stranger's paste buffer, so parsing needs a defined fallback for missing, unknown and malformed values. - It **multiplies distinct addresses**, which spreads analytics and caching thinner across near-identical views. - It forces you to **define a default for every parameter**, because the shortest form of the address must still render a sensible screen. So the useful shape of the answer is a short list with reasons: identity and refinements of the view go in, because sharing and Back depend on them; transient interface state, secrets and payloads stay out, because reproducing them is respectively pointless, dangerous and wasteful.

  • Where would you keep an unsaved form draft, since it does not belong in the address?
    In per-tab storage keyed by the form's address, restored when the user returns to that screen. The address says which form the user is filling in; the draft contents stay local, so sharing the link never ships half-written text to someone else.
  • Is an open dialog address state or component state?
    It depends on whether it is a destination. A dialog showing an entity the user would link to - a record's detail, a shared preview - is address state, so Back closes it. A confirmation prompt or a menu is transient: nobody wants to bookmark it, and reproducing it from a cold link is confusing.
  • The screen needs a large set of selected identifiers; where does that live?
    Not in the address, once it stops being readable or exceeds practical length limits. Persist the selection server-side or per tab and put a short reference to it in the address, or carry only the criteria that produced the selection, so the link stays short and reconstructs the same set.

The address is a return address, not a filing cabinet: enough for someone else to arrive at the same place, not a photocopy of everything on the desk.

saying these in an interview costs you the question

  • Putting a session token in the query string because it is convenient to pass along
  • Claiming every value a component holds should be mirrored into the address
  • Keeping filters purely in memory, so a shared link opens an unfiltered screen
  • Serializing a whole fetched record into the address instead of its identifier
  • Assuming addresses are private because the address bar sits on the user's machine
  • Treating address values as trusted, with no fallback for missing or unknown ones
open as a page

When designing a screen's address, what do the path, the query string and the fragment each carry?

level: middleimportance: should knowfreq 60%

basics

~20 s

The path carries identity - which screen and which entity. The query string carries refinements of that identity, such as search, filters, sort and page. The fragment carries client-only position or state and is never sent to the server.

open as a page

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%

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.

open as a page

A filter panel keeps its own copy of the filters and also writes them into the address; what goes wrong?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Two copies of one value drift apart. Back and Forward change the address while the panel keeps its stale copy, and code that syncs both directions can loop. Make the address authoritative and derive the displayed values from it.

open as a page