A route pattern was renamed and hand-concatenated links broke only when users clicked them — how do you generate links from the route table instead?
answer
- the pattern lived in more than one place
- matching read backwards
- identity, not path, at the call site
- required placeholders become required arguments
- walk the table in a test
basics
~20 sGive every entry an identity and build paths through the table: name the entry, supply its required parameters, let the helper encode them. A rename breaks the definition and every stale call site at build time, not at click time.
solid answer
~50 sHand-built paths duplicate the pattern in every call site, so the table stops being the single source of truth and a rename can only be caught by clicking. The fix is **reverse lookup**: the table is a value the app can read back, so each entry gets a stable identity, and a helper turns that identity plus parameters into a path. Three things follow. The pattern's required parameters become the helper's required arguments, so a missing or renamed one is caught by a build check rather than by a user. Encoding happens in one place on the way out, so a value containing a separator or an ampersand cannot corrupt the path. And the pattern exists exactly once, so changing a URL is a one-line edit. Back it with a test that walks every entry and asserts each generated link matches a pattern in the table.
go deeper
Build links through the app's link helper rather than by joining strings, and pass values as parameters so they are encoded for you.
Explain link generation as matching read backwards, and why required placeholders becoming required arguments is what moves the failure from click time to build time.
Show the guardrails: one generator, a rule against literal paths, a test that round-trips every entry, a redirect entry for the old pattern, and not-found logging to catch the rest.
Treat the set of patterns as a published surface with a change policy: what may be renamed, how long an old path is honoured, and how a URL change is reviewed like an interface change.
## Why the failure was invisible When a path is assembled from fragments at the call site, the pattern is copied into the codebase as many times as there are links to it. Nothing connects those copies to the entry in the route table. Renaming the entry therefore compiles, passes a type check, passes every test that does not click, and ships. The first thing that notices is a user clicking a link that now leads to the not-found screen. The defect is not the rename; it is that the table was not the only place the pattern lived. ## The table as a value you can read back A route table is data. Matching reads it in one direction: path in, entry and parameters out. **Link generation is the same data read backwards**: entry and parameters in, path out. Once you accept that, the design writes itself: - every entry carries a **stable identity** that is not its path, so the path can change without the identity changing; - a **generator** takes that identity plus the values for the pattern's placeholders and returns the path; - the generator **encodes** each value into the segment it fills; - optional positions are optional arguments; a catch-all takes the list of leftover segments; - anything that is not a path placeholder — a filter, a sort — is appended as a query by the same helper, so there is one place that knows how a link is shaped. ## What that buys 1. **Renames become one-line edits.** Change the pattern on the entry; every link follows, because no one else knows the path. 2. **Failures move earlier.** Missing or misspelled parameters, or a removed entry, break the call site. In a typed codebase the parameter names come from the pattern, so a stale call site stops building; even without types, one generator is a single place for a test to catch a missing value. 3. **Encoding is not optional any more.** Hand-concatenation is where a value containing a separator or a question mark silently becomes part of the path structure. A generator that encodes per segment cannot produce that bug. 4. **The URL surface becomes inventoriable.** You can print every pattern, diff that list between releases, and see exactly which public URLs a change alters. 5. **Redirects get cheap.** When you must change a path you actually care about, keep the old pattern as an entry that redirects to the generated new one, and the rest of the app is untouched. ## Keeping it honest A generator only helps while it is the only way to build a link. Guardrails that work: - a **lint or review rule** against literal path strings outside the table and its generator, with a narrow allowance for external URLs; - a **test that walks the table**, generates a link for every entry with sample parameters, and asserts each generated path matches that same entry when fed back through matching — this catches a pattern that can be described but never reached; - a **test for the reverse direction** on the paths you have promised publicly, so a rename that would break bookmarks fails a test instead of surprising users; - **not-found instrumentation** that records the path, so anything you still missed shows up as a cluster on one URL shape rather than as silence. ## Where generation stops Be clear about the edges, or the rule gets broken quietly: | Case | Belongs to the generator? | |---|---| | An in-app link to a known entry | yes, always | | A link that also carries filters or a sort | yes, with the query appended by the helper | | A path a person may paste into a message | yes, generated then made absolute | | A third-party or external URL | no; it is not in your table | | A path handed to you by an API response | no; treat it as data and validate it before following | ## Traps - Naming the identity after the path, so renaming the path renames the identity and the indirection buys nothing. - A generator that accepts a loose bag of values, so a missing placeholder silently produces a path with an empty segment. - Encoding twice — once at the call site and again in the generator — which produces visibly mangled links. - Keeping one hand-built link in the shell or in an email template, which is usually the one that breaks. - Treating the old path as free to delete on the day of the rename, when links people saved still point at it.
- How do you keep the old URL working after renaming a pattern?Keep the old pattern in the table as an entry that redirects to the generated new path, carrying its parameters across. That way saved links and shared messages still land, the redirect is visible in the table as a deliberate decision, and its traffic tells you when it is finally safe to remove.
- What does a test that walks the whole route table actually assert?For each entry: generate a path from its identity and sample parameters, then feed that path back through matching and assert the same entry wins with the same parameters. Round-tripping catches patterns that can be generated but never matched, placeholders the generator forgets, and encoding that changes a value on the way out.
- Is a generated link enough to stop a broken link at click time?No. Generation guarantees the path still exists as a pattern; it cannot guarantee the thing the parameter names is still there. That case is the route entry's load step resolving to not-found. Generation removes the class of breakage caused by stale patterns, not the class caused by deleted records.
saying these in an interview costs you the question
- Copies the path pattern into every call site that links to it
- Names the entry's identity after its current path
- Relies on clicking through the app to find broken links
- Concatenates raw parameter values into a path without encoding
- Encodes at the call site and again in the generator, mangling the link
- Deletes the old pattern the day it is renamed, breaking saved links