Show, Don't Tell
Zero-shot prompting asks an LLM to do something without showing examples. It works well for tasks the model was heavily trained on. Few-shot prompting adds examples directly in the prompt. It works when you need the model to follow a specific format, apply a specific style, or perform a task that's ambiguous enough that examples constrain the output space. Knowing when to switch is the skill.
- Your LLM outputs the right content but the wrong format โ you're spending tokens reformatting
- You have a classification task with 5 categories and the model keeps inventing a 6th
- Your model writes in a style that doesn't match your product voice
- You've tried describing what you want in instructions but the model ignores them
Three well-chosen examples in the prompt consistently outperform two paragraphs of instructions for format-sensitive tasks.
Few-Shot vs Zero-Shot
flowchart TD
task["New task"] --> wellKnown
wellKnown{"Well-known task?
(summarise, translate,
write code)"}
wellKnown -->|Yes| zeroShot["Zero-shot
Just describe the task"]
wellKnown -->|No| specific
specific{"Format-sensitive
or style-specific?"}
specific -->|Yes| fewShot["Few-shot
3โ5 examples in prompt"]
specific -->|No| zeroShot
Few-Shot Structure
// Building a few-shot prompt programmatically
const examples = [
{
input: "The checkout button doesn't work on mobile",
output: JSON.stringify({
category: "bug",
priority: "high",
component: "checkout",
platform: "mobile"
})
},
{
input: "Can you add dark mode?",
output: JSON.stringify({
category: "feature-request",
priority: "medium",
component: "ui",
platform: "all"
})
},
{
input: "How do I reset my password?",
output: JSON.stringify({
category: "support",
priority: "low",
component: "auth",
platform: "all"
})
}
];
function buildFewShotPrompt(userTicket: string): string {
const exampleBlock = examples.map(e =>
`Input: ${e.input}
Output: ${e.output}`
).join('
');
return `You are a support ticket classifier. Classify each ticket into the JSON format shown.
${exampleBlock}
Input: ${userTicket}
Output:`;
}
// Usage
const prompt = buildFewShotPrompt("App crashes when I upload a profile picture");
// Model completes: {"category":"bug","priority":"high","component":"profile","platform":"all"}Example Selection
Random examples are baseline. Strategic selection improves accuracy significantly for complex tasks.
// Strategy 1: Semantic similarity โ show examples most similar to the input
// Uses embeddings to find the closest examples in your example bank
async function selectSimilarExamples(
input: string,
exampleBank: Array<{ input: string; output: string }>,
k = 3
): Promise<Array<{ input: string; output: string }>> {
const inputEmbedding = await embed(input);
const ranked = await Promise.all(
exampleBank.map(async (example) => {
const exampleEmbedding = await embed(example.input);
const similarity = cosineSimilarity(inputEmbedding, exampleEmbedding);
return { example, similarity };
})
);
return ranked
.sort((a, b) => b.similarity - a.similarity)
.slice(0, k)
.map(r => r.example);
}
// Strategy 2: Label coverage โ ensure examples span all output categories
// For classification: include at least one example per class
function selectBalancedExamples(
exampleBank: Array<{ input: string; output: { category: string } }>,
categoriesNeeded: string[]
): Array<{ input: string; output: { category: string } }> {
return categoriesNeeded.map(category =>
exampleBank.find(e => e.output.category === category)!
);
}Ordering and Format Consistency
// Order effect: models pay more attention to early and late examples
// Put edge cases or tricky examples at the end (recency effect)
// Put typical cases first (helps establish the pattern)
// Format consistency: your examples must follow the EXACT same format
// or the model will blend the formats
// โ Inconsistent โ model will produce inconsistent output
const badExamples = [
"Input: foo
Output: {"a":1}",
"Input: bar
Output: {
"a": 2
}" // different JSON formatting
];
// โ
Consistent โ same format every time
const goodExamples = [
"Input: foo
Output: {"a":1}",
"Input: bar
Output: {"a":2}" // identical formatting
];
// If you want structured JSON output, use structured output mode
// instead of few-shot JSON examples:
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: prompt }],
response_format: {
type: 'json_schema',
json_schema: {
name: 'ticket_classification',
schema: {
type: 'object',
properties: {
category: { type: 'string', enum: ['bug', 'feature-request', 'support'] },
priority: { type: 'string', enum: ['low', 'medium', 'high'] },
},
required: ['category', 'priority'],
},
},
},
});Pitfalls
Too many examples
More examples are not always better. Beyond 5โ7 examples, returns diminish rapidly and you're spending tokens that could go to the actual task. If you need many examples to constrain the output, fine-tuning is usually more efficient than few-shot at scale.
Label leakage in examples
If your examples are not representative of your production input distribution, the model will learn patterns that don't transfer. Evaluate on a held-out set of real inputs, not on examples similar to the ones in your prompt.
Conflating few-shot with fine-tuning
Few-shot prompting works at inference time and costs tokens on every call. Fine-tuning bakes the pattern into model weights and costs nothing at inference time beyond the task itself. For high-volume, repetitive tasks (thousands of calls per day), fine-tune on your few-shot examples rather than including them in every prompt.

