Prompt Engineering Basics

Generative AI & LLM Fundamentals · Prompt engineering · 13 min read · Updated

What a good prompt contains

  1. Say clearly what the task is. A vague prompt gets a vague answer.
  2. Give a role and constraints, for example who the reader is and how long the answer should be.
  3. Add the context the model cannot know: your text, your data or your rules.
  4. A few examples of the exact output you want teach the format better than a description.
  5. The model uses all of this together to write the reply.
  6. You get an answer in the shape you asked for. Reuse the prompt as a template with variables.

A prompt is the text you give a model. Prompt engineering is the skill of writing prompts so that the model gives answers that are useful, correct and in the shape you need. It sounds fancy, but at its heart it is clear writing. If you can explain a job to a new colleague so that they do it well the first time, you can write good prompts. In this lesson you will learn a simple structure that works for most tasks, see a weak prompt turn into a strong one, and build a prompt builder in the lab that assembles the parts and warns you about common gaps.

Why prompts matter

A model cannot read your mind. It sees only the text you send. If your request is vague, the model fills the gaps with the most likely guess, which may not be what you want. Ask for “a good resume summary” and you will get a generic summary that could be about anybody. Ask for “a two-sentence resume summary for a warehouse supervisor with five years of experience who reduced picking errors by 20 percent, in simple English, with no buzzwords” and the result will fit.

Prompting is not a trick to fool the model. It is a way to remove guessing. Every detail you add is one less thing the model must invent.

The six parts of a good prompt

Most strong prompts contain some or all of these six parts. You do not need all of them every time. Use the ones that matter for the task.

  1. Role. Who the model should act as. “You are a careful career coach for factory and warehouse workers.” A role sets the tone and the level of detail.
  2. Task. What exactly to do, starting with a verb. “Rewrite the resume bullet below so that it starts with an action verb and shows a result.”
  3. Context. The material to work on, and any background the model needs: the resume text, the job description, the customer email. Put it between clear markers, such as <context> tags, so the model can tell data from instructions.
  4. Rules. The limits: “Do not invent numbers.” “Use simple English.” “Keep it under 25 words.” Rules prevent the most common problems before they happen.
  5. Examples. One to three pairs of input and output that show the style you want. Examples are often the strongest way to communicate a format. Use different examples so that the model does not copy a single one too closely.
  6. Output format. How the answer should look: bullets, a table, a number of sentences, or JSON. If a program will read the answer, the format must be exact (lesson 10).

A weak prompt and a strong prompt

Weak prompt:

Improve this: Responsible for stock and loading at the warehouse

The model must guess what “improve” means, who the reader is, how long the result should be, and whether it may add facts. It may add invented numbers or write a long paragraph.

Strong prompt:

You are a careful career coach for factory and warehouse workers.

Task: Rewrite the resume bullet below so it starts with an action verb and shows a result.

Context:
<context>
Responsible for stock and loading at the warehouse
</context>

Rules:
- Do not invent numbers.
- Use simple English.

Examples:
<example n="1">
Input: Did customer calls
Output: Handled 40 customer calls a day and solved 90% of issues on the first call
</example>

Output format: One bullet under 25 words. No extra text.

This takes a minute longer to write. But the result is much more predictable, and when it is not right you can see which part to fix. Notice one more detail: the rule “do not invent numbers” sits right next to an example that contains numbers. If the example contains numbers, the model may copy that style and invent some. This kind of clash between rules and examples is a common cause of errors, so read your own prompt the way the model will.

Habits that make prompts better

  • Be specific. Replace words like “good”, “nice”, “some” and “etc” with something you can measure: “three bullets, each under 20 words”.
  • Say what to do, not only what to avoid. “Write in plain sentences” works better than “don’t be fancy”.
  • Give the reader. “Explain it for a factory worker who has never used a computer” changes the whole answer.
  • Separate instructions from data. Use tags or clear markers around pasted text. This also protects you from text that tries to give the model new instructions (lesson 11).
  • Allow “I don’t know”. Add “If the answer is not in the text, say so.” This reduces made-up answers.
  • Ask for the steps when the task is complex. For maths or multi-step problems, ask the model to work through the steps before giving the answer. Lesson 9 covers this.
  • Iterate. Your first prompt is a draft. Run it on several real inputs, find where it fails, and change one thing at a time.

Zero-shot, one-shot and few-shot

A prompt with no examples is called zero-shot. One example makes it one-shot, and several make it few-shot. Start zero-shot with clear instructions. If the format or style is still wrong, add examples. Few-shot prompting is especially helpful for tasks like classification (“positive”, “negative”, “neutral”) and for matching a house style.

Prompts are not the whole story

Prompt engineering has limits. A model cannot know facts it was never given. If you need answers about your own documents, give it those documents (this is called retrieval, covered in the RAG course). If you need a guaranteed format, validate the answer in code. If an answer matters, a person should check it. Good prompts raise the quality, and testing and checking keep it there.

Your task: build a prompt builder

You will build a page with a form for the six parts of a prompt. As you type, the page assembles the full prompt, estimates its length in tokens, and shows tips about gaps such as a vague task or a missing format.

What you will build

  • A PromptParts type and a buildPrompt function that joins the parts in a clear order and skips empty ones.
  • A lint function that returns warnings. Each warning is either a tip or a problem.
  • A page with inputs for each part, a live preview, a token estimate (reuse estimateTokens from lesson 6) and a Copy button.

Steps

  1. Create lib/prompt.ts and define PromptParts: role, task, context, rules (a list), examples (a list of input and output pairs) and format.
  2. Write buildPrompt. Start with the role sentence, then Task, then the context inside <context> tags, then the rules as a bullet list, then the examples inside <example> tags, then the output format. Skip any part that is empty.
  3. Write lint. Add a problem when there is no task. Add tips for a very short task, for vague words like “good” or “some”, for a missing format, for no rules, and for exactly one example. Add a problem when the prompt is extremely long.
  4. Build the page in pages/prompt-builder.ts with signals for the parts. Compute the prompt, the warnings and the token estimate from them.
  5. Add a Copy button that uses the clipboard, and handle the case where the browser blocks it.

Hints

  • Test with a good prompt (no warnings) and a bad one (task: “make it good”). The checker should react to both.
  • The checker cannot judge quality. It only catches common gaps, so say so on the page.
  • Keep the order of the parts the same every time, so you can compare prompts easily.
  • Use the starting values from the lesson’s strong prompt to check your output.

You know it works when the preview changes as you type, an empty format field produces a tip, a task like “make it good” produces a tip about vague words, and the copied prompt works in any AI chat.

Solution

The prompt builder and the checker:

// A prompt is a small document. Writing it in labelled parts makes it easier to improve and to test.

export type PromptParts = {
  role: string;
  task: string;
  context: string;
  rules: string[];
  examples: { input: string; output: string }[];
  format: string;
};

export function buildPrompt(p: PromptParts): string {
  const out: string[] = [];
  if (p.role.trim()) out.push(`You are ${p.role.trim()}.`);
  if (p.task.trim()) out.push(`Task: ${p.task.trim()}`);
  if (p.context.trim()) out.push(`Context:\n<context>\n${p.context.trim()}\n</context>`);
  const rules = p.rules.map((r) => r.trim()).filter(Boolean);
  if (rules.length) out.push(`Rules:\n${rules.map((r) => `- ${r}`).join('\n')}`);
  const ex = p.examples.filter((e) => e.input.trim() && e.output.trim());
  if (ex.length) out.push(`Examples:\n${ex.map((e, i) => `<example n="${i + 1}">\nInput: ${e.input.trim()}\nOutput: ${e.output.trim()}\n</example>`).join('\n')}`);
  if (p.format.trim()) out.push(`Output format: ${p.format.trim()}`);
  return out.join('\n\n');
}

export type Warning = { level: 'tip' | 'problem'; text: string };

const VAGUE = /\b(good|nice|better|great|some|things|stuff|etc|appropriate)\b/i;

// A very simple prompt checker. It cannot judge quality, but it catches common gaps.
export function lint(p: PromptParts): Warning[] {
  const w: Warning[] = [];
  const task = p.task.trim();
  if (!task) w.push({ level: 'problem', text: 'There is no task. Say what the model must do, starting with a verb.' });
  else {
    if (task.split(/\s+/).length < 5) w.push({ level: 'tip', text: 'The task is very short. Add who it is for and what a good result looks like.' });
    if (VAGUE.test(task)) w.push({ level: 'tip', text: 'The task contains vague words (like "good" or "some"). Say exactly what you mean, for example "3 bullet points under 20 words".' });
  }
  if (!p.format.trim()) w.push({ level: 'tip', text: 'No output format. Say how the answer should look: bullets, a table, JSON, a word limit.' });
  if (!p.rules.some((r) => r.trim())) w.push({ level: 'tip', text: 'No rules. Add limits such as "do not invent facts" or "use simple English".' });
  if (p.context.trim() && !/<context>|"""|---/.test(buildPrompt(p))) w.push({ level: 'problem', text: 'Context is not separated from instructions.' });
  if (p.examples.filter((e) => e.input.trim() && e.output.trim()).length === 1) w.push({ level: 'tip', text: 'One example can make the model copy it too closely. Use two or three different ones.' });
  if (buildPrompt(p).length > 6000) w.push({ level: 'problem', text: 'The prompt is very long. Remove what the model does not need.' });
  return w;
}

And the page:

import { Component, computed, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { buildPrompt, lint, type PromptParts } from '../lib/prompt';
import { estimateTokens } from '../lib/sampling';

@Component({
  selector: 'app-prompt-builder',
  imports: [FormsModule],
  template: `
    <h1>Build a clear prompt</h1>
    <p class="hint">Fill in the parts. The prompt on the right is assembled for you, and a checker points out common gaps. Copy the result into any AI chat to try it.</p>

    <div class="grid2">
      <div class="card">
        <label for="r">Role</label><input id="r" [ngModel]="parts().role" (ngModelChange)="set('role', $event)" />
        <label for="t">Task</label><textarea id="t" [ngModel]="parts().task" (ngModelChange)="set('task', $event)" style="min-height:70px"></textarea>
        <label for="c">Context (paste the material to work on)</label><textarea id="c" [ngModel]="parts().context" (ngModelChange)="set('context', $event)" style="min-height:90px"></textarea>
        <label for="ru">Rules (one per line)</label><textarea id="ru" [ngModel]="rulesText()" (ngModelChange)="rulesText.set($event)" style="min-height:70px"></textarea>
        <label for="f">Output format</label><input id="f" [ngModel]="parts().format" (ngModelChange)="set('format', $event)" />
        <h2>Examples</h2>
        @for (e of parts().examples; track $index) {
          <input [ngModel]="e.input" (ngModelChange)="setExample($index, 'input', $event)" placeholder="Example input" />
          <input [ngModel]="e.output" (ngModelChange)="setExample($index, 'output', $event)" placeholder="Example output" />
        }
        <button class="secondary" (click)="addExample()">+ example</button>
      </div>

      <div class="card">
        <h2>Your prompt (about {{ tokens() }} tokens)</h2>
        <pre class="out">{{ prompt() }}</pre>
        <button (click)="copy()">{{ copied() ? 'Copied' : 'Copy prompt' }}</button>
        <h2>Checker</h2>
        @for (w of warnings(); track w.text) { <p [class]="w.level === 'problem' ? 'bad' : 'muted'">{{ w.level === 'problem' ? 'Problem' : 'Tip' }}: {{ w.text }}</p> } @empty { <p class="ok">No gaps found. Now test it with real examples.</p> }
      </div>
    </div>
  `,
})
export class PromptBuilderPage {
  parts = signal<PromptParts>({
    role: 'a careful career coach for factory and warehouse workers',
    task: 'Rewrite the resume bullet below so it starts with an action verb and shows a result.',
    context: 'Responsible for stock and loading at the warehouse',
    rules: [],
    examples: [{ input: 'Did customer calls', output: 'Handled 40 customer calls a day and solved 90% of issues on the first call' }],
    format: 'One bullet under 25 words. No extra text.',
  });
  rulesText = signal('Do not invent numbers.\nUse simple English.');
  copied = signal(false);

  full = computed<PromptParts>(() => ({ ...this.parts(), rules: this.rulesText().split('\n') }));
  prompt = computed(() => buildPrompt(this.full()));
  warnings = computed(() => lint(this.full()));
  tokens = computed(() => estimateTokens(this.prompt()).byChars);

  set<K extends keyof PromptParts>(key: K, value: PromptParts[K]) {
    this.parts.update((p) => ({ ...p, [key]: value }));
  }

  setExample(i: number, key: 'input' | 'output', value: string) {
    this.parts.update((p) => ({ ...p, examples: p.examples.map((e, j) => (j === i ? { ...e, [key]: value } : e)) }));
  }

  addExample() {
    this.parts.update((p) => ({ ...p, examples: [...p.examples, { input: '', output: '' }] }));
  }

  async copy() {
    try {
      await navigator.clipboard.writeText(this.prompt());
      this.copied.set(true);
      setTimeout(() => this.copied.set(false), 1500);
    } catch { /* clipboard can be blocked; the text is still on the screen */ }
  }
}

What you learned

  • A prompt works best when it removes guesswork: role, task, context, rules, examples and output format.
  • Be specific, separate instructions from data, allow “I don’t know”, and iterate.
  • Examples are powerful, but watch for clashes between the rules and the examples.
  • Prompts raise quality, but testing, validation and human checks keep it reliable.

In the next lesson you will learn more advanced techniques: step-by-step reasoning, prompt chains, and ways to combine several small prompts.

Download the solution (genai-basics-lesson-08.zip)

Frequently asked questions

Do longer prompts always give better results?

No. A prompt should be as long as it needs to be. Extra, unclear or repeated instructions can confuse the model and cost more. Add only the details that change the answer.

Should I say please and thank you to the model?

It does not matter much for quality. A clear and polite tone is fine, but the facts, the task and the format are what make the difference.

What is the difference between zero-shot and few-shot prompting?

Zero-shot gives only instructions. Few-shot also includes a small number of examples of the input and the output you want, which helps the model copy the format and style.

Is prompt engineering a real job?

Writing and testing good prompts is a real skill, and it is part of many roles, such as developer, analyst and content specialist. Most of it is clear writing, careful testing and knowing what the task needs.