Why TypeScript Dominates Modern Software
JavaScript is dynamically typed, which means type errors often go unnoticed until runtime in production. TypeScript adds a compile-time static type system that eliminates an entire class of runtime bugs, provides rich IDE autocompletion, and makes large codebases self-documenting.
1. TypeScript Generics: Reusable Type-Safe Functions
Generics allow you to write functions and classes that work with any datatype while preserving type information:
// Generic API Response Wrapper
interface ApiResponse<T> {
status: 'success' | 'error';
data: T;
timestamp: number;
}
interface UserProfile {
id: string;
name: string;
email: string;
}
// Type-safe consumption
function handleResponse(res: ApiResponse<UserProfile>) {
console.log(res.data.email); // Fully typed!
}
2. Discriminated Unions for Clean State Handling
A discriminated union is a pattern where multiple object types share a common literal property (the discriminator). This allows TypeScript to narrow down the exact type within switch or if blocks:
type NetworkState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: string[] }
| { status: 'error'; error: Error };
function renderUI(state: NetworkState) {
switch (state.status) {
case 'loading':
return 'Loading spinner...';
case 'success':
return state.data.join(', '); // state.data is guaranteed here
case 'error':
return state.error.message;
default:
return 'Ready';
}
}
3. Advanced Utility Types: Mapped & Conditional Types
TypeScript provides built-in utilities like Partial<T>, Required<T>, Pick<T, K>, and Omit<T, K>. You can also build your own conditional types using the infer keyword to unwrap promises or array element types.
Summary
Mastering TypeScript generics and discriminated unions transforms how you write software. You catch errors before runtime and provide an intuitive developer experience across your entire team.