When a human writes the code, types help them. When an agent writes the code, types police it. In the agent era, your type system is a verification layer that never sleeps.
TL;DR
- Strong types catch a whole class of agent mistakes automatically, before review.
- Make illegal states unrepresentable so the agent cannot produce them.
- Push validation to the boundaries; trust types inside.
- Types are also the best context you can give an agent about your code.
Why types matter more now
A type error is a bug the compiler catches for free. When agents generate code at volume, that free check scales beautifully — every line the agent writes gets verified against your types instantly, with no human in the loop. The stronger your types, the more agent mistakes turn into red squiggles instead of production incidents.
Types also feed the agent: a well-typed codebase tells the model exactly what shapes are expected, so it generates code that fits rather than guessing.
Make illegal states unrepresentable
The highest-leverage pattern: design types so wrong code does not compile. An agent (or a human) cannot reach a state the type system forbids.
// Weak: every combination is "valid," including nonsense
type Request = {
status: 'loading' | 'success' | 'error'
data?: Response
error?: Error
}
// Strong: the shape encodes the rules — no data on error, no error on success
type Request =
| { status: 'loading' }
| { status: 'success'; data: Response }
| { status: 'error'; error: Error }
With the discriminated union, an agent literally cannot write code that reads data in the error case — the compiler stops it. You have moved a class of bug from "caught in review, maybe" to "impossible."
Every illegal state you make unrepresentable is a code review you never have to do.
Validate at the boundaries
Types are a compile-time fiction; at runtime, external data is whatever actually arrives. Parse and validate at every boundary — API responses, form input, agent tool output — then trust the types inside.
import { z } from 'zod'
const User = z.object({ id: z.string(), email: z.string().email() })
// Parse once at the edge; the rest of the code gets a guaranteed-valid type
const user = User.parse(await res.json())
This matters doubly for agent output: treat what an agent returns as untrusted input and validate it into a known shape before it flows through your system.
Patterns worth standardizing
unknownoverany. Force a check before use;anysilently disables the very safety you want.- Branded types for IDs and units so a
UserIdcannot be passed where anOrderIdis expected. satisfiesto keep precise inferred types while checking against a constraint.- Strict config.
strict: true, no implicitany, no unchecked indexed access — the agent inherits whatever rigor you set.
The takeaway
In an AI-era codebase, types are not developer sugar — they are an always-on verification layer and the clearest context you can hand an agent. Invest in making your types tight and your illegal states unrepresentable, and a large fraction of agent mistakes never make it past the compiler. That is leverage that compounds with every line the agent writes.