Module 5 · Prompting & Context Engineering ⏱ 18 min

Few-Shot Examples and Prompt Templates

By the end of this lesson you will be able to:
  • Build a template renderer that fills named placeholders deterministically from variables
  • Assemble a prompt from instructions, few-shot examples, and a target in a fixed order
  • Reason about when few-shot examples earn their tokens versus when they waste them

A model never sees your program's variables — it only ever sees one big string. So the first job of prompt engineering is assembly: turn your instructions, your data, and your examples into that single string, deterministically, on every call. Hand-stitching that string with + works for one prompt and collapses at twenty.

A prompt template is a string with named holes you fill from variables, and a template renderer is the tiny function that fills them. This lesson builds both, then adds few-shot examples — sample input/output pairs placed in the prompt so the model copies the shape you want. You will leave able to render a template, splice in examples, and, just as important, judge when those examples are actually worth their tokens.

flowchart LR
  T["template with placeholders"] --> R["template renderer"]
  V["variables from your code"] --> R
  R --> P["final prompt string"]
  style R fill:#8b5cf6,color:#fff
  style P fill:#166534,color:#fff
A renderer takes a template plus variables and produces the final prompt string.

How examples teach: format, not facts

A model is a next-token predictor trained on millions of texts. Drop two or three worked examples into its prompt and it imitates their pattern — the same field names, the same ordering, the same style of answer. This is in-context learning: the examples shape behaviour for this one call without changing any weights. The leverage is biggest for output format. If you need JSON with keys label and confidence, three examples pin that down far more reliably than a paragraph describing it.

Notice what the examples do not do. They do not teach the model new facts — it already knew those, or it did not. They teach the shape of the answer. Keep that distinction sharp: few-shot is a formatting and steering tool, and reaching for it when the model merely lacks knowledge will burn tokens for no gain.

A template renderer replaces every placeholder with its value. Press Run.
function renderTemplate(template, vars) {
  let out = template;
  for (const key in vars) {
    out = out.split("{{" + key + "}}").join(vars[key]);
  }
  return out;
}

const t = "Classify: {{input}}\nLabel: {{label}}";
console.log(renderTemplate(t, { input: "blue sky", label: "weather" }));

Assembly order is part of the prompt

The renderer fills placeholders, but the order of the pieces is its own decision. A reliable assembly is: system instructions first (they set the task and the rules), then few-shot examples (they prime the format), then the target input last (so the model simply continues the pattern). Putting the target last is not cosmetic — it lets the model treat the examples as a lead-in and emit its answer as the natural next line.

Order also fights a real failure called lost in the middle: models attend best to the start and the end of a long prompt, and weakest to the centre. Keeping the instructions at the front and the target at the back means the two things the model most needs are in the high-attention zones, while examples sit in the middle where they only have to nudge, not carry, the answer.

flowchart TD
  I["system instructions"] --> P["assembled prompt"]
  E["few-shot examples"] --> P
  G["target input"] --> P
  P --> M["model call"]
  style P fill:#b45309,color:#fff
  style M fill:#166534,color:#fff
A prompt is assembled from sections in a fixed order; the target lands at the end so the model continues the pattern.
Assemble a prompt from non-empty sections joined by a blank line. Run it.
function assemblePrompt(parts) {
  return parts.filter(p => p && p.length > 0).join("\n\n");
}

const prompt = assemblePrompt([
  "You classify support tickets.",
  "Input: refund request\nOutput: billing",
  "Input: login fails\nOutput: tech",
  "Input: how do I upgrade?\nOutput: "
]);
console.log(prompt);

When examples help, and when they waste tokens

Examples earn their cost when the task has a rigid or unusual shape the model will not guess on its own: a fixed label set, a strict JSON schema, a step-by-step reasoning format, or a delicate classification boundary. They are wasted on tasks the model already does well from a clear instruction — there you are paying input tokens on every single call for no measurable gain.

That cost is not abstract. Every example is billed as input tokens on every call, and the whole assembled prompt must fit inside the context budget. Stack six fat examples and you have six fewer retrieved-document chunks you can afford to pass in. Treat examples like any scarce resource: keep the smallest set that reliably pins the format, and drop one whenever the output quality does not change.

The discipline: zero-shot first

The strongest habit in prompt work is to start zero-shot — instructions only, no examples — and look hard at the output. If the model already produces the right shape, ship it and keep the tokens. Only when it drifts do you add the smallest example set that fixes the drift, testing after each addition. This loop — cheapest thing that works — keeps prompts small and forces every example to earn its place, instead of accumulating examples defensively until the prompt is bloated, slow, and expensive for no reason.

flowchart LR
  B["fixed context budget"] --> A["assembled payload"]
  E["each example costs tokens"] --> A
  R["retrieved documents"] --> A
  A --> O["overflow is trimmed"]
  style A fill:#b91c1c,color:#fff
  style B fill:#3776ab,color:#fff
Inside a fixed budget, examples and retrieved context compete for the same tokens.
Format examples into Input/Output lines, then append the target for the model to complete. Run it.
function formatExamples(examples) {
  return examples.map(e => "Input: " + e.input + "\nOutput: " + e.output).join("\n");
}

const block = formatExamples([
  { input: "2+2", output: "4" },
  { input: "3+3", output: "6" }
]) + "\nInput: 5+5\nOutput:";
console.log(block);
Exercise

What does this print? The renderer replaces every placeholder with its value.

function render(t, v) {
  let out = t;
  for (const k in v) out = out.split("{{" + k + "}}").join(v[k]);
  return out;
}
console.log(render("{{greeting}}, {{who}}!", { greeting: "Hi", who: "Ada" }));
Exercise

Write renderTemplate(template, vars) that returns template with every occurrence of each {{key}} replaced by vars[key]. Replace all occurrences, not just the first.

function renderTemplate(template, vars) {
  // replace every {{key}} with vars[key]
}
Exercise

This fill is meant to replace every {{key}}, but it uses String.replace, which swaps only the first match — so a template with two {{x}} comes out half-filled. Fix it so all occurrences are replaced.

function fill(template, vars) {
  let out = template;
  for (const k in vars) {
    out = out.replace("{{" + k + "}}", vars[k]);
  }
  return out;
}
Exercise

Few-shot examples help MOST for which kind of task?

Exercise

Write buildFewShot(examples, targetInput) that returns a prompt string: each example as an Input:/Output: pair, a blank line between pairs, then the target as a final Input: with an empty Output: ready for the model to complete.

function buildFewShot(examples, targetInput) {
  // format each example, separate pairs with a blank line, append the target
}

Recap

  • A prompt template is a string with {{placeholders}}; a renderer fills them from variables so assembly is deterministic, not hand-stitched with +.
  • In-context learning means few-shot examples shape the format of the answer for one call without changing weights — they teach shape, not facts.
  • Assemble in a fixed order: instructions, then examples, then the target last, so the model continues the pattern and the ends (high attention) carry the load.
  • Examples are worth their tokens for rigid or unusual output shapes, and wasted on tasks the model already does well — they cost input tokens on every call.
  • Replace every occurrence of a placeholder: String.replace swaps only the first, so use split(...).join(...) or replaceAll.
  • Watch label leakage: never put the target answer inside an example's input.

Next: packing a whole context window — instructions, history, retrieved chunks, and these examples — under a fixed token budget.

Checkpoint quiz

What does in-context learning actually change when you add few-shot examples to a prompt?

A template is {{x}} and {{x}} and you fill it with { x: "Q" } using String.replace. What comes out?

Why keep the target input at the END of the assembled prompt rather than the middle?

Go deeper — technical resources