Skip to the solution
MediumAI Engineering13 min read

What is context engineering and how does it differ from prompt engineering?

571 views

The problem

Question presented to candidate: "Lately people say context engineering instead of prompt engineering. What is context engineering, how is it different from writing a good prompt, and what does it look like in a real agent or RAG system?"

What a strong answer should cover:

  • Prompt engineering is crafting the instruction text; context engineering is designing everything the model sees at each step (instructions, tool definitions, retrieved documents, memory, conversation history, tool results) and the system that assembles it
  • The context window is a finite budget, and quality falls as it fills with irrelevant or stale material, well before the hard limit (context rot, lost in the middle), so every item must earn its tokens
  • The main moves: write (keep notes and state outside the window), select (retrieve only what is relevant, load only the tools needed), compress (summarise old turns, trim tool output), isolate (sub-agents with their own clean context)
  • Ordering and stability: stable content first for caching, the question last, important material at the edges, a reserve for the output
  • It is an engineering discipline with code and evals: measure what was included, what was dropped and why, and test assembly changes like any other change

Clarifying questions expected:

  • Is this a single-call feature, a RAG system or a multi-step agent?
  • What is the context budget per call, and what does a typical step currently send?

Code / implementation expected: Optional β€” a context assembler that ranks, deduplicates and trims candidate items under a token budget is a strong signal.

  • context-engineering
  • prompting
  • rag
  • memory

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

13 min read Β· 10 sections

Read the code

Assemble one agent step under a token budget: select, dedupe, compress, order
// Context engineering: assemble what the model sees for ONE agent step from
// several sources, under a token budget. Token counts are given per item
// (in a real system they come from the provider tokenizer).
const WINDOW = 5000, RESERVE_OUTPUT = 1200;
const BUDGET = WINDOW - RESERVE_OUTPUT;

const system = { id: "rules", tok: 600 };
const tools = [
  ["search_orders", "orders"], ["refund_order", "orders"], ["get_invoice", "billing"], ["update_card", "billing"],
  ["create_ticket", "support"], ["send_email", "support"], ["run_sql", "admin"], ["deploy_service", "admin"],
  ["read_logs", "admin"], ["translate", "misc"], ["weather", "misc"], ["calendar", "misc"],
].map(([id, tag]) => ({ id, tag, tok: 180 }));
const memory = [
  { id: "prefers email over phone", tok: 20, score: 0.81 },
  { id: "plan: Pro, since 2024", tok: 15, score: 0.77 },
  { id: "asked about a pet name in March", tok: 25, score: 0.12 },
];
const chunks = [
  { id: "refund-policy#2", tok: 400, score: 0.91, text: "Refunds within 30 days..." },
  { id: "refund-policy#2-copy", tok: 400, score: 0.9, text: "Refunds within 30 days..." },
  { id: "double-charge-faq#1", tok: 350, score: 0.84, text: "If you were charged twice..." },
  { id: "card-update-guide#4", tok: 380, score: 0.58, text: "To update a card..." },
  { id: "pricing-2023#1", tok: 420, score: 0.55, text: "Old 2023 prices...", stale: true },
  { id: "careers-page#1", tok: 300, score: 0.21, text: "We are hiring..." },
];
const history = Array.from({ length: 12 }, (_, i) => ({ id: `turn ${i + 1}`, tok: 160 }));
const toolResult = { id: "search_orders result", tok: 2600, keepTok: 220 }; // raw JSON vs the 3 fields needed
const question = { id: "user question", tok: 40 };
const TASK_TAGS = new Set(["orders", "billing", "support"]);

const sum = (xs) => xs.reduce((a, x) => a + x.tok, 0);
const naive = system.tok + sum(tools) + sum(memory) + sum(chunks) + sum(history) + toolResult.tok + question.tok;
console.log(`naive: everything in = ${naive} tokens, budget ${BUDGET} (window ${WINDOW} - ${RESERVE_OUTPUT} for output) -> ${naive > BUDGET ? "does not fit" : "fits"}`);

const dropped = [];
const sections = [];
// 1. stable prefix first (cache friendly): rules + only the tools this task needs
const keptTools = tools.filter((t) => TASK_TAGS.has(t.tag));
tools.filter((t) => !TASK_TAGS.has(t.tag)).forEach((t) => dropped.push(`${t.id} (tool not needed for this task)`));
sections.push(["rules", 1, 1, system.tok], ["tools", tools.length, keptTools.length, sum(keptTools)]);
// 2. select: memory above a relevance threshold
const keptMem = memory.filter((m) => m.score >= 0.5);
memory.filter((m) => m.score < 0.5).forEach((m) => dropped.push(`memory "${m.id}" (score ${m.score})`));
sections.push(["memory", memory.length, keptMem.length, sum(keptMem)]);
// 3. compress: old turns become a summary, the last 4 stay verbatim; tool output trimmed to needed fields
const recent = history.slice(-4);
const summaryTok = Math.round(sum(history.slice(0, -4)) * 0.1);
sections.push(["history", history.length, `${recent.length}+summary`, sum(recent) + summaryTok]);
sections.push(["tool result", 1, "trimmed", toolResult.keepTok]);
sections.push(["question", 1, 1, question.tok]);
// 4. select: retrieved chunks by score, deduplicated, while budget remains
let used = sections.reduce((a, s) => a + s[3], 0);
const seen = new Set(), keptChunks = [];
for (const c of [...chunks].sort((a, b) => b.score - a.score)) {
  if (seen.has(c.text)) { dropped.push(`${c.id} (duplicate text)`); continue; }
  if (c.score < 0.5) { dropped.push(`${c.id} (score ${c.score} below 0.5)`); continue; }
  if (c.stale) { dropped.push(`${c.id} (superseded source)`); continue; }
  if (used + c.tok > BUDGET) { dropped.push(`${c.id} (no budget left)`); continue; }
  seen.add(c.text); keptChunks.push(c); used += c.tok;
}
sections.splice(3, 0, ["retrieved", chunks.length, keptChunks.length, sum(keptChunks)]);

console.log("\nsection      candidates  kept        tokens");
for (const [name, cand, kept, t] of sections) console.log(`${name.padEnd(12)} ${String(cand).padStart(10)}  ${String(kept).padEnd(10)} ${String(t).padStart(6)}`);
console.log(`${"total".padEnd(12)} ${"".padStart(10)}  ${"".padEnd(10)} ${String(used).padStart(6)} of ${BUDGET} (${((100 * used) / BUDGET).toFixed(1)}%)`);
console.log(`saved vs naive: ${naive - used} tokens (${((100 * (naive - used)) / naive).toFixed(1)}%)`);

console.log("\nfinal order (stable first, question last):");
console.log("  " + ["rules", "tools", "memory", "retrieved: " + keptChunks.map((c) => c.id).join(", "), "history summary", "last 4 turns", "tool result (trimmed)", "user question"].join("\n  "));
console.log("\ndropped:");
dropped.forEach((d) => console.log("  - " + d));

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 40 of 83 in AI engineering

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

Up nextMedium

What is an AI agent and how does the agent loop work?

Continue