TypeScript Code Review: What AI Catches and Misses in 2026
TypeScript's type system catches some bugs automatically. AI code review catches others. Here's what tsc misses, what AI review adds, and what still needs a human eye in 2026.
Tired of slow code reviews? AI catches issues in seconds. You decide what gets published.
TypeScript Code Review: What AI Catches and Misses in 2026
TL;DR: TypeScript handles a specific class of bugs. Once you define your types carefully, the compiler rejects entire categories of mistakes at build time. The illusion is that this makes code review easier. It does not — it just changes what you are reviewing for. The compiler proves type consistency. AI review catches the patterns the compiler cannot see: any escapes, lying type assertions, missing exhaustiveness, unsafe non-null assertions, over-broad generics. Humans still own the logic, the architecture, and the complex type relationships AI gets wrong. This article walks through all three layers with the bad-code examples that show up in real PRs.
TypeScript adoption has crossed the threshold where it is the default rather than the choice. State of JS 2024 measured 67% of respondents writing more TypeScript than JavaScript, with the largest single group writing only TypeScript. Stack Overflow's 2024 Developer Survey put TypeScript at 38.5% of all respondents and 43.4% of professional developers — fifth among programming languages overall, ahead of Bash, Java, and C#. The conversation has shifted from "should we adopt TypeScript" to "what does review even mean now that the compiler does so much."
The honest answer is that the compiler does a narrow job extremely well, and a lot of other jobs that look related not at all. This article walks through what tsc actually proves, the five TypeScript-specific patterns AI review catches in almost every PR we see, the type-system territory AI gets wrong, and the three-layer check we recommend for any team shipping AI-assisted TypeScript code.
What TypeScript's type system actually catches (and what it doesn't)
The compiler proves one specific property of your code: the types you wrote are internally consistent with each other. If getUser returns Promise<User> and you call getUser().email, the compiler will tell you Promise<User> has no email property. If you pass a number to a function that expects string, the compiler will refuse. If you forget to handle the case where .find() returns undefined and you have strictNullChecks on, the compiler will catch the dereference. That work is real, and it eliminates a category of mistakes that used to live in production.
What the compiler cannot prove is anything about the values that actually flow through your code at runtime. The signature function getUser(id: string): User does not guarantee the function returns a User. It guarantees the function's static analysis assumes a User is returned. If the function reaches into cache.get(id) as User on a cache miss, the compiler sees a User. The runtime sees undefined, and your .email access crashes with the same TypeError you would have gotten in plain JavaScript.
This is the core gap every TypeScript code review has to handle. The compiler is doing static reasoning about types. The bugs that ship are about runtime values that disagree with the static reasoning. There are five recurring ways the runtime values escape what the compiler sees:
anypropagation. Once a value isany, the TypeScript handbook is explicit: "Usinganydisables all further type checking." Property access onanyreturnsany. Assignments fromanyto typed variables succeed silently. OneJSON.parse(x) as anycan quietly remove type safety from an entire call chain.- Type assertions (
as Type). Type assertions are removed at compile time. There is no runtime check. If the assertion is wrong, no exception fires until your code accesses a missing property. - Index access on arrays and records.
arr[0]returnsT, notT | undefined, unlessnoUncheckedIndexedAccessis enabled. That flag is separate fromstrict: trueand most projects do not enable it. - Narrowing failures. Complex conditional logic can fool the narrower. A check that should refine a type sometimes does not, and the compiler accepts code that would crash on the path it could not reason about.
- Strict mode gaps. TypeScript's
strict: trueis opt-in. Codebases without it run withnoImplicitAny: false,strictNullChecks: false, andstrictPropertyInitialization: false. A parameter declared with no type defaults toany, aUser | undefinedcan be dereferenced without a check, and uninitialized class properties compile cleanly.
That last point is the one most teams underestimate. The compiler enforces only the flags you turned on. Strict mode is a family — noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables, and alwaysStrict — and a codebase running with three of the eight has a meaningfully different safety profile from a codebase running with all eight plus noUncheckedIndexedAccess. Most code review tools assume the strictest setting and surface findings that the local tsc configuration would not catch.
TypeScript-specific patterns AI review catches well
These five patterns show up in nearly every AI-generated TypeScript PR we see. They are the bread-and-butter findings — boring, obvious in hindsight, and missed often enough that automating the check has real value.
Pattern 1: any escape hatches around parsed input
// AI-generated — bypasses all type safety on the response
const userData = JSON.parse(apiResponse) as any;
userData.nonExistentField.nested.value; // No error from tsc, crashes at runtime
// Better — uses unknown plus a runtime guard
const userData: unknown = JSON.parse(apiResponse);
if (isUserData(userData)) {
// userData is now narrowed to UserData
console.log(userData.email);
}
function isUserData(value: unknown): value is UserData {
return (
typeof value === "object" &&
value !== null &&
"email" in value &&
typeof (value as { email: unknown }).email === "string"
);
}
The JSON.parse(apiResponse) as any pattern is the single most common type-safety leak in AI-generated TypeScript. It is shorter to write than a guard, the compiler stops complaining, and the runtime behavior looks fine on the happy path. The day the API returns an unexpected shape, you find out that nothing was ever checking the data. AI review catches this because the diff signature is recognizable: a JSON.parse immediately followed by as any or an unsafe assignment.
Pattern 2: Unsafe type assertions without runtime validation
// AI-generated — asserts cache returns User, but cache.get returns undefined on miss
function getUserEmail(id: string): string {
const cached = cache.get(id) as User;
return cached.email; // Crashes when cache misses
}
// Better — handle the actual return type
function getUserEmail(id: string): string | undefined {
const cached = cache.get(id);
if (!cached) return undefined;
return cached.email;
}
The TypeScript handbook is direct on this: "Because type assertions are removed at compile-time, there is no runtime checking associated with a type assertion. There won't be an exception or null generated if the type assertion is wrong." AI tools reach for as Type whenever they cannot quickly satisfy the compiler, which makes it the second most common finding. The fix is almost always to handle the actual signature instead of asserting around it.
Pattern 3: Missing discriminated union exhaustiveness
type Status = "loading" | "success" | "error" | "idle";
// AI-generated — handles three cases, forgets one
function render(status: Status) {
if (status === "loading") return <Spinner />;
if (status === "success") return <Content />;
if (status === "error") return <ErrorMessage />;
// 'idle' case missing — tsc does not flag this without explicit exhaustiveness
}
// Better — switch with never check forces exhaustiveness
function render(status: Status) {
switch (status) {
case "loading": return <Spinner />;
case "success": return <Content />;
case "error": return <ErrorMessage />;
case "idle": return <Placeholder />;
default: {
const _exhaustive: never = status;
throw new Error(`Unhandled status: ${_exhaustive}`);
}
}
}
The never exhaustiveness pattern is documented in the TypeScript handbook under Narrowing. The trick is that never is assignable to every type but no type is assignable to it (except never itself). When all union members are handled, the default branch has narrowed status to never and the assignment succeeds. The day a teammate adds "retrying" to the Status union without updating the function, the assignment fails to compile. AI review catches the missing case immediately on the diff because the union expansion is visible in the type definition.
Pattern 4: Non-null assertions standing in for null handling
// AI-generated — silences the compiler with !, crashes on null
const root = document.getElementById("app")!;
root.appendChild(newNode); // Throws if #app is missing
// Better — handle the actual signature
const root = document.getElementById("app");
if (!root) {
throw new Error("App root element missing");
}
root.appendChild(newNode);
The non-null assertion operator (!) tells the compiler "trust me, this is not null." There is no runtime enforcement — when the assertion is wrong, the next access throws a TypeError. AI tools reach for ! whenever the compiler warns about null | undefined and the fix would otherwise be a guard or an early return. The bang is shorter. The trade is real type safety for a few characters saved. Most teams choose to forbid the operator entirely through the ESLint @typescript-eslint/no-non-null-assertion rule, which gets the AI to stop reaching for it.
Pattern 5: Over-broad generics that degrade to any
// AI-generated — generic in name only, no constraint
function first(arr: any[]): any {
return arr[0];
}
// Callers lose all type information about what was in the array
// Better — properly constrained generic
function first<T>(arr: readonly T[]): T | undefined {
return arr[0];
}
// Callers get T | undefined, which propagates through the call chain
The first signature is any[] -> any, which means every caller loses type information. The compiler still accepts the code, but the type system stops doing its job. The fix is a real generic with a constraint — <T>(arr: readonly T[]): T | undefined. AI review surfaces this pattern because the lost-information signature is recognizable in the diff: a function declared with any parameters or return types where a generic would carry information through.
What AI review misses in TypeScript code
The patterns above are the easy wins. There is a separate territory where AI review consistently struggles, and pretending otherwise sets up reviewers for false confidence. These are the type-system corners where deep TypeScript expertise still matters.
Complex conditional types and distributivity. When T extends U ? X : Y is applied to a union, TypeScript distributes the conditional across each member. The resulting type is the union of the per-member results, not a single result for the union as a whole. AI reviewers consistently miss when a conditional type was meant to apply atomically and the distributivity changes the answer. The fix is wrapping the type parameter in a tuple ([T] extends [U]) to opt out of distribution — a rare enough trick that AI tools tend to skip the check entirely.
Template literal type bugs. TypeScript supports type-level string manipulation through template literal types — patterns like `on${Capitalize<K>}`. The expansion rules interact with conditional types and unions in ways that are easy to get wrong. AI review will accept a route-type definition that compiles but does not actually constrain what it appears to constrain. Catching this needs a human who has built the type and run it against test cases at the type level.
Mapped type edge cases. Mapped types ({ [K in keyof T]: ... }) interact with optional properties, readonly modifiers, and the as clause in non-obvious ways. The difference between { [K in keyof T]?: T[K] } and Partial<T> is real in some cases and irrelevant in others. AI reviews tend to approve mapped-type changes that compile without spotting the semantic shift.
Variance errors in complex generic hierarchies. When a generic appears in both input and output positions of a function signature, the variance becomes invariant. Subclasses of the input type are no longer assignable to the parent. This shows up in event-emitter patterns, listener registration APIs, and anywhere a parameter type also appears in a return position. AI review will not catch a variance regression in a refactor because the change still compiles and the test suite usually does not exercise the variance path.
These are the cases where a senior TypeScript reviewer earns their keep. Document them as human-review territory in your team's review policy, and you will stop expecting AI tools to flag work that genuinely needs type-system expertise.
TypeScript code review in practice: the three-layer check
The right model for reviewing AI-generated TypeScript is three layers running in sequence, each catching what the layer above cannot see. Skip any one of them and bugs leak through.
Layer 1: tsc --noEmit in CI. The compiler is fast, deterministic, and cheap. Run it on every push with the strictest flags your codebase will tolerate. strict: true plus noUncheckedIndexedAccess is the upper bound. If the codebase is too noisy for the full set, start with strictNullChecks alone — it eliminates the largest class of runtime crashes for the least migration effort. Bloomberg runs strict mode by default across 50 million lines and 2,000+ engineers, which is the existence proof that the configuration scales.
Layer 2: AI review on every PR. This is the layer that catches the five patterns above. AI review reads the diff against the codebase, flags the recognizable bad shapes, and explains why each one matters. Quick Review runs in under a minute per PR. Our 12-item checklist for AI-generated code covers the broader categories — hallucinated packages, hardcoded secrets, missing tests — that apply to any language; the TypeScript-specific items are the ones above.
Layer 3: Human review on logic, architecture, and complex types. Humans own the parts neither the compiler nor AI can prove: does this code solve the right problem, does the architecture hold up, does the new endpoint route correctly under load, does the complex conditional type actually express what it appears to express. A good AI review pre-screen gives the human reviewer a clean diff to focus on. Our writeup on diff bots versus agentic review covers when the cross-file exploration matters most.
The copy-paste checklist below covers what every PR should pass before the human reviewer opens it. Drop it into .github/PULL_REQUEST_TEMPLATE.md for GitHub, .gitlab/merge_request_templates/ for GitLab, or the Bitbucket PR template settings.
## TypeScript review checklist
- [ ] No new `any` without a comment explaining why
- [ ] Every type assertion (`as Type`) has corresponding runtime validation
- [ ] Discriminated unions use the `never` exhaustiveness pattern in `default`
- [ ] No `!` non-null assertions without a guard proving non-null
- [ ] Generics are constrained — no `<T extends any>` or implicit `any` parameters
- [ ] `strict` mode is not disabled for new files (`// @ts-nocheck` forbidden)
- [ ] `tsc --noEmit` passes locally before push
- [ ] AI review pass on the PR (Quick Review or Deep Review)
Git AutoReview and TypeScript: what it catches automatically
Our 20+ built-in security rules catch the TypeScript anti-patterns above directly on the PR: unconstrained any usage, missing null guards in async chains, unsafe type assertions adjacent to network responses, console.log and debugger artifacts in production paths, and naming inconsistencies relative to the rest of the codebase. The rules run per PR through Quick Review with no configuration — install the extension, connect a repo, and the next PR you open gets the check.
Deep Review goes further when a TypeScript change has cross-file reach. The agent explores the actual usage of a type assertion through the codebase to confirm whether the runtime data ever produces the shape being asserted. If cache.get(id) as User shows up in the diff and the cache implementation is in another file, Deep Review reads both and reports the actual return signature. The mode runs in 2-5 minutes for typical PRs and 5-8 minutes for very large ones — slower than Quick Review, much deeper, and worth running on changes that touch shared types or generics.
BYOK matters specifically for TypeScript review. Your code, your types, and your business logic go directly from VS Code to your AI provider — Anthropic, Google, or OpenAI — at the provider's direct rate, typically $2–5/mo for an active developer. Nothing routes through gitautoreview.com servers. The pricing math: Git AutoReview $14.99/mo flat plus ~$2–5/mo API costs vs CodeRabbit at $24/user/mo bundled. For a team of five TypeScript developers, that is $85–100/mo total versus $120/mo on CodeRabbit, and you keep full control over which model reviews your code.
Related Resources
- Code Review Checklist for AI-Generated Code — 12-item language-agnostic checklist; the TypeScript items above complement it
- Diff Bots vs Agentic Review — when Deep Review's cross-file exploration matters most
- GitHub Code Review Best Practices 2026 — broader review process, automation, and metrics
- AI PR Review Guide 2026 — tooling comparison: CodeRabbit, Copilot, Bugbot, Git AutoReview
- AI PR Review Overview — product page with setup walkthrough for GitHub, GitLab, and Bitbucket
Pricing footnote: Git AutoReview $14.99/mo Team plan is subscription only. The ~$2–5/mo API costs are paid directly to your AI provider (Anthropic, Google, or OpenAI bills you). CodeRabbit and Qodo bundle AI compute into their per-user price, which makes their headline number larger but eliminates the BYOK control over which model reviews your code.
Tired of slow code reviews? AI catches issues in seconds. You decide what gets published.
Frequently Asked Questions
Does TypeScript eliminate the need for code review?
What TypeScript anti-patterns does AI review catch best?
Should I enable TypeScript strict mode?
What does AI code review add beyond `tsc --noEmit`?
How do I catch missing discriminated union cases?
Does Git AutoReview work with TypeScript monorepos?
Try it on your next PR
AI reviews your code for bugs, security issues, and logic errors. You approve what gets published.
Free: 10 AI reviews/day, 1 repo. No credit card.
Related Articles
Bitbucket Pull Request Automation: Complete Guide 2026
Bitbucket PR automation in 2026: Pipelines triggers, AI code review, merge checks, and how to cut review time by 60% without leaving VS Code. Works on Cloud and Data Center.
Code Review Checklist for AI-Generated Code: 12 Things to Verify
AI writes code faster than developers can review it. Here are 12 things to check in every AI-generated PR — from hallucinated packages to security gaps, logic errors, and test coverage.
GitHub Copilot Code Review Cost 2026: What Changes on June 1
GitHub Copilot Code Review starts consuming Actions minutes on June 1. We broke down exactly what teams of 5, 10, and 20 developers will pay — and when the math tips against staying.
Get the AI Code Review Checklist
25 PR bugs AI catches that humans miss — with real code examples. Free PDF, sent instantly.
One-click unsubscribe. We never share your email.