Table of content
- tsconfig.json
- Core Language Features
- Utility Types
- Advanced Features
- Practical Patterns
- Testing & Tooling
- Anti-patterns to Avoid
- Underrated Helpers
- Wrap-up
tsconfig.json
We recommend starting with a well-configured tsconfig.json file to enable strict type checking and modern JavaScript features. Here’s a basic setup:
{
"compilerOptions": {
"target": "ES2020", // Modern JavaScript features
"module": "ESNext", // Use ES modules
"strict": true, // Enable all strict type-checking options
"noImplicitAny": true, // Disallow implicit 'any' types
"strictNullChecks": true, // Ensure null and undefined are handled explicitly
"esModuleInterop": true, // Enable compatibility with CommonJS modules
"skipLibCheck": true, // Skip type checking of declaration files
"forceConsistentCasingInFileNames": true // Ensure file names are case-sensitive
},
"include": ["src/**/*"], // Include all TypeScript files in src directory
"exclude": ["node_modules", "**/*.spec.ts"] // Exclude node_modules and test files
}
Core Language Features
Type Inference
Let TypeScript infer types wherever possible. It reduces noise and keeps the codebase clean.
const name = "Alice"; // inferred as string
Type Annotations (for function boundaries)
Always type function parameters and return values explicitly for clarity and contract enforcement.
function greet(name: string): string {
return `Hello, ${name}`;
}
Union & Intersection Types
type Status = "draft" | "published"; // Union: can be either
type Article = Text & Metadata; // Intersection: must satisfy both
Union types are used when a value can be one of several types.
Intersection types combine multiple types into one with all their properties.
Type Aliases vs. Interfaces
type User = { id: number; name: string };
interface Admin {
id: number;
role: string;
}
typeis more flexible — supports primitives, unions, and intersections.
interfaceis better for extending objects and class contracts.
Literal Types
type Color = "red" | "green" | "blue";
Restrict a value to exact strings, numbers, or booleans.
as const
const roles = ["admin", "user"] as const;
// typeof roles[number] → 'admin' | 'user'
Marks arrays and objects as deeply immutable and narrows literal types.
Utility Types
Partial<T>
Makes all properties in a type optional.
type User = { id: string; name: string };
Partial<User>; // { id?: string; name?: string }
Required<T>
Opposite of Partial — makes all properties required.
type Person = { name?: string; age?: number };
type RequiredPerson = Required<Person>; // { name: string; age: number }
Readonly<T>
Creates a type with all properties set to readonly.
type Config = { port: number };
const cfg: Readonly<Config> = { port: 3000 };
// cfg.port = 4000; ❌ Error: cannot assign to readonly property
Record<K, T>
Constructs an object type with a set of keys and uniform value type.
Record<"admin" | "user", boolean>; // { admin: boolean; user: boolean }
Pick<T, K>
Select specific properties from a type.
Pick<User, "id" | "name">;
Omit<T, K>
Exclude specific properties from a type.
Omit<User, "name">;
Exclude<T, U>
Exclude members from a union type.
Exclude<"a" | "b" | "c", "a">; // 'b' | 'c'
Extract<T, U>
Extract members of type T that are assignable to U.
type Events = "click" | "scroll" | "keydown";
type MouseEvents = Extract<Events, "click" | "scroll">; // 'click' | 'scroll'
NonNullable<T>
Removes null and undefined from a type.
type MaybeString = string | null | undefined;
type JustString = NonNullable<MaybeString>; // string
ReturnType<T> / Parameters<T>
Get type information from functions and constructors.
function getUser() {
return { id: 1, name: "Alice" };
}
type User = ReturnType<typeof getUser>; // { id: number; name: string }
function greet(name: string, age: number) {}
type GreetArgs = Parameters<typeof greet>; // [string, number]
ConstructorParameters<T>
Gets the parameters of a class constructor.
class Person {
constructor(
public name: string,
public age: number
) {}
}
type PersonArgs = ConstructorParameters<typeof Person>;
// [string, number]
Advanced Features
keyof
Get all property names of a type as a union of string literals.
type UserKeys = keyof User; // "id" | "name"
typeof
Use the type of a variable or constant.
const config = { host: "localhost", port: 3000 };
type Config = typeof config;
infer (inside conditional types)
Extract and reuse a type within a conditional.
type Unwrap<T> = T extends Promise<infer U> ? U : T;
Mapped Types
type Optional<T> = { [K in keyof T]?: T[K] };
Allows you to apply transformations across properties of a type.
Template Literal Types
type Lang = `lang-${"en" | "fr"}`; // 'lang-en' | 'lang-fr'
Conditional Types
type IsString<T> = T extends string ? true : false;
Enables dynamic type evaluation.
Discriminated Unions
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; size: number };
Enables safe narrowing using the
kindfield.
satisfies operator
type Config = {
port: number;
mode: "dev" | "prod";
};
// WRONG: TypeScript widens cfg.mode to string
const cfg = {
port: 3000,
mode: "dev",
}; // Type: { port: number; mode: string }
const typed: Config = cfg; // but now cfg.mode is widened to `string`
// CORRECT: Use satisfies to ensure type safety without widening
const cfg = {
port: 3000,
mode: "dev",
} satisfies Config;
- TypeScript ensures cfg matches Config
- But cfg.mode is still inferred as ‘dev’ (not widened to string)
Practical Patterns
Safe Object Access
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
Prevents unsafe access with invalid keys.
Deep Readonly
type DeepReadonly<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>;
};
Recursively makes all fields immutable.
Narrowing unknown safely
function isString(x: unknown): x is string {
return typeof x === "string";
}
Use type guards to safely handle
unknown.
Testing & Tooling
Enable strict mode
"strict": true
Enables full type safety features in your project.
Use tsc --noEmit for type-only validation
Compile code purely to check for type errors, without emitting JS output.
Recommended Tools
tsup/tsx– fast TypeScript bundling and dev serverts-prune– finds unused exportstypescript-json-schema– generate JSON schemas from typestypedoc– auto-generate documentation from comments
Anti-patterns to Avoid
- Overusing
anyor excessiveas - Over-declaring types already inferred
- Using runtime checks where compile-time types suffice
- Not typing function boundaries
Underrated Helpers
never: ensures exhaustive checkingassertsfunctions: custom type refinersunknown: safer thanany; forces type checks
Wrap-up
TypeScript is a powerful type system that can model real-world data accurately and safely. Mastering these features will not only make your code cleaner and safer but also easier to maintain in the long run.