Meet Modi
Back to Blog
·5 min

The switch statement that blamed your Wi-Fi for a decision the server made on purpose

Users kept being told to check their internet connection. Their internet connection was fine. The bug: a switch statement built for 3 error codes was still running the show after the backend's real list grew to 6.

By Meet Modi
Error HandlingAPI ContractsFrontend

Support tickets started clustering around one message: check your internet connection. The users filing them were on stable Wi-Fi, mid-conversation, sending the ticket from the same device seconds after seeing the error.

The request had gone out fine. A response had come back fine. The server had looked at the request and said no, deliberately, for a specific reason. None of that reached the screen.

The bug: a switch statement recognized 3 rejection codes. The backend could send at least 6. Everything past the 3rd fell into a default case that assumed the only reason a request could fail was the network.

The switch statement as originally written

The accept-a-challenge flow handled the cases that existed when it was written: the challenge was already accepted, the challenge was no longer active, the user wasn't a participant. Reasonable coverage, at the time.

function getAcceptErrorMessage(code: string): string {
  switch (code) {
    case "ALREADY_ACCEPTED":
      return "You've already accepted this challenge.";
    case "CHALLENGE_INACTIVE":
      return "This challenge is no longer active.";
    case "NOT_A_PARTICIPANT":
      return "You're not part of this challenge.";
    default:
      return "Check your internet connection and try again.";
  }
}

The backend's rejection codes grew over time. It started sending SELF_CHALLENGE when a user tried to accept their own challenge, CHALLENGE_FULL when capacity was reached, and QUIZ_GENERATION_FAILED when the underlying quiz couldn't be built server-side. The switch statement never grew with it.

Call sequence for the failure: user taps accept, request succeeds, server returns 200 with code: "CHALLENGE_FULL" in the body, client parses the code, switch statement doesn't recognize it, falls to default, shows a network error. Nothing about that path touches the actual network layer. The message is just wrong.

Why a default case is dangerous here

A default case in a switch over a fixed error enum makes sense when the enum is genuinely closed and every value is enumerated. It stops making sense the moment the enum is owned by a different service that can add values without coordinating a frontend release first. The default silently became the catch-all for every future code, whether or not it had anything to do with connectivity.

The default case wasn't wrong when it was written. It became wrong three backend releases later, without a single line of frontend code changing.

The fix: cover the actual list

function getAcceptErrorMessage(code: string): string {
  switch (code) {
    case "ALREADY_ACCEPTED":
      return "You've already accepted this challenge.";
    case "CHALLENGE_INACTIVE":
      return "This challenge is no longer active.";
    case "NOT_A_PARTICIPANT":
      return "You're not part of this challenge.";
    case "SELF_CHALLENGE":
      return "You can't accept your own challenge.";
    case "CHALLENGE_FULL":
      return "This challenge has reached its participant limit.";
    case "QUIZ_GENERATION_FAILED":
      return "We couldn't generate this quiz. Try again in a moment.";
    default:
      return "Something went wrong on our end. Please try again.";
  }
}

The default case is still there, but now it's a genuine fallback for an unrecognized code, not the primary handler for half the real ones. And it no longer claims to know the cause is the network, since past this point we genuinely don't know what happened.

What I'd do differently

I'd log the raw, unrecognized code whenever the default case fires, instead of just showing a generic message. That alone would have surfaced CHALLENGE_FULL in our error tracking the week the backend shipped it, instead of three months and a pile of confused support tickets later.

The deeper habit I'm changing: when I add a new error case to a backend enum, I now search the frontend for every switch statement over that enum's type, not just the one screen I was testing against. This one had two more call sites with the identical stale list.

More Posts