← Back to writing

Building full-stack AI products with clear state boundaries

April 7, 20265 min read

Why state boundaries matter

AI products feel simple from the outside. A user asks a question, the system responds, and the conversation continues.

Under the hood, the product usually has several different kinds of state:

  • transient UI state for loading, tabs, drawers, and small interactions
  • persisted product state for chats, users, and usage data
  • authenticated session state for access and protected actions
  • billing or quota state for limits, upgrades, and webhook-triggered changes

When those boundaries are unclear, every new feature becomes harder to reason about.

What I optimize for

I prefer to keep each state category attached to the part of the system that owns it.

  • UI state stays close to the interface unless it needs to be shared globally.
  • Product state is shaped around durable records in the database.
  • Auth state is treated as infrastructure, not as scattered conditional logic.
  • Payment state is updated from explicit events instead of optimistic guesses.

That creates a cleaner path from user action to backend write.

A simple mental model

When I build a product-style feature, I usually trace it through a sequence like this:

type Flow = {
  clientAction: string;
  apiRequest: string;
  databaseWrite: string;
  asyncUpdate?: string;
};

The point is not the type itself. The point is making the lifecycle visible before complexity spreads across the codebase.

Where this becomes valuable

This matters most in products that combine AI with account systems or payments. Those products often need:

  • authenticated access before a request can run
  • persisted history so the experience survives refreshes and follow-ups
  • usage tracking or payment checks before expensive actions
  • backend-driven updates after an external system confirms status

That is why I enjoy building them. They are good examples of full-stack work that requires both frontend clarity and backend discipline.