TypeScript can be as complex as you want it to be. Generics, conditional types, mapped types, template literal types — there's a lot there. Most of the value I get from it comes from a much smaller set of features.

Interfaces for Object Shapes

``typescript interface Post { slug: string; title: string; date: string; tags: string[]; coverImage?: string; } ``

Defining the shape of my data structures is where TypeScript earns its keep most clearly. The ? for optional fields, the array type notation — these two things alone catch a surprising number of bugs.

Union Types

``typescript type Status = 'draft' | 'published' | 'archived'; ``

A string that can only be one of a known set of values. TypeScript will error if you try to assign anything else. This pattern replaces a whole class of runtime checks.

as const for Config Objects

``typescript const THEMES = ['light', 'dark', 'system'] as const; type Theme = typeof THEMES[number]; // 'light' | 'dark' | 'system' ``

Derive a union type from an array automatically. The array and the type stay in sync.

Utility Types

Partial, Required, Pick, Omit — these four cover most of the cases where I'd otherwise be copying and tweaking interface definitions. Worth knowing cold.

unknown Over any

When I genuinely don't know the type, I use unknown rather than any. It forces me to narrow the type before I use the value, which is exactly the discipline I want. any silences TypeScript; unknown works with it.

Keep strict: true

Turn on strict mode from the start. Retrofitting it onto an existing codebase is painful. Starting with it means you never have to.