Step-by-Step Instructions

40 min intermediate Lesson 3

Learning Outcomes

  • Break complex coding tasks into numbered steps that AI can follow sequentially
  • Order steps correctly based on dependencies and logical flow
  • Judge when step-by-step helps versus when it over-constrains
  • Handle sequential dependencies between steps
  • Combine narrative descriptions with numbered instructions for optimal results

Lesson Plan

Segment Duration Topic
Intro 3 min Why sequence matters for complex tasks
Explain 7 min Breaking tasks into numbered steps
Demo 8 min Ordering for optimal results
Explain 7 min When steps help vs constrain
Demo 7 min Handling sequential dependencies
Practice 6 min Combining narrative and numbered approaches
Wrap-up 2 min Key takeaways, preview next lesson

Before You Begin

Pre-work:

  • Complete Lessons 1-2 (Anatomy and Role/Context)
  • Think of a recent multi-file or multi-step coding task
  • Review: have you ever given AI a complex task and got back something that was "close but wrong"?

Shopping List:

  • Any AI coding tool (Claude Code, Cursor, or Codex CLI)
  • A project with a feature you'd like to build that requires 3+ steps
  • Your prompts folder for saving experiments

1 Breaking Complex Tasks into Numbered Steps

When a task involves multiple operations, AI performs significantly better when you break it into explicit numbered steps. Without steps, AI must infer the correct order — and it often gets it wrong.

The problem with monolithic prompts:

Build a user registration feature with email verification, password hashing,
database storage, and a welcome email.

This might produce a single giant function that mixes all concerns, or it might miss one of the four requirements entirely, or implement them in an order that doesn't work (like sending the email before storing the user).

The step-by-step version:

Implement user registration. Follow these steps in order:

1. Create a registerUser function that accepts { email, password, name }
2. Validate the input (email format, password minimum 8 chars, name non-empty)
3. Check if a user with this email already exists — if so, throw ConflictError
4. Hash the password using bcrypt with 12 rounds
5. Store the user record in the database with is_verified = false
6. Generate a verification token (random 32-byte hex string) with 24h expiry
7. Store the token in a verification_tokens table linked to the user
8. Send a verification email using the sendEmail service
9. Return { userId, message: "Verification email sent" } — never return the token

Each step should be its own clearly separated block of code with a comment.

Why steps work:

  • Completeness — each requirement is explicit, nothing gets skipped
  • Order — dependencies are handled (you can't hash before validating)
  • Testability — each step is a unit that can be verified
  • Clarity — AI knows exactly what "done" means
NOTE
Key Insight
Numbered steps act as a checklist for the AI. Just like a pilot's pre-flight checklist prevents skipping critical steps, numbered prompts prevent AI from taking shortcuts or making assumptions about order.

How many steps is right?

  • 3-5 steps: Most common, good for feature implementations
  • 6-10 steps: Complex workflows, multi-stage processes
  • 10+ steps: Consider splitting into multiple prompts instead

2 Ordering for Optimal Results

The order of your steps matters more than you might think. AI generates code sequentially — each step builds on what came before. Getting the order wrong creates cascading problems.

Rule 1: Dependencies first

If step B uses something created in step A, A must come first.

Wrong order:

1. Write the route handler that calls userService.getProfile()
2. Create the UserService class with a getProfile method
3. Define the User interface

Right order:

1. Define the User interface with the fields we need
2. Create the UserService class with a getProfile method that returns User
3. Write the route handler that calls userService.getProfile()

Rule 2: Data structures before logic

Define types, interfaces, and schemas before the code that uses them.

1. Define the OrderStatus enum (pending, confirmed, shipped, delivered, cancelled)
2. Define the Order interface with all fields including status: OrderStatus
3. Create the state machine rules (which transitions are valid)
4. Implement the transitionOrder function that enforces the rules
5. Add the API endpoint that calls transitionOrder

Rule 3: Simple before complex

Start with the straightforward path, then add complexity.

1. Implement the basic search — query the database by title, return results
2. Add pagination (page, limit parameters, total count in response)
3. Add filtering (by category, date range, price range)
4. Add sorting (by relevance, date, price — ascending/descending)
5. Add caching — cache results for identical queries for 5 minutes

Rule 4: Setup before action

Configuration, initialisation, and connections come before the code that uses them.

1. Set up the Redis connection with retry logic
2. Create the cache utility (get, set, invalidate methods)
3. Implement the product service with caching at the repository layer
4. Add cache invalidation hooks on product update/delete
WARNING
Watch Out
If AI produces code where later functions reference undefined variables or uncreated types, your steps are in the wrong order. Reorder so that every step only references things created in previous steps.

3 When Steps Help vs Constrain

Step-by-step instructions are powerful but not always appropriate. Knowing when to use them — and when NOT to — is a key skill.

Steps HELP when:

  • The task has a clear sequence (build A, then B uses A)
  • You've tried a freeform prompt and got incomplete or disordered results
  • The task involves multiple files or components
  • There are critical ordering constraints (migrate data before deleting old columns)
  • You need to ensure nothing is skipped

Steps CONSTRAIN when:

  • You're exploring possibilities ("What's the best approach to...?")
  • The task is genuinely simple and unambiguous
  • You want creative solutions (steps force a specific path)
  • You're over-specifying implementation details

Example: When steps hurt

Over-specified:

Create a utility function to format currency:
1. Accept a number as input
2. Create a variable called formatter
3. Use Intl.NumberFormat with locale "en-US"
4. Set style to "currency" and currency to "USD"
5. Call formatter.format(number)
6. Return the result

This is so detailed that you've essentially written the code yourself in English. Better:

Create a currency formatting utility that:
- Supports multiple currencies (USD, EUR, GBP at minimum)
- Accepts locale as a parameter with "en-US" as default
- Handles edge cases: NaN, negative numbers, very large numbers
- Returns a string like "$1,234.56"

The second version tells AI WHAT you want without dictating every line of HOW. This leaves room for AI to apply best practices you might not have thought of.

The guideline: Use steps for ORCHESTRATION (what happens in what order) but not for IMPLEMENTATION (how each individual piece works internally).

Good use of steps (orchestration):
1. Validate the incoming webhook payload
2. Determine which event type it is
3. Route to the appropriate handler
4. Execute the handler and capture the result
5. Return appropriate HTTP status code

Bad use of steps (micro-managing implementation):
1. Create a variable called payload
2. Parse the JSON body
3. Check if payload.type exists
4. If it's "order.created" call handleOrderCreated
5. If it's "order.updated" call handleOrderUpdated
...
TIP
Tip
If you find yourself writing more than 2 sentences per step, you're probably over-specifying. Steps should be outcomes ('validate the input') not implementations ('use a regex to check if the email contains @').

4 Handling Sequential Dependencies

Some tasks have steps where the output of one step becomes the input to the next. Managing these dependencies clearly prevents AI from making incorrect assumptions.

Explicit dependency notation:

1. Create the database migration to add a "preferences" JSONB column
   to the users table with a default value of {}

2. Update the User model to include the preferences field
   (use the column from step 1)

3. Create a PreferencesService that provides get/set/merge operations
   on the user's preferences (works with the model from step 2)

4. Add PUT /users/me/preferences endpoint that accepts partial updates
   (uses PreferencesService from step 3)

5. Add validation middleware that ensures preference keys are from
   an allowed list: ["theme", "language", "notifications", "timezone"]
   (attach to the endpoint from step 4)

Notice how each step references which previous step it builds on. This prevents AI from creating disconnected code.

Handling branching dependencies:

Sometimes steps don't form a linear chain. Indicate this:

Steps 1-2 are independent (can be in either order):
1. Create the EmailTemplate interface { subject, body, variables }
2. Create the NotificationChannel enum { email, sms, push }

Steps 3-4 depend on both 1 and 2:
3. Create the NotificationService that accepts a channel and template
4. Implement the send method with channel-specific logic

Step 5 depends on 3-4:
5. Create the API endpoint that triggers notifications

The "carry forward" technique:

When steps build on each other, you can tell AI to maintain state:

Implement a data pipeline with these sequential stages:

1. FETCH: Download JSON from the API endpoint, handle rate limiting
   Output: raw JSON array

2. TRANSFORM: Map the raw data to our internal format
   Input: raw JSON array from step 1
   Output: array of CleanRecord objects

3. VALIDATE: Check each record for required fields and valid ranges
   Input: CleanRecord array from step 2
   Output: { valid: CleanRecord[], invalid: ErrorRecord[] }

4. LOAD: Batch insert valid records into PostgreSQL
   Input: valid records from step 3
   Also: log invalid records for review

Each function should accept the output of the previous step as input.
Use TypeScript generics to enforce the pipeline type safety.
NOTE
Key Insight
When you name the outputs of each step (e.g., 'Output: raw JSON array'), AI can maintain consistency between steps. Without explicit output names, AI might change the data shape between steps, causing type mismatches.

5 Combining Narrative and Numbered Approaches

The most effective prompts for complex tasks often combine a narrative overview with numbered steps. The narrative provides the "why" and big picture; the steps provide the "what" in sequence.

The hybrid pattern:

I need to add real-time notifications to our app. Currently, users
only see new messages when they refresh the page. We want them to
receive instant updates via WebSocket when they get a new message,
a friend request, or a system alert.

Our stack: Node.js backend, React frontend, PostgreSQL.
We already have a messages table and a REST endpoint for fetching messages.

Implementation steps:

1. Set up a WebSocket server alongside our existing Express app
   (use the ws library, share the same HTTP server)

2. Create an authentication handshake — clients send their JWT
   during the WebSocket upgrade, reject unauthenticated connections

3. Maintain a Map of userId -> WebSocket connections (handle multiple
   tabs/devices per user)

4. Create a notify(userId, event) utility that looks up connections
   and sends JSON messages

5. Hook into existing services: when a message is saved to DB,
   call notify(recipientId, { type: "new_message", data: message })

6. On the frontend, create a useWebSocket hook that:
   - Connects on mount, authenticates with stored JWT
   - Reconnects with exponential backoff on disconnect
   - Exposes incoming events via a callback
   - Cleans up on unmount

7. Integrate the hook into the NotificationBell component to show
   real-time unread count

Keep the WebSocket server stateless (no in-memory message queues) —
if we scale to multiple servers later, we'll add Redis pub/sub.

Why this works: The narrative (first paragraph) explains the motivation, current state, and goal. The technical context (second paragraph) sets constraints. The steps provide clear implementation order. The final note adds a forward-looking constraint.

Another example — the "context then steps" pattern:

Context:
Our e-commerce checkout currently processes payments synchronously.
This causes timeouts when the payment provider is slow (>5s).
We need to move to an async pattern where the user sees "processing"
and we confirm via webhook.

Current flow: Cart -> POST /checkout -> charge card -> show result
Target flow: Cart -> POST /checkout -> return "processing" -> 
             receive webhook -> update order -> notify user

Steps to implement:

1. Add an "order_status" column to orders (pending, processing,
   confirmed, failed) — currently we only have confirmed orders

2. Modify POST /checkout to:
   - Create order with status "processing"
   - Send payment request to provider with our webhook URL
   - Return 202 Accepted with { orderId, status: "processing" }

3. Create POST /webhooks/payment to receive provider callbacks:
   - Verify the webhook signature
   - Update order status based on payment result
   - Trigger appropriate follow-up (email confirmation or failure notice)

4. Add GET /orders/:id/status for the frontend to poll
   (simple endpoint returning { status, updatedAt })

5. Update the frontend checkout page to:
   - Show a "processing" state after submit
   - Poll /orders/:id/status every 2 seconds
   - Transition to success/failure page when status changes
   - Timeout after 30 seconds with "we'll email you" message
TIP
Tip
The narrative portion should answer: What exists today? What's wrong with it? What should exist after? The steps should answer: How do we get from here to there, in what order?

When to use pure narrative (no steps):

  • Design discussions ("What architecture would you recommend for...?")
  • Code review ("Review this function for potential issues")
  • Explanation requests ("Explain how this middleware chain works")

When to use pure steps (minimal narrative):

  • Well-understood tasks with clear technical requirements
  • Migrations and upgrades ("Upgrade from React Router 5 to 6")
  • Sequences where the context is already established

When to combine both:

  • Feature implementations (most common)
  • System design + implementation
  • Refactoring with a clear goal and multiple stages

Questions & Answers

Q: What if AI doesn't follow my steps in order?
This usually means your steps have implicit dependencies that AI resolved differently. Make dependencies explicit: "Step 3 uses the interface defined in step 1." If AI consistently reorders steps, ask yourself whether your ordering might not be optimal — sometimes AI finds a better sequence.
Q: Should each step be a separate prompt, or all steps in one prompt?
For most tasks (3-7 steps), use one prompt. AI maintains context between steps and can ensure consistency. For very complex tasks (10+ steps) or when each step requires iteration, break into separate prompts with explicit handoff: "In the previous step, we created X. Now..."
Q: How do I handle steps that might fail or have alternatives?
Include conditional logic in your steps: "3. Check if the table already has the column. If yes, skip to step 5. If no, run the migration in step 4." Or specify the error handling: "If the API returns a 429, implement exponential backoff with max 3 retries before failing."
Q: Is it better to have fewer detailed steps or more brief steps?
Prefer more brief steps. Each step should describe ONE action or decision. "Validate input, hash password, and store user" is three steps masquerading as one. Split them. Brief steps are easier for AI to execute completely and for you to verify individually.

Key Takeaways

  1. Numbered steps prevent AI from skipping requirements — each step is an explicit checkpoint.
  2. Order matters: dependencies first, data structures before logic, simple before complex.
  3. Use steps for orchestration, not implementation — describe outcomes, not line-by-line code.
  4. Name your step outputs — "Output: array of CleanRecord" prevents shape mismatches between steps.
  5. Combine narrative (the why) with steps (the what) — this gives AI both motivation and a clear path.

Next Steps: In Lesson 4 — Few-Shot Examples, we'll learn how showing AI what you want (with input/output examples) can be more powerful than describing it.