In my previous post I wrote about five principles for effective collaboration with AI. The first principle — think first, code later — deserves its own story.
Most developers open Claude Code, type a prompt, and let it build. For a small utility file that works fine. But as soon as you ask for a feature of any size, you get code that’s technically correct but doesn’t fit. Wrong patterns, different naming, a structure no one on your team recognizes. You spend more time correcting than you saved.
The problem isn’t that the AI codes poorly. The problem is that you let it run before it knows where it’s going. Plan Mode flips that around. In this mode, Claude Code first reads your codebase, analyzes existing patterns, and proposes an approach — before writing a single line of code. The difference: you don’t get code that works, you get code that fits.
Every time an AI writes code, it makes dozens of decisions. Which pattern do I use? Do I create a new table or extend an existing one? Polling or events? Controller or Minimal API? Without a plan, those choices are invisible — they’re hidden in the code it delivers. You don’t discover them until you open the pull request. And by then, course-correcting is expensive: throw away code, start over, or — worse — just leave it.
Plan Mode doesn’t change whether the AI makes assumptions. It changes when you see them. In a plan, those assumptions are spelled out as proposals. “I want to create a new table.” “I’ll use a controller.” Those are moments where you can say: no, we do that differently. Course-correcting in a plan takes seconds. Course-correcting in code takes hours.
Without a plan
Say your team is building an ordering platform in ASP.NET Core. A request comes in: users want to receive notifications when the status of their order changes. You open Claude Code and type:
“Build a notification system for order status changes.”
Claude Code gets right to work. No questions, no analysis — it delivers working code. But look at what it produces:
- It creates a standalone
NotificationServicewith its own logic — while your team uses MediatR for command/query separation and everything runs through handlers. - It creates a new
Notificationstable with its own migration — while your existingAuditLogalready tracks events and can be extended. - It implements a polling mechanism that checks the database every 30 seconds — while you have an event-based architecture with
IEventHandler<T>.
And then the structure. Your entire API is built with Minimal APIs. No controllers, no [Route] attributes. But Claude delivers this:
[ApiController]
[Route("api/[controller]")]
public class NotificationsController : ControllerBase
{
private readonly NotificationService _service;
public NotificationsController(NotificationService service)
{
_service = service;
}
[HttpGet("{userId}")]
public async Task<IActionResult> GetNotifications(int userId)
{
var notifications = await _service.PollForUpdates(userId);
return Ok(notifications);
}
}
Technically correct. Compiles. Does what you asked. But it doesn’t fit your project in any way. A colleague seeing this in a pull request would think it was copied from a different project.
The problem isn’t that Claude gets it wrong — it doesn’t know what “right” means in your project. Without a plan, it lacks the context to make the right choices.
With a plan
Same scenario, same feature — but a different approach. This time you give Claude context and ask it to think first.
Activating Plan Mode
You open Claude Code and write a prompt that gives direction:
“We want a notification system for status changes. Go into plan mode and first analyze how we handle events in /Application/EventHandlers. Look at our existing architecture before proposing anything.”
Claude Code switches to Plan Mode — it reads and analyzes before writing anything.
Explore
Claude dives into your codebase and comes back with findings:
I’ve analyzed the codebase. Here’s what stands out:
- In
Application/EventHandlers/I find a consistentIEventHandler<T>pattern. All domain events are handled through this interface.- MediatR is used for command/query separation — commands go through
IRequestHandler, queries throughIRequestHandler<TRequest, TResponse>.- In the data layer there’s an
AuditLogentity that already tracks events with fields likeEntityId,Type, andMessage. This is used project-wide for traceability.
The key: Claude understands your patterns before proposing anything. It has read, not guessed.
The plan
Based on that analysis, Claude comes up with a plan:
- New
OrderStatusChangedEventthat fits the existing event pattern- A
NotificationHandlerthat implementsIEventHandler<OrderStatusChangedEvent>- Extend the existing
AuditLogentity with notification fields — no new table- A Minimal API endpoint for fetching notifications, via a MediatR query
No new system, but an extension that fits what’s already there.
The result
You approve the plan. Claude implements.
The event handler:
public class NotificationHandler : IEventHandler<OrderStatusChangedEvent>
{
private readonly IMediator _mediator;
public NotificationHandler(IMediator mediator)
{
_mediator = mediator;
}
public async Task Handle(OrderStatusChangedEvent @event)
{
await _mediator.Send(new CreateAuditLogCommand
{
EntityId = @event.OrderId,
Type = "notification",
Message = $"Order status changed to {@event.NewStatus}"
});
}
}
The API endpoint:
app.MapGet("/api/notifications/{userId}", async (int userId, IMediator mediator) =>
{
var result = await mediator.Send(new GetNotificationsQuery(userId));
return Results.Ok(result);
})
.WithName("GetNotifications")
.WithTags("Notifications");
No controller, no polling, no standalone service. The same feature as in the previous example — but now built as if your own team wrote it.
Claude runs dotnet build and dotnet test — all green.
Not for everything
Plan Mode isn’t for everything. A bugfix with a clear stack trace, a typo, a config change — you don’t need a plan for that. That’s overhead with no added value.
Rule of thumb: if you can already picture the result, you don’t need a plan. If you’re unsure about the approach, you do.
Think first
The AI is always going to make choices. The question isn’t whether, but when you see them. In a plan you read: “I’m going to create a new NotificationService.” Then you can say: no, use our existing handlers. In code you only discover that same decision as a complete implementation you have to throw away.
The best code is code that looks like your team wrote it. Plan Mode is how you get there — not by typing more, but by making the assumptions visible before they end up in your codebase.
Next time you build a feature, start with: “Go into plan mode and first analyze how we do this in the existing codebase.” You’ll notice the difference.