Introduction: Why "It Just Compiles" Is a Bad Criterion
Imagine a situation: you open a Pull Request on GitHub, see a green CI checkmark, and hear an inner voice: "The types match, so the code is correct." Sound familiar? If so, you are falling into a trap we call "TypeScript's false sense of security".
TypeScript is not a panacea. It doesn't catch logical errors, doesn't verify business requirements, and certainly doesn't guarantee your code won't break at runtime. That's why code review on TypeScript isn't about "glancing over it," but about a systematic approach. In this article, I'll tell you what you really should look at when reviewing TypeScript code. I run a blog on PythonLib, and here we are used to digging deep — get ready.
1. The `any` Type: A Red Flag
The most obvious thing, but somehow the most common. any is the TypeScript off switch. If you see any in the code, your task is to understand: is it the author's laziness or a truly justified case?
Bad example:
function processData(data: any) { return data.name.toLowerCase();}Here we lose all type information. If data comes in as null or undefined — hello, runtime error.
Good example:
interface UserData { name: string; age?: number;}
function processData(data: UserData) { return data.name.toLowerCase();}What to check during code review: If any is still used (e.g., for working with JSON of unknown structure), does the author require input validation? Often, any masks the lack of proper typing for external data.
2. Strict Mode: Don't Disable It
The first thing I check in tsconfig.json is the flag strict: true. If it's missing, you are depriving yourself of a whole layer of checks: strictNullChecks, noImplicitAny, strictFunctionTypes, and others.
Without strictNullChecks, you could write:
const user = getUser(); // might return nullconst name = user.name; // TS won't complain, but it will crash at runtimeDuring code review: If you see a project running without strict mode — sound the alarm. This is technical debt that accumulates exponentially. It's better to spend a day enabling strict mode and fixing errors than a month catching bugs in production.
3. Generics: Power and Responsibility
Generics are TypeScript's superpower. But with great power comes great responsibility. During code review, I always look: did the author overcomplicate the types?
Example of unnecessary complexity:
function identity<T extends Record<string, unknown>>(arg: T): T { return arg;}Why constrain T to Record<string, unknown> if we are just returning the same thing? It's enough:
function identity<T>(arg: T): T { return arg;}What to check:
- Are generics used where flexibility is needed, or could a specific type suffice?
- Are there excessive constraints (extends) that hinder reusability?
- Are types inferred correctly — does the user have to manually specify
<SomeType>where TS could have figured it out on its own?
4. unknown vs any: Choose the Right Weapon
When working with data whose type you don't know (API, localStorage, user input), use unknown, not any. unknown forces you to explicitly check the type before using it.
Bad:
<