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. With Cursor, a production feature that used to take two days can ship in two hours. That speed is real, and nobody who's felt it wants 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.
Quick take: Vibe coding is prompting an AI assistant and shipping its output after only a brief review. It works only if TypeScript catches the mistakes: CodeScene's 2026 research found AI-generated code has 1.7x more defects, 94% type-related. Four gates, strict tsconfig, typescript-eslint, Zod at the boundary, types before prompts, configure in under 30 minutes.
Why Does Pure Vibe Coding Fail 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.
What Is the Quality-First Framework?
The quality-first framework is a set of four sequential TypeScript checks, types before prompts, a minimum strict tsconfig, typescript-eslint's strict-type-checked ruleset, and Zod validation at runtime boundaries, that together catch the overwhelming majority of the type-related defects AI assistants introduce. Each gate targets a different failure mode: wrong data shapes, unsafe array access, unsafe assertions, and malformed data crossing the network boundary. According to CodeScene's 2026 research, the four gates together take under 30 minutes to configure once and then run automatically on every commit, no manual review step required for the type-level checks. Here's each gate in order.
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 Do the Four Quality Gates Catch at a Glance?
Run through the gates in this order when setting up a new vibe-coded project:
- Write the interface or type before you write the prompt, so the AI has a contract to satisfy.
- Turn on
strict: trueplusnoUncheckedIndexedAccessandexactOptionalPropertyTypesin tsconfig, one-time setup. - Add typescript-eslint's
strict-type-checkedruleset to blockany, non-null assertions, and unsafe assignments. - Wrap every external data boundary, fetch responses, localStorage, URL params, in a Zod schema.
| Gate | Catches | Setup time |
|---|---|---|
| Types before prompts | Wrong data shapes, guessed property names | 5-10 minutes per feature |
| Minimum tsconfig (strict, noUncheckedIndexedAccess) | Null derefs, unsafe array access, bad optional props | One-time |
| typescript-eslint strict-type-checked | any, non-null assertions, unsafe assignments | One-time |
| Zod at runtime boundaries | Malformed API responses at the entry point | Per external data source |
Should You Add a Fifth Gate: AI Code Review?
Type-level gates catch type errors before they ship, and the four above do that job well, but they don't catch everything, and by mid-2026 the numbers behind vibe coding got bigger, not smaller. Roughly 72% of developers now use AI coding tools daily, and an estimated 41% of code written today started as an AI suggestion. That's a lot of code passing through a reviewer who may only skim it.
This is where a dedicated AI code review layer earns its place alongside TypeScript and ESLint, not instead of them. Tools like CodeRabbit Pro run dozens of scanner layers against a pull request and flag security issues, logic gaps, and inconsistencies that a type checker was never built to catch, injection risks, leaked credentials, or a function that compiles cleanly but contradicts the ticket it was supposed to close. Think of it as gate five: types before prompts, strict tsconfig, typescript-eslint, Zod at the boundary, and then an AI reviewer scanning the diff before a human signs off.
One thing hasn't changed, though, and I'd argue it's the most important part of this whole framework: don't let AI be the only reviewer of AI-written code. Teams leaning hard into vibe coding actually need a stronger human review gate, not a weaker one, because the volume of code moving through the pipeline went up. A CI check that flags any and a bot that flags a suspicious diff both reduce risk. Neither replaces a human asking "does this actually do what we needed?"
What Does This Framework Actually Give You?
Compounding quality is the effect where each well-typed project produces reusable type definitions and habits that make the next project faster and safer, rather than every project starting from zero. 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.