Writing Clear, Actionable Instructions

10 minpublished

Write precise, actionable prompts that give AI assistants the specific direction needed for quality code generation.

Writing Clear, Actionable Instructions for AI Code Assistants

The difference between frustrating AI coding sessions and productive ones often comes down to one thing: how clearly you communicate what you want. Vague instructions produce vague code. Precise, actionable prompts produce code that works.

Think of AI code assistants like extremely talented junior developers who know syntax perfectly but need clear direction. They won't guess your requirements—they'll interpret your words literally. This lesson will teach you how to write instructions that get results on the first try.

Why Clarity Matters in AI-Assisted Coding

When you write unclear prompts, you create a debugging loop before you've even written code. The AI generates something that's technically valid but doesn't match your intent. You clarify. It tries again. You iterate. Minutes turn into hours.

Clear instructions cut through this waste. They help you:

  • Get working code faster: Less back-and-forth means more building
  • Reduce errors: Specific constraints prevent common mistakes
  • Build confidence: Predictable results make AI tools feel reliable
  • Scale your work: Good prompting patterns become reusable templates

Let's look at what makes instructions clear and actionable.

The Three Components of Clear Instructions

Every effective prompt contains three elements:

  1. What you want built (the outcome)
  2. How it should work (the behavior)
  3. Why it matters (the constraints)

Missing any of these creates ambiguity.

Vague vs. Clear: A Real Example

Vague:

Create a login function

Clear:

Create a login function that:
- Accepts email and password as parameters
- Returns a JWT token on success
- Throws a ValidationError for invalid email format
- Throws an AuthenticationError for wrong credentials
- Uses bcrypt for password comparison
- Logs failed attempts for security monitoring

The vague version leaves everything to interpretation. The clear version specifies behavior, error handling, security approach, and observability. The AI now has a complete picture.

Start with the Outcome

Always begin by stating exactly what you're building. Use concrete nouns and action verbs.

Weak opening:

I need something for users

Strong opening:

Create a UserProfile component that displays user information

The strong version names the artifact (UserProfile component) and its purpose (displays user information). This immediately frames everything that follows.

Be Specific About the Artifact Type

Different code artifacts have different expectations. Specify:

  • Functions: "Create a pure function that..."
  • Classes: "Build a class that implements..."
  • Components: "Create a React component that renders..."
  • APIs: "Design a REST endpoint that handles..."
  • Tests: "Write unit tests that verify..."

This tells the AI what patterns and conventions to follow.

Define the Behavior Precisely

After stating what you want, describe how it should work. Break complex behaviors into bullet points.

Example: Creating a data validation function

Create a validateUserInput function that:

Input:
- Accepts an object with username, email, and age properties

Validation rules:
- username: 3-20 characters, alphanumeric only
- email: valid email format
- age: number between 13 and 120

Output:
- Returns { valid: true, errors: [] } if all checks pass
- Returns { valid: false, errors: ['error messages'] } if any check fails
- Each error message should specify which field failed and why

Behavior:
- Check all fields even if one fails (don't short-circuit)
- Return errors in the order: username, email, age

Notice how this leaves nothing to chance. Input format, each validation rule, output structure, and execution behavior are all specified. Related to this approach is the context-constraints lesson, which covers how to set boundaries that prevent unwanted behaviors.

Use Examples to Eliminate Ambiguity

Sometimes the best way to communicate intent is to show, not tell. Include example inputs and expected outputs.

Create a formatCurrency function.

Examples:
formatCurrency(1234.5) → "$1,234.50"
formatCurrency(999) → "$999.00"
formatCurrency(0.5) → "$0.50"
formatCurrency(1234567.89) → "$1,234,567.89"

Requirements:
- Always show 2 decimal places
- Use comma as thousands separator
- Prefix with dollar sign
- Handle numbers up to billions

Examples act as test cases and specification simultaneously. The AI can pattern-match against them. For more on this technique, see few-shot-patterns.

Specify Your Constraints

Constraints are requirements that limit how something should be built. They're as important as features.

Technical Constraints

Create a file upload handler that:
- Accepts only .jpg, .png, and .pdf files
- Rejects files larger than 5MB
- Uses streaming to handle large files without loading into memory
- Works with Node.js streams API (not third-party libraries)

These technical boundaries prevent the AI from using approaches that won't work in your environment.

Performance Constraints

Write a search function that:
- Searches through up to 10,000 product records
- Returns results in under 100ms
- Uses binary search (assume sorted array)
- Prioritizes performance over code brevity

Performance requirements influence algorithm choice and implementation details.

Style and Convention Constraints

Create a data fetching hook following these conventions:
- Use TypeScript with explicit return types
- Follow React hooks naming (useXxx)
- Use async/await (not .then chains)
- Include JSDoc comments for public functions
- Match our existing error handling pattern with Result types

This ensures generated code fits your codebase style. For larger contexts, check out codebase-aware-prompting.

Action Verbs Matter

Different verbs signal different levels of completeness and quality.

Low-quality verbs:

  • "Make a function..." (too casual)
  • "Do something that..." (vague)
  • "Help me with..." (unclear outcome)

High-quality verbs:

  • "Create a function that..." (build from scratch)
  • "Refactor this code to..." (improve existing)
  • "Add error handling that..." (extend functionality)
  • "Implement the algorithm that..." (specific approach)
  • "Generate tests that verify..." (create verification)

Match your verb to your intention.

Break Down Complex Requests

Don't ask for everything at once. Complex features need decomposition.

Too complex:

Build a complete authentication system with login, signup, 
password reset, email verification, and session management

Better approach:

First, let's create the user registration function:
- Accept email, password, and username
- Validate input format
- Hash password with bcrypt (10 rounds)
- Store user in database
- Return the created user object (without password)
- Handle duplicate email error

We'll add login and session management in subsequent steps.

This focused request gets better results. You can iterate toward the complete system. The breaking-down-projects lesson covers this strategy in depth.

Provide Context When Needed

Sometimes the AI needs background to make good decisions.

Without context:

Create a caching function

With context:

Create a caching function for our API client.

Context:
- Our API rate limits to 100 requests/minute
- Data freshness matters more than speed
- We're building a dashboard that refreshes every 30 seconds
- Cache should expire after 25 seconds to ensure fresh data

Create a function that:
- Caches GET request results by URL
- Invalidates cache after 25 seconds
- Bypasses cache if force=true parameter is passed

Context explains the "why" behind your constraints, helping the AI make aligned decisions. Learn more about managing what information to include in context-window-management.

Use Step-by-Step Instructions for Algorithms

When you need specific logic flow, spell out the steps.

Create a function that processes a shopping cart checkout.

Follow these steps in order:
1. Validate that cart is not empty
2. Check that all items are still in stock
3. Calculate subtotal from item prices
4. Apply any discount codes (if valid)
5. Calculate tax based on shipping address
6. Calculate shipping cost
7. Generate final total
8. Return breakdown object with all amounts

If any step fails, return an error object with:
- step: which step failed
- reason: why it failed
- data: relevant context

This algorithmic clarity prevents the AI from reordering logic or skipping steps. The chain-of-thought lesson explores this reasoning pattern further.

Specify Error Handling Explicitly

Don't assume the AI will handle errors the way you want.

Create a fetchUserData async function that:

Happy path:
- Fetches user data from /api/users/:id
- Returns parsed JSON response

Error handling:
- If user not found (404): throw NotFoundError with user ID
- If network fails: throw NetworkError with original error
- If response isn't JSON: throw ParseError with response text
- If request times out after 5s: throw TimeoutError

All custom errors should:
- Extend Error class
- Include timestamp
- Include request ID if available

Explicit error handling prevents silent failures and makes debugging easier. The error-resolution lesson covers strategies for handling AI-generated error scenarios.

Include Expected Edge Cases

Good code handles edge cases. Tell the AI what they are.

Create a function that merges two sorted arrays.

Edge cases to handle:
- One or both arrays are empty → return the non-empty array or empty array
- Arrays contain duplicate values → keep all duplicates in sorted order
- Arrays are different lengths → merge completely
- Input contains negative numbers → sort correctly
- Very large arrays (10,000+ items) → maintain O(n) time complexity

By naming edge cases, you ensure they're tested and handled.

Request the Right Level of Abstraction

Be explicit about whether you want low-level implementation or high-level design.

For implementation:

Write the complete implementation of a LRU cache class with:
- get(key) method
- put(key, value) method
- Maximum capacity of 100 items
- O(1) time complexity for both operations
- Use a Map and doubly-linked list

For design:

Describe the high-level architecture for a LRU cache system.
Include:
- Data structures needed
- Key methods and their signatures
- Time complexity analysis
- Memory considerations

Don't write the implementation yet.

This prevents the AI from over- or under-delivering. Related techniques appear in ai-architecture.

Common Clarity Mistakes to Avoid

Mistake 1: Assuming Shared Context

Bad:

Add the validation we discussed

The AI has no memory of previous conversations unless you provide context.

Good:

Add email and password validation:
- Email must match regex: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
- Password must be at least 8 characters with 1 number and 1 special char

Mistake 2: Using Domain Jargon Without Definition

Bad:

Implement the standard OAuth flow

"Standard" is ambiguous. OAuth has multiple flows.

Good:

Implement OAuth 2.0 Authorization Code flow:
1. Redirect to /authorize endpoint
2. Receive callback with code
3. Exchange code for access token
4. Store token securely

Mistake 3: Conflicting Requirements

Bad:

Make it fast and handle all edge cases thoroughly

Speed and thoroughness can conflict.

Good:

Prioritize performance for the happy path (90% of requests).
Edge case handling can be slower but must be correct.
Target: <50ms for valid input, <200ms for invalid input.

Mistake 4: Asking for Opinions

Bad:

What's the best way to structure this?

This invites bikeshedding.

Good:

Structure this using the repository pattern with:
- Interface defining data operations
- Concrete implementation for PostgreSQL
- Separate file for each

Make decisions, then ask for implementation. The top-mistakes lesson covers more pitfalls.

Putting It All Together: A Complete Example

Here's a well-structured prompt that combines all these principles:

Create a rate limiting middleware for Express.js.

Purpose:
Protect our API from abuse by limiting requests per user.

Requirements:
- Track requests by IP address
- Allow 100 requests per 15-minute window
- Use sliding window algorithm (not fixed window)
- Store request counts in Redis
- Return 429 status when limit exceeded

Response headers:
- X-RateLimit-Limit: maximum requests allowed
- X-RateLimit-Remaining: requests left in current window
- X-RateLimit-Reset: timestamp when limit resets

Error response format:
{
  "error": "Rate limit exceeded",
  "retryAfter": <seconds until reset>
}

Edge cases:
- If Redis is unavailable: log error and allow request (fail open)
- If IP address is missing: use 'unknown' as identifier
- Reset counter properly when window slides

Code style:
- Use async/await
- Add TypeScript types
- Include JSDoc comments
- Export as default function

This prompt leaves no ambiguity. The AI has everything needed to generate production-quality code.

Practice: Improving Your Prompts

The best way to develop this skill is deliberate practice. After each AI interaction:

  1. Review the output: Did it match your intent?
  2. Identify gaps: What was unclear in your prompt?
  3. Refine and retry: Rewrite the prompt more clearly
  4. Save patterns: Keep prompts that worked well

Over time, you'll develop prompt templates for common tasks. These become part of your workflow, making you faster and more consistent.

Next Steps

Clear instructions are the foundation of effective prompting, but there's more to learn:

Master clear instructions first. Everything else builds on this foundation. The difference between developers who struggle with AI and those who thrive often comes down to communication clarity. You're now equipped to be in the latter group.