skip to content

After a second renderer is registered, one unchanged route starts returning a different representation to some clients. How do you diagnose it?

level: seniorimportance: should knowfreq 44%

answer

  1. the route is local, the registry is global
  2. capture the header the server received
  3. some clients means header shapes differ
  4. log candidates and the winner
  5. constrain the route, do not reorder

basics

~20 s

Capture the Accept header the server actually received, then dump the candidate renderers and the winner for that route. The newcomer either entered an undeclared route's candidate set or outranked the incumbent for those clients' headers.

solid answer

~50 s

Work from the request inward, not from the handler outward. First capture the `Accept` header **as the server saw it** — intermediaries add and rewrite it, so the header the client sent may not be the header that arrived. Second, dump the registry: what the new renderer declares it produces, at what priority, and whether it claims a wildcard. Third, log the candidate set and the winner for the failing route. The usual causes are a renderer that produces a wildcard or a very broad type and sorts ahead of the incumbent, or a route that declares no produced types and so admitted the new renderer into its candidate set for free. The fix is almost always to declare produced types on the affected routes and make priority explicit, rather than to reorder registration and hope.

go deeper

for a junior

The takeaway is that the format a route returns depends on configuration shared by the whole application, so a route can change behaviour without anyone editing it.

for a middle

Explain how a newly registered renderer enters a route's candidate set: the route declared no produced types, or the renderer's declared types are broad enough to match.

for a senior

Demonstrate the order — received header first, registry dump second, decision logging third — and choose a fix that bounds the route rather than reordering global registration.

for a principal

The systemic question is whether representation is allowed to be implicit at all. Requiring declared produced types costs boilerplate on every route and buys you a registry whose additions cannot change existing behaviour.

## Why an unchanged route changes behaviour Renderer selection is a function of three things, and only one of them lives next to the handler. The route's declaration is local; the **registry** and the **incoming request headers** are global. A registration made anywhere in the application — including inside a dependency that self-registers — re-decides selection for every route that did not constrain itself. So 'the route did not change' is true and irrelevant. The qualifier *some clients* is the second clue. If every client flipped, the registry alone explains it. If only some did, their `Accept` headers differ from the rest, and the new renderer wins the ranking only for that shape of header. ## The diagnostic order 1. **Capture the received `Accept`.** Log it on the failing route, from the server's view of the request. Proxies, gateways and clients' own libraries add defaults, so the header that arrived and the header the client believes it sent are different questions. 2. **Reproduce with that exact header**, not with a convenient one. A request with no `Accept` takes the default path and will not reproduce a ranking bug. 3. **Dump the registry at startup** — every renderer, its declared produced types, and its priority. This is the single highest-value piece of instrumentation for this class of incident, and it costs a few lines. 4. **Log the decision, not just the outcome:** the candidate set after narrowing, the ranked acceptable types, and the renderer chosen. An outcome tells you what happened; the candidate set tells you *why it was eligible*. 5. **Check the route's declaration.** No declaration means the route accepted the registry's whole universe. 6. **Diff the registration site**, including transitively pulled-in configuration, not only your own code. ## The usual causes | Cause | Signature | Fix | |---|---|---| | New renderer declares a wildcard or very broad produced type | Every route with no declaration flips, for many header shapes | Narrow the renderer's declared types; declare produced types per route | | New renderer sorts ahead on equal rank | Only clients whose header ranks both types equally flip | Set explicit priorities instead of relying on registration order | | Route declares nothing | The route was never constrained and silently gained a representation | Declare produced types on the route | | Header rewritten in transit | Only clients behind one hop flip | Fix the hop, and log the received header to prove it | | New renderer handles a value type the old one could not | The flip tracks the returned value's shape, not the client | Constrain by value type or by route declaration | ## Things that mislead - **Testing with a browser.** A browser sends a rich markup-first header, so it exercises a different ranking than the affected clients do. Reproducing 'fine in the browser' proves nothing. - **Assuming a wildcard-producing renderer is harmless.** A renderer that declares it can produce anything matches every request, so its position in the registry becomes the whole decision for unconstrained routes. - **Reordering registration as the fix.** It works until the next registration lands. Ordering that matters should be explicit priority, not source-file sequence. - **Changing the route's declaration before reproducing.** Once you narrow the route the symptom vanishes, and you have lost the evidence for whether the registry or a rewritten header was the cause. - **Reading the response's `Content-Type` as proof of intent.** It tells you which renderer ran, not why it was chosen. ## What to leave behind A fix that prevents recurrence has three parts: 1. **Declared produced types** on every route that is meant to speak one format, so selection is bounded locally. 2. **Explicit priorities** in the registry, so that adding a renderer cannot reorder the incumbents. 3. **A test that pins the representation** for the header shapes real clients send — including the no-`Accept` case, which is the one test suites usually skip because their client sets a header by convention. ## What interviewers listen for They want the instinct to capture the received header before theorising, the knowledge that the registry is global state while the route is local, and a fix that constrains rather than reorders. A candidate who immediately says 'I'd log the candidate renderers and the winner' has debugged this before.

  • Only clients behind one gateway are affected. What does that point at?
    An intermediary rewriting or supplying `Accept`. Gateways, service meshes and client libraries all add defaults, so the ranking the server performs uses a header the client never sent. Prove it by logging the header as received on the failing route and comparing it with what the client emits; fix it at the hop, not in renderer configuration.
  • Why is reordering the registry a weaker fix than declaring produced types on the route?
    Because ordering is global and shared. Reordering restores the old winner for every route at once and stays correct only until the next registration lands — including one inside a dependency. A route that declares what it produces is bounded locally: no future registration can add a representation to it, and the constraint is visible next to the handler.
  • The response's Content-Type shows the new type. Does that identify the cause?
    No — it identifies the winner, not the reason it won. The renderer could have been eligible because the route declares nothing, ranked first because the client's header weights both types equally, or matched because it declares a wildcard. You need the candidate set after narrowing and the ranked acceptable types to tell those apart.

saying these in an interview costs you the question

  • Blames the client without capturing the Accept header the server received.
  • Assumes no proxy or gateway ever rewrites or adds Accept.
  • Believes a renderer declaring a wildcard can never outrank a specific one.
  • Reorders registration as the fix instead of constraining the route.
  • Reproduces in a browser, whose header shape differs from the affected clients.
  • Changes the route declaration before reproducing, destroying the evidence.