Published Mar 21, 2026
TypeScript tips that will level up your codebase fast
TypeScript is much more than adding : string to your variables. The type system is incredibly powerful, and learning a few advanced patterns can dramatically improve your code quality and developer experience.
Discriminated unions for state management
Instead of optional fields and boolean flags, model your state as a discriminated union:
// Instead of this:
type RequestState = {
loading: boolean;
error?: string;
data?: User[];
};
// Do this:
type RequestState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'error'; error: string }
| { status: 'success'; data: User[] };
Now TypeScript will enforce that you handle every state, and you can never accidentally access data when the request is still loading.
Template literal types
Template literal types let you create string patterns at the type level:
type EventName = `on${Capitalize<string>}`;
type CSSUnit = `${number}${'px' | 'rem' | 'em' | '%'}`;
function setWidth(width: CSSUnit) { /* ... */ }
setWidth('100px'); // OK
setWidth('2rem'); // OK
setWidth('hello'); // Error!
The satisfies operator
The satisfies operator checks that a value matches a type without widening it:
const palette = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255],
} satisfies Record<string, string | number[]>;
// palette.green is still typed as string, not string | number[]
palette.green.toUpperCase(); // Works!
Utility types you should know
Partial<T>— makes all properties optionalRequired<T>— makes all properties requiredPick<T, K>— selects specific propertiesOmit<T, K>— removes specific propertiesExtract<T, U>— extracts members assignable to UReturnType<T>— gets the return type of a function
These built-in utilities eliminate a huge amount of boilerplate type definitions. Combine them to build exactly the type you need.