Skip to content
Lesson 10 of 12

Multi-Step Prompting — Complex Tasks

9 min read

When a Single Prompt Is Not Enough

Some tasks are too complex for a single prompt. If you need more than three paragraphs to describe what you want, the AI is likely to miss or misinterpret some part of it. Multi-step prompting breaks complex tasks into a chain of simpler prompts, where each step produces a reviewable intermediate result before the next step builds on it.

This is not a workaround for model limitations. It is a better workflow even with perfect models. Complex tasks have complex failure modes. When something goes wrong in a 10-step task executed as a single prompt, you have to debug the entire output. When something goes wrong in step 4 of a 10-step chain, you know exactly where to look and can fix just that step.

The Decomposition Principle

The decomposition principle is simple: if a task has multiple distinct phases, each phase should be its own prompt. The signs that a task needs decomposition:

  • It involves multiple files or components
  • It has a planning phase and an execution phase
  • It requires decisions that depend on intermediate results
  • It would take you more than 3 paragraphs to describe fully
  • Different parts require different types of reasoning (analysis vs. generation vs. review)

Single Prompt (Fragile)

Analyze our authentication codebase, find all security vulnerabilities,
propose fixes for each one, implement the fixes, write tests for the new
code, and update the documentation.

This prompt asks the AI to do six different things. It will likely rush through the analysis, miss vulnerabilities, implement quick patches instead of proper fixes, and produce superficial tests.

Multi-Step (Reliable)

Step 1 -- Analyze:

Analyze the authentication code in src/auth/ and src/middleware/auth.ts.
List every potential security vulnerability you find. For each, describe:
the vulnerability type, the specific code location, the risk level, and the
potential exploit scenario. Do not suggest fixes yet.

Step 2 -- Prioritize (after reviewing Step 1):

Based on the 7 vulnerabilities you found, prioritize them. Which ones need
immediate fixes and which can wait? Consider: exploitability, impact, and
effort to fix. Give me a ranked list.

Step 3 -- Fix (one at a time):

Fix vulnerability #1: the JWT token is not validated for expiration in the
authMiddleware. Implement the fix in src/middleware/auth.ts. Do not touch
other files yet.

Each step is simple, reviewable, and builds on verified output from the previous step.

The Plan-Then-Execute Pattern

This is the most common multi-step pattern for developers. You ask the AI to create a plan, review and approve the plan, and then execute it step by step.

Step 1 -- Plan:

I need to add a notification system to our Express.js app. Users should
receive in-app notifications and optional email notifications for: new
comments on their posts, new followers, and order status changes.

Before writing any code, create a plan that covers:
1. Database schema changes (what tables/columns to add)
2. New files to create (services, routes, middleware)
3. Existing files to modify
4. The order in which to implement these changes
5. Dependencies between changes (what must be done before what)

Output the plan as a numbered checklist I can review.

Step 2 -- Review and adjust:

Good plan. Two adjustments:
- Use a separate notifications table instead of adding columns to the users
  table
- Skip email notifications for now, we will add those in phase 2
- Add a WebSocket integration for real-time delivery in step 6

Update the plan with these changes.

Step 3 -- Execute step by step:

Execute step 1 from the plan: Create the Prisma schema for the notifications
table. Include the migration.
Step 1 looks good. Execute step 2: Create the NotificationService in
src/services/NotificationService.ts.
I see an issue in step 2 -- the createNotification method should also accept
a metadata field for arbitrary JSON data. Fix that, then move to step 3.

This pattern gives you a checkpoint between every step. If the AI's plan has a flaw, you catch it before any code is written. If step 3 introduces a bug, you do not need to redo steps 1 and 2.

Sequential Prompting: Output Feeds Input

In sequential prompting, the output of one step becomes the explicit input for the next step. This is useful for tasks where each step transforms data.

Example: Database Migration

Step 1: Analyze the current orders table schema and list all columns with
their types, constraints, and indexes.

After getting the schema analysis:

Step 2: Based on the schema above, design the migration to split the orders
table into orders (header info) and order_items (line items). Show me the new
schemas side by side with the data migration strategy.

After approving the schema design:

Step 3: Write the Prisma migration file that:
1. Creates the order_items table
2. Migrates data from the JSON items column in orders to individual rows
   in order_items
3. Removes the items column from orders
4. Adds the foreign key constraint

Use a transaction to ensure atomicity.

After the migration:

Step 4: Update the OrderService to use the new schema. The service methods
should return the same data shape as before (orders with nested items array)
to maintain backward compatibility with the API layer.

Each step depends on the verified output of the previous step. The schema design cannot be written without the analysis. The migration cannot be written without the approved schema. The service update cannot be written without the completed migration.

Task Lists and Checkpoints

For longer multi-step tasks, maintain a running task list that tracks progress. This keeps both you and the AI aligned on what has been done and what remains.

Here is our task list for the notification system migration. Check off items
as we complete them:

[x] 1. Create notifications table schema
[x] 2. Create NotificationService
[x] 3. Create notification routes (GET /notifications, PATCH /read)
[ ] 4. Add notification triggers in CommentService
[ ] 5. Add notification triggers in FollowerService
[ ] 6. Add WebSocket integration for real-time delivery
[ ] 7. Write tests for NotificationService
[ ] 8. Write integration tests for notification triggers

We just finished step 3. Now implement step 4: Add notification triggers
in CommentService. When a new comment is created on a post, create a
notification for the post author. Use the NotificationService.create() method
from step 2.

This task list serves as shared state between you and the AI. It prevents the AI from losing track of what has been completed and what patterns were established in earlier steps.

The Checkpoint-and-Verify Pattern

After each step, explicitly verify the result before proceeding. This catches errors early and prevents them from propagating through the chain.

Before we move to step 5, let me verify step 4. Run through these checks:

1. Does the CommentService.createComment() method call
   NotificationService.create() after successfully creating the comment?
2. Is the notification only created if the commenter is NOT the post author?
   (no self-notifications)
3. Does the notification include the comment preview text (first 100 chars)?
4. If the notification creation fails, does the comment still get created?
   (notification failure should not block the comment)

Check the code we wrote in step 4 against these requirements.

Verification prompts catch bugs at the step level instead of discovering them later during integration or testing. They are especially important for business logic requirements that are easy to miss.

Handling Failures in Chains

When a step in the chain produces incorrect output, you need to fix it without redoing all previous steps. The key is providing the AI with clear context about what is correct (previous steps) and what needs fixing (the current step).

Step 4 has an issue. The notification trigger fires even when the commenter
is the post author, creating self-notifications.

Fix only this specific issue in src/services/CommentService.ts. The fix
should add a check: if comment.authorId === post.authorId, skip the
notification creation. Do not modify anything from steps 1-3.

If a step is fundamentally wrong and cannot be patched:

Step 4 needs to be redone. The approach of calling NotificationService
directly from CommentService creates tight coupling. Instead, use an event
emitter pattern:

- CommentService emits a 'comment:created' event with the comment data
- A new NotificationListener in src/listeners/ subscribes to 'comment:created'
  and creates the notification

Rewrite step 4 with this approach. Steps 1-3 remain unchanged.

Parallel Decomposition

Not all multi-step tasks are sequential. Sometimes steps can be executed in parallel because they are independent.

We need to add three new API resources. These are independent and do not
depend on each other:

Task A: Create the Tags resource (CRUD endpoints + service + schema)
Task B: Create the Categories resource (CRUD endpoints + service + schema)
Task C: Create the Comments resource (CRUD endpoints + service + schema)

For each, follow the same pattern as our existing Products resource in
src/routes/products.ts, src/services/ProductService.ts, and
src/schemas/product.ts.

Let us start with Task A. After I review it, we will do B and C.

Even though the tasks are independent, doing them sequentially lets you verify that the first one establishes the right pattern before replicating it. Once Task A is approved, Tasks B and C can follow the same template with minimal review.

When to Merge Steps

Sometimes you over-decompose and create unnecessary overhead. Merge steps when:

  • Two steps are very simple and naturally belong together (create a file and add its import)
  • The intermediate result of a step has no value for review (you do not need to approve the TypeScript interface before seeing the implementation)
  • Steps are so small that the overhead of reviewing each one exceeds the benefit

The sweet spot is steps that are large enough to produce a meaningful, reviewable artifact but small enough that you can verify correctness in under a minute. For most developer tasks, this means one file or one logical unit per step.