Why TypeScript is needed, pros and cons
TypeScript is a superset of JavaScript that adds static types and catches type errors before your code ever runs.
Theory
TL;DR
- JavaScript crashes at runtime when you pass the wrong data type; TypeScript flags it in your editor while you type
- Analogy: JavaScript is like writing a check without a balance check; TypeScript is banking software that blocks the overdraft before you submit
- TypeScript compiles to plain JavaScript, so browsers and Node.js run it unchanged
- Use TS for teams of 5+ or codebases over 10k lines; skip it for quick one-off scripts
Quick Example
// JavaScript: no warning here, crash happens at runtime
let userAge = 25;
userAge = "25";
console.log(userAge.toFixed(0)); // TypeError: userAge.toFixed is not a function
// TypeScript: your editor flags this immediately
let userAge: number = 25;
userAge = "25"; // Error: Type 'string' is not assignable to type 'number'
console.log(userAge.toFixed(0)); // Never reached - blocked at compile timeIn the JS version the bug lives undetected until it hits production. TypeScript moves that moment to your editor, before any code ships.
Key Difference
JavaScript checks types when code actually runs. TypeScript layers structural type checking on top, so your IDE spots mismatches the moment you type them. According to the TypeScript team at Microsoft, this shift cuts bugs by 15-20% in large codebases. Runtime panics become editor notices.
When to Use
- Solo prototype under 1k lines - stick with JS; the setup overhead does not pay off
- Team project or React/Vue/Next.js app - use TS; catches wrong props before the browser does
- Node.js API with request validation - use TS; validates data shapes before they hit the database
- Legacy JS codebase - migrate incrementally with
allowJs: true; no need to rewrite everything at once - Quick automation script - JS is faster to iterate
How the Compiler Handles This
TypeScript's compiler (tsc) parses your .ts files, runs structural type checking (if two objects have matching shapes, their types are compatible), then emits plain .js files targeting ES5 or ES6. In VSCode, the TypeScript Language Service runs in the background and provides real-time diagnostics without a full compile step. Types disappear completely in the output. Node.js and browsers never see them.
Common Mistakes
Mistake 1: any everywhere
// Wrong: defeats the point of using TypeScript
function processData(data: any) {
return data.foo.bar; // Runtime crash, just like plain JS
}
// Right: use unknown and narrow it explicitly
function processData(data: unknown) {
if (typeof data === 'object' && data !== null && 'foo' in data) {
return (data as { foo: { bar: string } }).foo.bar;
}
}any turns off all type checking for that value. You lose every benefit. Use unknown instead and narrow the type down.
Mistake 2: Skipping strictNullChecks
// Wrong (strictNullChecks: false in tsconfig)
let user = getUser();
user.name.toUpperCase(); // Crashes if user is null
// Right (strictNullChecks: true)
let user = getUser();
user?.name?.toUpperCase(); // SafeWithout strictNullChecks, TypeScript lets null and undefined pass anywhere. Most runtime crashes are still hiding. Enable strict mode from day one.
Mistake 3: Mixing up interface and type
// interface: supports declaration merging
interface User { name: string }
interface User { age: number } // Merges automatically: { name: string; age: number }
// type: no merging, better for unions and intersections
type Status = 'active' | 'inactive';
type AdminUser = User & { role: 'admin' };Many teams default to interface for object shapes and type for unions and compositions. Both work; the difference shows up when you need to extend or compose types.
Real-World Usage
- React / Next.js - typed component props:
interface Props { user: User } - Express / NestJS - DTOs that validate request shapes before touching the database
- Redux Toolkit -
PayloadAction<User>keeps action payloads predictable - Node.js -
@types/nodeprovides type definitions for the standard library
Follow-up Questions
Q: What is structural typing in TypeScript?
A: Two types are compatible if their shapes match, regardless of their names. An object with { x: number } satisfies any type that requires { x: number }, even without an explicit declaration.
Q: How does TypeScript handle JavaScript libraries without types?
A: It uses @types/ packages, for example @types/react or @types/node. These are .d.ts declaration files that describe the shape of a library without shipping TypeScript source.
Q: Should you always enable strict mode?
A: Yes, in new projects. strict: true in tsconfig.json enables strictNullChecks, noImplicitAny, and several other checks that catch the most common bugs. Turning them off gives you TypeScript in name only.
Q: How do you migrate a JavaScript project to TypeScript without breaking CI?
A: Set allowJs: true and checkJs: false in tsconfig.json. Add // @ts-check to individual files as you go. This lets .js and .ts files coexist in the same project during the transition.
Examples
Typed React Component
interface UserProps {
id: number;
name: string;
isAdmin: boolean;
}
const UserCard: React.FC<UserProps> = ({ id, name, isAdmin }) => (
<div>
{name} (ID: {id}){isAdmin && <strong> Admin</strong>}
</div>
);
// TypeScript catches this before the browser loads the component:
// <UserCard id="1" name="Alice" isAdmin={false} />
// Error: Type 'string' is not assignable to type 'number'Passing id="1" (string) instead of id={1} (number) is one of the most common React bugs. TypeScript catches it at compile time. This pattern appears in every Next.js and Remix codebase using typed components.
Discriminated Union in an API Handler
type ApiResponse =
| { success: true; data: string }
| { success: false; error: string };
function handleUser(id: number): ApiResponse {
if (id > 0) {
return { success: true, data: 'User found' };
}
return { success: false, error: 'Invalid ID' };
}
const result = handleUser(1);
if (result.success) {
console.log(result.data); // TypeScript knows 'data' exists here
} else {
console.log(result.error); // And 'error' exists here
}The success field acts as a discriminator. TypeScript narrows the type inside each branch, so accessing .data on the error branch is a compile-time error, not a runtime surprise. This pattern shows up constantly in NestJS and Express handlers.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.