TypeScript Best Practices
Good TypeScript is less about clever types and more about a handful of habits - strict settings, honest naming, and narrowing instead of casting - applied consistently.
Naming Conventions
Use PascalCase for types, interfaces, and classes, and camelCase for variables and functions. Prefix boolean-returning values and properties with `is`, `has`, or `should` so their meaning is obvious at the call site without needing to check the type.
Consistent Naming
// Good: PascalCase for types, camelCase for values, is/has prefix for booleans
interface UserProfile {
displayName: string;
isVerified: boolean;
}
function getDisplayName(profile: UserProfile): string {
return profile.displayName;
}
const profile: UserProfile = { displayName: "Ada", isVerified: true };
console.log(getDisplayName(profile));Turn On strict Mode
Setting `"strict": true` in `tsconfig.json` enables a bundle of checks - including `strictNullChecks` and `noImplicitAny` - that catch entire categories of bugs, like forgetting that a value can be `null`, at compile time instead of in production.
strictNullChecks in Action
// tsconfig.json: { "compilerOptions": { "strict": true } }
function greet(name: string | null): string {
if (name === null) {
return "Hello, guest";
}
return `Hello, ${name.toUpperCase()}`;
}
console.log(greet(null));
console.log(greet("ada"));Avoid any, Reach for unknown
`any` disables type checking entirely for a value, which lets bugs sneak through silently. `unknown` is the type-safe alternative for genuinely unknown input - it forces a narrowing check, such as a `typeof` or `instanceof` test, before you're allowed to use the value for anything.
unknown Instead of any
function parseInput(input: unknown): number {
if (typeof input === "string" && input.trim() !== "") {
return Number(input);
}
if (typeof input === "number") {
return input;
}
throw new TypeError("Expected a string or number");
}
console.log(parseInput("42"));
console.log(parseInput(7));- Use readonly arrays and as const for data that should never mutate
- Model state with discriminated unions instead of loose boolean flags
- Write exhaustive switch statements with a never-typed default case
- Avoid the non-null assertion (!) in favor of real narrowing
- Keep functions small and single-purpose so their types stay simple
- Colocate types with the code that uses them instead of one giant types file
Exercise: TypeScript Best Practices
Why is unknown generally preferred over any for values whose type isn't known yet?