One skipped transform, two unrelated crashes
A quiz report showed every answer as wrong. A carousel on the same page threw and took the whole surface down with it. Same root cause: a call site that skipped a step every other call site took for granted.
By Meet ModiA report screen showed every question as incorrect. Not most, all of them, for every user who opened it. In the same release, the page itself started crashing outright for a subset of users, before the report even had a chance to render wrong.
Two bug reports, filed an hour apart, that looked unrelated enough to get assigned to two different people.
The bug: one call site fetched data and handed it straight to the UI, skipping the transform every other call site in the app used without exception.
Symptom 1: the field that didn't exist
The UI read a field called correctAnswer. Camel case, matching every other model in the frontend. The API returned correct_answer, snake case, matching every other response from the backend. Every other screen in the app ran responses through a shared transform before rendering. This one didn't.
// what every other call site does
const report = toFrontendModel(await fetchQuizReport(quizId));
// what this call site did
const report = await fetchQuizReport(quizId);
renderReport(report); // report.correctAnswer is undefined, alwaysreport.correctAnswer was undefined for every question, because the object only had correct_answer. The comparison against the user's selected answer failed for every single question, so the report rendered as if the user had missed all of them, regardless of what they'd actually picked.
It's a quiet failure. No thrown exception, no red screen. undefined !== selectedAnswer is just always true, so the report looked plausible enough to ship past a casual glance.
Symptom 2: the carousel with nothing in it
A carousel on the same page took a prop saying it was enabled, alongside a list of items to show. On most days that list has entries. On days with no promotional cards to show, a state the product genuinely has to handle, not an edge case, the list was empty, and the carousel was still told it was enabled.
function getActiveSlideIndex(items: Item[], elapsedMs: number) {
const slideDuration = 4000;
return Math.floor(elapsedMs / slideDuration) % items.length; // items.length can be 0
}Modulo by zero in JavaScript returns NaN, not a thrown error by itself, but the index then got used to access items[NaN], which is undefined, and the render logic downstream assumed a real item and dereferenced a property on it. That threw, uncaught, and took the parent page component down with it.
Two symptoms, same shape of mistake: something upstream assumed a precondition (a transformed object, a non-empty list) that nothing enforced.
The fix for both
Route the report call through the same transform as everywhere else:
const report = toFrontendModel(await fetchQuizReport(quizId));And guard the carousel before dividing into a list that might be empty:
function getActiveSlideIndex(items: Item[], elapsedMs: number) {
if (items.length === 0) return -1; // caller treats -1 as "render nothing"
const slideDuration = 4000;
return Math.floor(elapsedMs / slideDuration) % items.length;
}What I should have done first
I should have made the transform impossible to skip, by having fetchQuizReport return the frontend model directly instead of exposing the raw response as a separate importable function. The shared step only stayed shared by convention, and conventions get skipped under deadline pressure by whoever hasn't memorized every step yet.
I also should have noticed that both bugs shipped in the same PR review. If I'd asked why this one call site looked different from the six others touching the same endpoint, I'd have caught it before either bug reached a user.
More Posts
The vector database was innocent
I built a RAG service over the OpenTelemetry docs, then pointed OpenTelemetry back at it to find out why answers took 16 seconds. It wasn't the LLM. It wasn't the vector search either.
A query that returned nothing nested inside it
The exact same logical query returned full nested data through the REST layer and empty shells through the service layer underneath it. Same fields requested. Same backend. One shorthand parameter that only one of the two actually understood.
One field, three different envelopes, and a fix that made it worse
The same subscription-status field came back nested two levels deep, one level deep, or bare at the top, depending on account state. My fix didn't catch that. It added math on top of a check that was already wrong.