Andrej Karpathy posted the term in February 2025: "There's a new kind of coding I call 'vibe coding', where you fully give in to the vibes, embrace exponentials, and forget that the code even exists." (source) Forty thousand retweets. Every developer recognized themselves in it.
Vibe coding isn't reckless. It's what happens when AI tools get good enough that you can describe intent and review output instead of writing every line. I've shipped production features in two hours that would have taken two days with Cursor. That speed is real, and I don't want to give it back.
But here's what I can't stop thinking about: CodeScene's 2026 research found that AI-generated code has 1.7x more defects than human-written code. Vibe-coded projects accumulate technical debt 3x faster. And 94% of those AI errors are type-related.
That last number changes the conversation. If nearly all AI code quality failures are type errors, and TypeScript exists specifically to catch type errors at compile time, then TypeScript isn't a nicety for vibe coding. It's the only safety net that makes it sustainable.
TL;DR:
- AI code has 1.7x more defects; 94% are type-related (CodeScene 2026)
- TypeScript
strict: truecatches most of these at compile time, not at runtime- Define types before prompting AI, better output AND automatic validation
- The full framework configures in under 30 minutes
Why Pure Vibe Coding Fails at Scale
When you ask an AI to "add a user dashboard," it writes plausible-looking code fast. That's the problem. "Plausible" and "correct" aren't the same thing. AI models predict what tokens should follow, they don't execute your code mentally and verify the logic.
The defects follow predictable patterns. I've seen the same ones dozens of times across different projects:
- Functions typed as returning
anywhen the actual shape matters downstream - API responses wrapped in
Record<string, unknown>instead of a real interface - Optional chaining skipped:
user.profile.nameinstead ofuser.profile?.name - Array access assumed safe:
items[0].idon a list that might be empty
None of these fail in development. All of them crash in production, triggered by the exact user who hits the edge case. Sound familiar?
TypeScript with strict: true catches every single pattern above at compile time, before a single line reaches your users.
The Quality-First Framework
Step 1: Types Before Prompts
Don't ask AI to "write a function that fetches user orders." Instead, define the shape of the data first:
interface Order {
id: string;
userId: string;
items: OrderItem[];
status: 'pending' | 'shipped' | 'delivered' | 'cancelled';
total: number;
createdAt: Date;
}
interface FetchOrdersResult {
orders: Order[];
nextCursor: string | null;
total: number;
}
Now prompt with the contract attached: "Write a fetchOrders(userId: string, cursor?: string): Promise<FetchOrdersResult> function using these types."
Two things happen. The AI has a precise contract rather than guessing the return shape. And when it generates the implementation, TypeScript checks that the output actually satisfies FetchOrdersResult. Wrong shape? Compile error immediately, not a silent crash six hours later in production.
Step 2: The Minimum tsconfig
Your tsconfig.json for any vibe coding project:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"target": "ES2022",
"moduleResolution": "Bundler"
}
}
strict: true is non-negotiable, it bundles eight flags that together catch the most common AI errors. noUncheckedIndexedAccess makes array indexing return T | undefined, so items[0] forces you to handle the empty case rather than assume. exactOptionalPropertyTypes stops AI from assigning undefined to optional properties that don't accept it.
The full breakdown of every strict flag is in the TypeScript strict mode guide.
Step 3: typescript-eslint Catches What tsc Misses
The TypeScript compiler and ESLint with typescript-eslint have different coverage. The compiler checks types. ESLint catches patterns that are technically valid TypeScript but almost always indicate AI-generated bugs:
npm install -D @typescript-eslint/eslint-plugin @typescript-eslint/parser
Add to your ESLint config:
{
"extends": ["plugin:@typescript-eslint/strict-type-checked"],
"parserOptions": {
"project": true
},
"rules": {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/no-unsafe-assignment": "error"
}
}
These three rules block any, the ! non-null assertion operator, and unsafe assignments. AI assistants reach for all three constantly when they can't infer an exact type. Treating them as errors forces the AI (or you in the review) to be explicit.
Step 4: Zod at Runtime Boundaries
TypeScript disappears at runtime. External API responses can return anything regardless of what your types say. The one gap in TypeScript's protection is the boundary where external data enters your system, fetch responses, localStorage reads, URL params.
Zod closes that gap:
import { z } from 'zod';
const OrderSchema = z.object({
id: z.string(),
userId: z.string(),
items: z.array(z.object({
productId: z.string(),
quantity: z.number().positive(),
price: z.number()
})),
status: z.enum(['pending', 'shipped', 'delivered', 'cancelled']),
total: z.number()
});
type Order = z.infer<typeof OrderSchema>; // TypeScript type from schema, free
const response = await fetch('/api/orders/123');
const data = OrderSchema.parse(await response.json());
// data is fully typed AND validated, bad shapes crash loudly at the boundary
You get full TypeScript inference for free via z.infer, and any malformed API response fails loudly at the entry point rather than corrupting data silently three function calls deep.
What This Actually Gives You
Write types first, generate code second, let TypeScript and ESLint check the output. That's the complete framework.
Is it slower than pure vibe coding? No. I've measured it. The type definitions take 5-10 minutes. The CI check on push takes 30 seconds. What you skip is the production debugging session two weeks later that costs a full day. The friction moves forward, closer to where you can actually fix things cheaply.
The real win is compounding. Each project you build with this framework produces reusable type definitions that speed up the next project. AI generates better code when you feed it well-typed context. And you build the habit of thinking about data shapes before implementations, which makes you a better reviewer of AI output, not just a faster shipper.
For the deeper connection between TypeScript types and AI assistant quality, how well-typed context measurably improves Copilot and Claude Code output, see TypeScript types for AI.