TypeScript isn’t just JavaScript with type annotations. It’s a tool that helps you catch mistakes early, model your data correctly, and write safer, more maintainable code.
Here are 9 TypeScript concepts every developer should understand.
TypeScript catches many mistakes during type checking, before your code runs.
function greet(user: { name: string }): string {return user.name.toUpperCase();}greet({ age: 25 });// ❌ Error: 'name' is missing
Why it matters: Catching type errors early reduces avoidable runtime bugs. It doesn’t eliminate every possible runtime error.
type, interface, and enumUse type and interface to describe data structures.
type User = {id: number;name: string;};interface Admin extends User {role: "admin";}
type: Supports unions, intersections, and type aliases.interface: Supports extension and declaration merging.enum: Defines named constants, though string unions are often a simpler alternative.type Status = "loading" | "success" | "error";
Union types restrict values to a defined set of options.
type Operator = "+" | "-" | "*" | "/";function calculate(operator: Operator) {// Only the four allowed operators are accepted.}calculate("%"); // ❌ Type error
Narrowing also lets TypeScript determine a more specific type through checks such as typeof, in, and equality comparisons.
Avoid duplicating information that already exists in your code.
const operations = {"+": (a: number, b: number) => a + b,"-": (a: number, b: number) => a - b,};type Operator = keyof typeof operations;// "+" | "-"
Add another operation, and the Operator type updates automatically.
keyof and typeofThese operators help you derive types from existing values and structures.
type Operations = typeof operations;type Operator = keyof Operations;
typeof: Gets the type of a variable or value.keyof: Extracts an object’s keys as a union of property names.Together, they help keep your types synchronized with your implementation.
Generics let you write reusable functions without sacrificing type safety.
function identity<T>(value: T): T {return value;}const name = identity("Viet"); // stringconst age = identity(25); // number
TypeScript infers T from the argument, so you usually don’t need to specify it manually.
TypeScript provides built-in utilities to transform existing types.
type User = {id: number;name: string;email: string;};type PartialUser = Partial<User>;type UserName = Pick<User, "name">;type UserWithoutId = Omit<User, "id">;
Use Record when you need a consistent mapping between keys and values.
type OperationFn = (a: number, b: number) => number;type Operator = "+" | "-";const operations: Record<Operator, OperationFn> = {"+": (a, b) => a + b,"-": (a, b) => a - b,};
This checks that all required keys exist and that their values match the expected function type.
satisfies OperatorThe satisfies operator validates a value against a type without replacing its inferred type with that target type.
const operations = {"+": (a: number, b: number) => a + b,"-": (a: number, b: number) => a - b,} satisfies Record<"+" | "-", OperationFn>;
Compared with a type annotation, satisfies can preserve more specific information about the expression while still checking compatibility.
Use it when you want validation without unnecessarily widening your inferred types.
any vs. unknown vs. neverThese types serve very different purposes.
any — Opt out of type safety
let value: any = 10;value.foo.bar(); // No compile-time error
unknown — Validate before using
let value: unknown = "hello";if (typeof value === "string") {value.toUpperCase(); // Safe after narrowing}
never — Represent impossible values
function fail(message: string): never {throw new Error(message);}
never is also useful for exhaustive checks in discriminated unions.
Safer patterns:
unknown over any for values of uncertain type.keyof typeof.satisfies to validate object shapes.never to enforce exhaustive checks.Potentially unsafe patterns:
any disables meaningful type checking.as SomeType can override the compiler’s assumptions without validating the value.! can hide a possible null or undefined.@ts-ignore can suppress useful errors.unknown without narrowing doesn’t make an operation safe.Remember: TypeScript provides static type safety, not a guarantee against all runtime failures. Validate external data, handle errors, and test your application.