AI Interview Questions · Lesson 3 of 8
Prompt Engineering and Structured Output
Zero-shot to chain-of-thought, ReAct, system prompts, JSON output, prompt injection, templates, versioning and how to debug a prompt that misbehaves.
- Intermediate
- 15 min read
- 11 questions
Before this lessonLesson 2: LLMs and Transformers: How They Actually Work
What you will learn
- Choose the right prompting technique for a task and explain why it works
- Get reliable structured output and defend against prompt injection
- Treat prompts as versioned, tested production artefacts instead of clever strings
Your Progress
0 of 8 lessons 0%
- Lessons0 / 8
- Completed0
- Est. time left~ 2 hours
Create a free account to keep your progress on every device.
Prompting questions look easy and are where many candidates give shallow answers. Interviewers are checking whether you treat a prompt as an engineering artefact: something with requirements, tests, versions, failure modes and a cost. "I would tell the model to be more careful" is not an answer; "I would add three counter-examples, force a JSON schema and add a regression test" is.
A useful frame for every question below: specify, show, structure, verify. Specify the task and constraints, show examples, structure the output, and verify with tests.
The 11 questions in this lesson
- What is prompt engineering, what makes a good prompt, and how is it different from prompt tuning and fine-tuning?
- Explain zero-shot, one-shot and few-shot prompting. When does few-shot help, and how do you make it stable?
- What is chain-of-thought prompting? Explain self-consistency and tree-of-thought, and when reasoning models make them unnecessary.
- What is ReAct prompting and how does it relate to agents?
- What is a system prompt versus a user prompt? How do you handle multi-turn conversations and instruction priority?
- How do you get reliable structured output (JSON) from an LLM?
- What is prompt injection, and how do you defend against it? What is jailbreaking?
- How do you manage prompts in production: templates, versioning, testing and rollout?
- What is prompt chaining? When do you decompose a task, and what are meta-prompts?
- Your LLM ignores instructions, is too verbose, or gives inconsistent answers. How do you debug a prompt?
- How do you optimise prompts for cost and latency?
23. What is prompt engineering, what makes a good prompt, and how is it different from prompt tuning and fine-tuning?
Warm-up
Prompt engineering is designing the text (and structure) that goes into a model so it reliably produces the output you need. It changes behaviour at inference time with no training. A strong prompt usually has these parts:
| Part | What it does | Example |
|---|---|---|
| Role / context | Sets perspective and audience | You are a support agent for a payments company. |
| Task | One clear instruction | Classify the ticket into exactly one category. |
| Inputs | The data to act on, clearly delimited | Ticket text inside <ticket> tags |
| Constraints | Rules, tone, length, what not to do | If unsure, answer UNKNOWN. Max 40 words. |
| Examples | Show the pattern (few-shot) | Two or three input and output pairs, including a hard case |
| Output format | Makes results machine-readable | Return JSON matching this schema |
Follow-up: Your prompt works on your 10 test cases but fails in production. What went wrong with how you tested?
24. Explain zero-shot, one-shot and few-shot prompting. When does few-shot help, and how do you make it stable?
Core
Zero-shot gives only the instruction. One-shot adds a single example; few-shot adds several. Examples teach the model the format, tone and edge-case behaviour in a way that is hard to describe in words, and they are the cheapest way to improve consistency.Few-shot helps most for classification with fuzzy labels, extraction with a specific schema, style transfer and tricky formatting. It helps least when the model already does the task well zero-shot, or when the task needs knowledge the model lacks (examples cannot supply missing facts).
Making it stable: pick examples that cover the decision boundary, not only easy cases; balance the labels so the model does not learn the majority class; keep a consistent format across examples; vary the order in testing because models show recency bias and can copy the last example; pick examples dynamically by similarity to the input (retrieval-based few-shot) for diverse inputs; and keep them short so they do not eat the context window. If results are still inconsistent across near-identical inputs, lower temperature, add explicit rules, and add a validation step.Follow-up: Your few-shot classifier over-predicts the most frequent label. What do you change first?
25. What is chain-of-thought prompting? Explain self-consistency and tree-of-thought, and when reasoning models make them unnecessary.
Core
Chain-of-thought (CoT) asks the model to write intermediate reasoning steps before the final answer ("think step by step", or worked examples that include reasoning). It improves accuracy on arithmetic, logic and multi-step problems because each generated step becomes context for the next; the model effectively gets more compute per problem. It helps little on simple lookups and costs extra tokens and latency.- Self-consistency: sample several independent reasoning paths at a temperature above 0 and take the majority vote of the final answers. It trades cost (N calls) for accuracy and works when answers are short and checkable.
- Tree-of-thought: explore several reasoning branches, evaluate partial progress and backtrack, like a search over thoughts. Useful for puzzles and planning, but expensive and rarely worth it in production.
- Reasoning models (models trained with reinforcement learning to think before answering) already do this internally. Forcing extra "think step by step" instructions on them is usually unnecessary and sometimes harmful; instead give clear goals and let them reason, and pay attention to the reasoning-effort setting for cost control.
import random
from collections import Counter
random.seed(3)
def noisy_solver(question):
# stands in for one sampled chain of thought that is right 60% of the time
return "42" if random.random() < 0.6 else random.choice(["41", "43", "40"])
def self_consistent(question, n=15):
votes = Counter(noisy_solver(question) for _ in range(n))
return votes.most_common(1)[0][0], votes
single_correct = sum(noisy_solver("q") == "42" for _ in range(1000)) / 1000
voted_correct = sum(self_consistent("q")[0] == "42" for _ in range(1000)) / 1000
print("single sample accuracy :", single_correct)
print("majority-of-15 accuracy :", voted_correct)Follow-up: When is self-consistency a bad idea?
26. What is ReAct prompting and how does it relate to agents?
Core
ReAct (Reasoning plus Acting) interleaves a short reasoning step with an action that calls a tool, then feeds the tool's observation back before the next reasoning step. The pattern isThought, Action, Observation, Thought, ... Final answer. It grounds the model in real data (search results, database rows, calculator output) instead of guesses and makes its behaviour inspectable.
Almost every modern agent framework is a productionised ReAct loop, with tool calls made through the provider's native function-calling API instead of parsing text. The failure modes to name: infinite loops (so cap the steps), tools returning long noisy output (truncate and summarise), the model inventing a tool or argument (validate against a schema), and cumulative context growth (compact or summarise old steps).
Follow-up: How do you stop a ReAct agent from looping forever on a failing tool?
27. What is a system prompt versus a user prompt? How do you handle multi-turn conversations and instruction priority?
Core
A system prompt is set by the developer and defines persistent behaviour: role, rules, tone, tool policy, output format. The user prompt is the changing request. Chat APIs give them different roles so the model can weight the developer's instructions above the user's, a hierarchy often called the instruction hierarchy. It is a strong tendency, not a security boundary, which is why prompt injection remains possible.
Multi-turn: the model is stateless, so every request carries the conversation. As it grows, apply a budget: keep the system prompt and the last few turns verbatim, summarise older turns, store durable facts (user preferences, decisions) in a separate memory that is re-injected, and retrieve past turns only when relevant. Watch for topic switches (old context can mislead the model) and re-state key constraints near the end of long prompts because attention to the middle weakens. Do not put secrets or business logic you cannot afford to leak in the system prompt: users can and will extract it. Treat it as visible, and enforce anything security-critical in code.Follow-up: Your chatbot loses track of the user's earlier requirements after 15 turns. Describe your memory design.
28. How do you get reliable structured output (JSON) from an LLM?
Core
Use layers, from weakest to strongest guarantee:
- Ask clearly: show the schema and an example, say "return only JSON". Works most of the time, fails silently the rest.
- Function calling / tool schemas: declare a JSON Schema for the arguments; the model returns structured arguments in a dedicated field.
- Structured outputs / constrained decoding: the provider or inference engine masks tokens that would break a grammar or schema at every step, so output is syntactically valid by construction. Open-source engines offer the same with grammar-based decoding.
- Validate and retry: always parse and validate (for example with Pydantic or JSON Schema); on failure, retry with the error message so the model can repair its output, with a small retry cap and a safe fallback.
Constrained decoding guarantees the shape, not the truth: a perfectly valid JSON can still contain a wrong value, so add semantic checks (ranges, enums, cross-field rules) and keep fields simple; deeply nested schemas and very long outputs raise error rates. Put enumerations in the schema instead of the prose, and give the model a place to say "unknown".
import json
def fake_model(prompt, attempt):
# first attempt is sloppy, the retry (which sees the error) is correct
if attempt == 0:
return "Sure! Here you go: {'name': 'Ada', age: 36}"
return '{"name": "Ada", "age": 36}'
def validate(text):
data = json.loads(text) # raises on bad JSON
assert set(data) == {"name", "age"}, "wrong keys"
assert isinstance(data["age"], int) and 0 < data["age"] < 130, "bad age"
return data
def extract(prompt, max_retries=2):
error = None
for attempt in range(max_retries + 1):
p = prompt if error is None else prompt + "\nYour last output was invalid: %s. Return only valid JSON." % error
raw = fake_model(p, attempt)
try:
return validate(raw), attempt
except Exception as e:
error = str(e)
raise ValueError("gave up: " + error)
print(extract("Extract name and age from: Ada is 36."))Follow-up: Constrained decoding guarantees valid JSON. Why is that still not enough?
29. What is prompt injection, and how do you defend against it? What is jailbreaking?
Core
Prompt injection is when text the model reads contains instructions that override or hijack the developer's intent. Direct injection: the user types "ignore previous instructions". Indirect injection is the dangerous one: the instructions hide in content the system fetches (a web page, an email, a PDF, a tool result), and the model obeys them. It is the LLM equivalent of SQL injection, except that instructions and data travel in the same channel, so there is no perfect escaping. Jailbreaking is a related but different goal: tricking the model into violating its safety policy (role-play framings, encoding tricks, multi-turn escalation). Injection attacks the application; jailbreaks attack the model's alignment.There is no complete fix, so design as if the model will be fooled. Practical rules: limit blast radius (least-privilege tools, read-only by default, scoped tokens, no ability to send data out); break the "lethal trifecta" of private data access, exposure to untrusted content and an exfiltration channel, because having all three in one agent is how data leaks; require human approval for irreversible or external actions; mark untrusted content and tell the model it is data, not instructions (helpful, not sufficient); validate outputs and never execute model-produced code or SQL without sandboxing; and red-team continuously.
Follow-up: Your agent reads emails and can send emails. Describe an indirect-injection attack and two controls.
30. How do you manage prompts in production: templates, versioning, testing and rollout?
Core
Treat prompts like code. Store them in version control (or a prompt registry) as templates with named variables, never as strings scattered through the application. Give each a version, an owner and a changelog. Every prompt change goes through the same pipeline as a code change: run it against a regression set of real and adversarial inputs, compare metrics against the current version, then release gradually (canary or A/B) with the ability to roll back instantly.
- Testing: a golden dataset with expected outputs or rubric scores; automatic checks (schema validity, banned phrases, length) plus an LLM-as-judge for quality, calibrated against human labels. Re-run it on every model upgrade, because prompts are model-specific.
- Observability: log the prompt version, model, parameters, token counts and outcome for every request so a bad answer can be traced to a change.
- Safety: escape or delimit user-supplied variables so they cannot break out of the template.
import hashlib
from string import Template
PROMPTS = {
("summarise", "v2"): Template(
"You are a careful editor.\n"
"Summarise the text between <doc> tags in at most $words words for $audience.\n"
"If the text has no clear content, reply exactly: NO_CONTENT.\n"
"<doc>$doc</doc>"
),
}
def render(name, version, **vars):
text = PROMPTS[(name, version)].substitute(**vars) # KeyError if a variable is missing
fingerprint = hashlib.sha256(text.encode()).hexdigest()[:8]
return text, {"prompt": name, "version": version, "fingerprint": fingerprint}
text, meta = render("summarise", "v2", words=40, audience="executives", doc="Q3 revenue grew 12 percent...")
print(text)
print(meta) # log this with every requestFollow-up: A model provider ships a new version and your accuracy drops 4 points with no prompt change. What now?
31. What is prompt chaining? When do you decompose a task, and what are meta-prompts?
Core
Prompt chaining splits a complex job into several simple calls where each output feeds the next: extract, then classify, then draft, then critique. Reasons to chain: each step is easier to test and debug; you can use a small cheap model for easy steps and a large one only where needed; you can insert deterministic code, validation or human review between steps; and long tasks stay inside the context window. The costs are latency (sequential calls), more moving parts, and error accumulation, so measure each step's accuracy, not just the final answer.Rules of thumb: prefer one good prompt for simple tasks; chain when the task has distinct stages, needs different tools or models per stage, or when a single prompt becomes long and contradictory. A router (classify the request, then pick a specialised prompt or model) is the most common chain in production. Parallel calls (map-reduce style summarisation over chunks) cut latency for independent sub-tasks.
Meta-prompts are prompts that write or improve prompts: giving a model your task, failing examples and asking it to propose a better prompt, or to generate diverse test inputs. Useful for bootstrapping and for automatic prompt optimisation, but every generated prompt still needs to pass your tests; do not accept it because it sounds good.Follow-up: Your three-step chain is 92% accurate at each step. What is the end-to-end accuracy, and how do you improve it?
32. Your LLM ignores instructions, is too verbose, or gives inconsistent answers. How do you debug a prompt?
Core
Debug prompts like software: reproduce, isolate, change one thing, measure. My checklist:
- Collect failing cases and turn them into a small test set; note the failure type (ignored constraint, wrong format, hallucination, refusal).
- Read the whole rendered prompt as the model sees it. Look for contradictions, buried instructions, missing context, or user data that overrides the rules.
- Make instructions specific and positive: "Answer in at most three bullet points" beats "be concise"; say what to do, not only what to avoid; state the priority when rules conflict.
- Move critical rules to the start and repeat them briefly at the end for long prompts; use delimiters or XML tags to separate instructions from data.
- Add examples of the desired behaviour, including a counter-example for the mistake you keep seeing.
- Constrain output with a schema or max tokens; lower temperature for consistency.
- Split the task if one prompt is doing too much; add a verification pass for high-stakes outputs.
- Re-run the whole test set after each change so a fix for one case does not break another, and try a stronger model to tell whether the problem is the prompt or the model's capability.
For non-English users add multilingual examples or instruct the model to answer in the user's language; do not assume an English-tuned prompt transfers.
Follow-up: The model follows your format 95% of the time. How do you get the last 5%?
33. How do you optimise prompts for cost and latency?
Core
Cost is roughly input tokens x input price + output tokens x output price, and output tokens are both pricier and slower (decoding is sequential). So the levers, in the order I would pull them:
- Cut output length: ask for concise formats, cap
max_tokens, return codes or labels instead of prose. - Prompt caching: providers discount and speed up repeated prompt prefixes. Put static content (system prompt, tools, few-shot examples, reference documents) first and variable content last so the prefix stays cache-friendly.
- Shrink the input: fewer, better retrieved chunks; strip boilerplate; summarise history; compress examples.
- Route by difficulty: a small model for easy requests, a large one only when needed, with a fallback on low confidence.
- Parallelise independent calls; stream so the user sees the first token early (perceived latency); batch offline workloads with discounted batch APIs.
- Cache answers for repeated or semantically similar questions, and set budgets and alerts per feature.
Measure with a fixed benchmark so savings are not paid for with a silent quality drop: report cost per successful task, not cost per call.
Follow-up: Your prompt has a 6,000-token static preamble. How does the position of the user's question in it affect cost?
Sources and further reading
- Wei et al., Chain-of-Thought Prompting Elicits Reasoning
- Wang et al., Self-Consistency Improves Chain of Thought
- Yao et al., ReAct: Synergizing Reasoning and Acting
- Yao et al., Tree of Thoughts
- OWASP Top 10 for LLM Applications
- AI Engineering interview questions (Outcome School, Apache-2.0)
