Lesson 10 of 30
A model's raw output is free-form text — but your code usually needs a specific, parseable shape it can actually run logic against. Getting from one to the other reliably is its own problem, with solutions that differ a lot in how much you can trust them.
11 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
Why is provider-enforced schema output considered more reliable than simply prompting the model to 'respond in JSON'?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
An LLM generates free-form text by default — even when you ask for JSON in the prompt, what comes back is still fundamentally a text completion, and text completions can go wrong in text-shaped ways: an extra sentence before the JSON starts, a trailing comma, a field renamed to something close-but-not-exact. That's a real problem the moment your code needs to do something programmatic with the response — parse a field, insert it into a database, render it into a specific UI shape — rather than just display it as prose.
There are three real approaches, and they differ a lot in how much you can trust them:
Treat schema-enforced output as the real fix whenever the provider supports it. Defensive parsing (option 2) is the honest fallback for cases where it isn't available or reliable enough on its own — not a substitute for it when a stronger option exists.
// app/api/ai/interview/route.ts — the interview grader asks for strict
// JSON, but doesn't trust that blindly; it parses defensively and falls
// back to a safe default instead of crashing or passing bad data through.
const parsed = parseInterviewVerdict(out.content);
let result: EvalResult = parsed ?? {
verdict: "gap",
missingConcept: null,
followUp:
"I couldn't read that evaluation — the model didn't answer in the " +
"expected format. Try rephrasing your answer, or pick a different " +
"model in Settings.",
};
This is option 2 in practice, done honestly: the system prompt in lib/ai/prompts/interview.ts asks the model for strict JSON, but the route never assumes that request was honored. parseInterviewVerdict attempts to parse the response, and if that fails, the code falls back to treating the response as a "gap" verdict — a safe, defined default — instead of letting a malformed response crash the request or silently propagate broken data into the UI. This is worth internalizing as a pattern on its own: whenever a prompted-JSON approach is your only option (a provider without schema enforcement, a model that doesn't reliably honor it), the fallback path is not optional polish, it's the difference between a graceful degradation and a production incident.
try/catch that swallows the error. Catching a parse failure and then doing nothing with it just moves the crash somewhere less visible."How do you make sure an LLM response is actually usable as data in your application?" tests whether a candidate reaches for real reliability mechanisms — schema enforcement, defensive parsing with a defined fallback — instead of assuming a prompt instruction alone is sufficient.