A client blanks the page whenever a GraphQL response has any errors entry — what would you change?
answer
- Errors are positional, not a verdict
- A habit borrowed from status-code thinking
- Attribute each error, then degrade that region
- A stale fallback beats no fallback — wrong
- Alert on rate per path, not presence
basics
~20 sStop treating the errors list as a verdict on the whole response. Render everything data resolved and use each error's path to degrade only the region over that hole. Reserve a full failure screen for a body with no usable data.
solid answer
~50 sA non-empty errors list means "these positions failed", not "this request failed", so an all-or-nothing branch discards every field the server resolved — and usually replaces it with something worse, such as a stale local snapshot the user cannot tell is stale. The change is a per-path policy in the boundary layer that turns a response into view state: keep `data`, index the errors by `path`, and hand each screen region both its slice of the tree and the errors landing inside it. A region with a clean subtree renders normally; a region over a hole renders the values that survived plus an explicit unavailable marker; a null with no error under it renders an empty state. Only a body with no usable data earns a whole-page failure. Log the paths, not just the messages, so a field that degrades on every request is visible.
code
json · 16 lines{
"data": {
"term": {
"enrollments": [
{ "id": "ENR-0001", "section": { "code": "STAT-101-A", "seatsRemaining": 12 } },
{ "id": "ENR-6118", "section": { "code": "BIO-240-C", "seatsRemaining": null } }
]
}
},
"errors": [
{
"message": "Seat service timed out",
"path": ["term", "enrollments", 6117, "section", "seatsRemaining"]
}
]
}go deeper
Know that data and errors arrive together and that discarding the body is not the default. If you meet this branch in a codebase, be able to say what it costs rather than assuming it is correct.
Explain the mechanics of the fix: index the errors by path, match each region against those paths by prefix, and choose between a normal render, a degraded render and an empty state. Say why a fabricated default is worse than a gap.
Show that you have shipped this: where the policy lives, how regions map onto response paths, which fields are exempt because a partial value would mislead, and how per-path telemetry keeps a permanently degraded field from going unnoticed.
Own it as a cross-team standard — one boundary layer rather than per-screen ad hoc handling, an agreed rule for which fields may degrade, and the escalation path when a dependency's failure rate makes a nullable field effectively unusable in the product.
### The habit, and where it comes from Most fetch layers are written against a protocol where a response is either a payload or a failure, never both. That instinct carries over as a single branch at the top of the client's parsing code: `if (response.errors) { showFailure(); return; }`. Under GraphQL that branch is a category mistake. A non-empty `errors` list means *these specific positions failed*; it says nothing about the rest of the tree, which the server executed, resolved, serialized and paid for. The all-or-nothing branch throws all of it away. ### What it costs, concretely Consider a term dashboard over an enrolment graph. The advisor view pages a term's enrolments — a page of 8,400 rows on the largest term — and each row shows a section code, a status, and a remaining seat count sourced from a separate seat service. That service times out on three rows. The server does exactly what it should: 8,397 rows come back complete, three carry `null` in `seatsRemaining`, and the errors list holds three entries with paths like `["term", "enrollments", 6117, "section", "seatsRemaining"]`. The client's top-level branch sees a non-empty errors list, discards the body, and falls back to the last snapshot it kept locally. Advisors now see a full, confident, entirely stale table — seat counts from the previous night's load, presented with no visual difference from live data — and they enrol students into sections that filled hours ago. The failure of three fields became a wrong answer on 8,400 rows. This is the specific reason the blank-screen habit is worth an interview question: its usual sibling is not a blank screen but a *plausible* screen built from stale data. ### The change: attribute, then degrade per region Move the decision from the whole response to the individual position, in the boundary layer that turns a response body into view state: 1. **If `data` is absent, fail the page.** Execution never produced a tree; there is nothing to render and the errors are about the request itself. 2. **Otherwise keep `data` and index the errors by `path`.** Split off entries with no path — they are not attributable to any position and belong in a page-level banner, not a widget. 3. **Give each screen region its slice plus the errors landing inside it.** A region rooted at response position `P` owns every error entry whose path starts with `P`. 4. **Render three ways.** Clean subtree: render normally. Subtree with errors: render the values that survived and an inline, explicitly labelled unavailable state over the hole. Null with no error in its subtree: that is a legitimate empty value, render the empty state, not an error. 5. **Never substitute.** A `0` where a seat count failed reads as "full"; a cached value with no staleness label reads as current. If you fall back, label it. ``` function present(response, page): if response.data is absent: return page.renderWholeFailure(response.errors) attributed = groupByPath(response.errors) # entries that carry a path unattributed = response.errors without a path for region in page.regions: slice = read(response.data, region.path) regionErrs = attributed.pathsStartingWith(region.path) if regionErrs is not empty: region.renderDegraded(slice, regionErrs) else if slice is null: region.renderEmptyState() else: region.render(slice) if unattributed is not empty: page.showBanner(unattributed) ``` ### Where partial rendering is the wrong answer Per-region degradation is a default, not a law. Some values are misleading when incomplete rather than merely incomplete: a total, a balance, a count that drives a decision. If the seat-count column is the entire point of the table, showing 8,397 numbers and three gaps is fine; showing a *summed* "seats remaining this term" computed over a page with three holes is not — that number is silently wrong and nothing on screen says so. Decide per field whether a gap is acceptable, and where it is not, suppress the derived value explicitly rather than computing it over partial input. ### Make the degradation visible to you, not just to the user A partial render is a quiet failure by construction: the user sees a small marker, and nobody is paged. That is the intended tradeoff, but it needs a counterweight in telemetry. Record a metric keyed by the error `path` (with list indices stripped, so the cardinality stays bounded) rather than by the message text, and alert on the **rate and duration** of failures at a path, not on the mere presence of an errors entry. A field that has degraded on every request for two days is an outage that the partial-render policy is hiding; a field that degrades on 0.1% of requests is the policy working. Without the per-path metric you cannot tell those apart. ### What a strong answer sounds like State the reframe first — errors are positional, not a verdict — then describe the boundary that attributes each error to a region, then name the two traps: fabricated substitutes, and unlabelled stale fallbacks that turn a visible gap into an invisible wrong answer. Finish with the observability rule, because that is what makes graceful degradation safe to ship rather than a way to hide a broken dependency.
- When is refusing to render a partial response the right call?When the gap makes the output wrong rather than merely incomplete. A list with three unavailable cells is honest; a total, balance or count summed over that same list is silently short and nothing on screen says so. Decide per field: where a derived value cannot tolerate holes, suppress the value explicitly instead of computing it over partial input.
- What is wrong with falling back to the last cached copy when one field errors?It converts a visible gap into an invisible wrong answer. The user sees a complete, confident screen with no indication that the numbers are hours old, and acts on them. If you fall back at all, fall back only for the affected positions and label the value as stale with its age, so the staleness is part of what is rendered.
- How do you keep graceful degradation from hiding a permanently broken field?Emit a metric keyed by the error path with list indices stripped, so cardinality stays bounded, and alert on rate and duration rather than on any entry appearing. A path failing on a fraction of requests is the policy working; the same path failing on every request for days is an outage the policy is concealing, and it should page someone.
saying these in an interview costs you the question
- Treats a non-empty errors list as a failed request
- Falls back to a stale copy without labelling it
- Replaces the page with a raw error message
- Alerts whenever any errors entry appears
- Renders zeros or defaults where a field errored
- Retries the whole operation for one failed field