| name | typescript-code-review |
| description | Review TypeScript for type safety, security, performance, maintainability. Makes invalid states unrepresentable via branded types, discriminated unions, template literals, satisfies, infer. Use for TypeScript review, type audits, antipatterns, type-level correctness. |
TypeScript Code Review
Review TypeScript code with a focus on making invalid states unrepresentable. The type system is the most powerful correctness tool available — when types prevent invalid states at compile time, LLMs and humans alike produce fewer bugs because mistakes don't compile.
Review Workflow
Copy this checklist and track progress:
TypeScript Review Progress:
- [ ] Step 1: Check tsconfig.json strictness
- [ ] Step 2: Audit for impossible-state patterns
- [ ] Step 3: Scan for type safety issues
- [ ] Step 4: Check for security vulnerabilities
- [ ] Step 5: Identify performance problems
- [ ] Step 6: Evaluate code quality and patterns
- [ ] Step 7: Structure findings with severity levels
Step 1: Check tsconfig Configuration
Read tsconfig.json first — it establishes the type-safety baseline.
Minimum required:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}
Flag missing strict settings. Document which checks are disabled and why.
Step 2: Audit for Impossible-State Patterns
This is the highest-value review area. Improving types to make invalid states unrepresentable is the single most impactful change for long-term maintainability — especially when LLMs edit the codebase, because the compiler catches mistakes before they ship.
For detailed patterns and examples, see references/impossible-states.md.
Check for these opportunities:
-
Branded types — Are domain primitives distinguished? A UserId and OrderId are both strings but must not be interchangeable. See references/impossible-states.md for branded type patterns.
-
Discriminated unions with mutual exclusion — Can two properties be set simultaneously when only one should be? Use type discriminators and ?: never to make invalid combinations unrepresentable.
-
never for exhaustiveness — Do switch statements cover all cases? A default: const exhaustive: never = value pattern ensures new union members trigger compile errors.
-
satisfies over type assertions — Does as X lose literal type information? satisfies validates structure while preserving narrow types.
-
Template literal types — Are string formats (CSS values, API paths, event names) typed as string when they could be constrained to valid patterns?
-
Literal unions with string & {} — Could autocomplete be improved while still accepting unknown values? type MimeType = 'image/png' | 'image/jpeg' | (string & {}) gives IDE suggestions without restricting valid inputs.
-
infer for type extraction — Are complex types manually constructed when they could be derived from source types using conditional inference?
Step 3: Type Safety Scan
Search for these issues in order of severity:
any usage — Replace with unknown + type guards or proper interfaces. Every any makes invalid states representable.
- Unsafe type assertions —
as X bypasses the type system. Prefer type guards, satisfies, or narrowing.
- Type predicates over
filter(Boolean) — Replace .filter(Boolean) as X[] with .filter((n): n is X => condition) to preserve narrowing without a manual cast.
- Missing return types — Explicit return types catch errors at function boundaries.
- Improper null handling — Use
?. and ?? instead of manual checks. Check array index access with noUncheckedIndexedAccess.
- Unnarrowed unions — Discriminated unions need a
type field for reliable narrowing. See references/common-antipatterns.md.
- Runtime/runtime type sync — Are type definitions and runtime values defined separately? Use
as const + typeof X[number] to derive types from a single runtime source, or Zod schemas with z.infer<> as the single source of truth.
- Missing runtime validation at trust boundaries — External data (API responses, form input, env vars) needs runtime validation. Define schemas once with Zod and infer types:
type User = z.infer<typeof UserSchema>. Use parse at trust boundaries (throws on invalid) and safeParse for user input (expected failures).
Step 4: Security Review
Check for high-impact issues first. See references/security-checklist.md.
- SQL/NoSQL injection — String concatenation in queries → parameterized queries.
- XSS —
innerHTML with unsanitized input → textContent or DOMPurify.
- Hardcoded secrets — Source code secrets → environment variables with Zod validation.
- Path traversal — Unvalidated file paths → normalize and validate against base directory.
- Missing input validation — External data needs runtime validation at trust boundaries (Zod, io-ts).
Step 5: Performance Review
See references/performance-tips.md.
- Sequential awaits —
Promise.all for independent operations.
- O(n²) algorithms —
Map/Set for O(1) lookups.
- Memory leaks — Event listeners, subscriptions, timers without cleanup.
- Bundle size — Named imports over whole-library. Dynamic imports for heavy modules.
- React specifics — Missing
memo/useMemo/useCallback, unstable object deps.
- Loops — Prefer
for...of over forEach for performance and clarity. Use map/filter/reduce when constructing new collections.
- String building — Prefer template literals over string concatenation (
+). Template literals are faster and more readable.
Step 6: Code Quality Evaluation
- Naming — camelCase variables/functions, PascalCase types/interfaces. Use
is/has prefixes for booleans (isActive, hasPermission). Names should reveal intent: isLegalDrinkingAge() over isOverEighteen().
- Immutability —
const, readonly, spread over mutation. readonly T[] for input arrays. Prefer pure functions (same input → same output, no side effects).
- Magic numbers — Replace unnamed literals with named constants:
const MS_PER_DAY = 24 * 60 * 60 * 1000; instead of 86400000.
- Function parameters — Limit to 2-3 parameters. Use object parameters for more. Avoid boolean flags that change behavior — split into separate functions or use discriminated unions.
- Error handling — Catch
unknown errors. Throw Error objects (custom error classes for domain). Never throw strings or plain objects.
- Import hygiene —
import type for type-only imports. No barrel exports that load everything.
- Modern patterns —
satisfies, as const, template literal types, branded types.
- Control flow — Always use block statements (
{}) for if/for/while — no single-line bodies. Prefer early returns over nested if/else chains. No dangling else blocks after return.
- No non-null assertions — Avoid
! on property access. Use ?., ??, or explicit null checks. Indexed array access (arr[i]!) and Map.get()! are acceptable workarounds for noUncheckedIndexedAccess.
- No eval or Function constructor — Never use
eval() or new Function() — they allow arbitrary code execution, disable tree-shaking, and defeat static analysis.
Step 7: Structure Findings
## Summary
[1-2 sentence overview and main concern]
## Critical Issues 🔴
[Must-fix: security, type errors, bugs]
## Important Improvements 🟡
[Should-fix: impossible-state patterns, anti-patterns, maintainability]
## Suggestions 🔵
[Nice-to-have: style, modern syntax, minor optimizations]
## Positive Observations ✅
[What the code does well]
## Detailed Findings
### [Category]
**File**: `path/to/file.ts:line`
- **Issue**: [Description]
- **Current**:
```typescript
[code]
- Recommended:
[code]
- Reasoning: [Why]
## Framework-Specific Checks
**React + TypeScript**:
- Component prop interfaces over inline types
- Proper hook return types (`useState<User | null>(null)`)
- Event handler types (`React.MouseEvent<HTMLButtonElement>`)
- `useRef<HTMLDivElement>(null)` for DOM refs
- `React.ReactNode` for children, not `React.ReactElement`
**Node.js + TypeScript**:
- Typed Express/Fastify handlers
- Async error handling middleware
- Zod-validated environment variables at startup
- Database result typing (never `any` from query results)
## Automated Checks
Recommend these tools if not already in use:
```bash
tsc --noEmit # Type checking
npx eslint . --ext .ts,.tsx # Linting with @typescript-eslint
npx ts-prune # Find unused exports
npx depcheck # Find unused dependencies
npx madge --circular src/ # Detect circular dependencies