10 TypeScript Types Every Frontend Dev Should Know
Most developers use TypeScript like it's JavaScript with extra colons. Here are 10 type features that actually change how safely you write code.

A lot of TypeScript usage stops at basic annotations — string, number, boolean — and stays there. That's fine for simple cases, but it means missing the type features that catch real bugs before they ship. Here are 10 worth actually knowing.
1. Union types — a value that can be one of several types.
type Status = 'idle' | 'loading' | 'success' | 'error';
Better than a generic string — TypeScript will catch a typo like 'sucess' immediately.
2. Interfaces vs Type aliases — both describe object shapes; interfaces can be extended/merged, type aliases can represent unions and primitives too.
interface User { name: string; age: number; }
type ID = string | number;
Rule of thumb: interfaces for object shapes that might be extended, type aliases for everything else.
3. Generics — write reusable code that works with multiple types while keeping type safety.
function firstItem<T>(arr: T[]): T {
return arr[0];
}
Without generics, you'd either lose type safety (any) or duplicate the function per type.
4. Optional and readonly properties
interface Config {
timeout?: number;
readonly apiKey: string;
}
? marks a property as optional; readonly prevents reassignment after creation — genuinely useful for props that shouldn't change after initialization.
5. Utility types: Partial, Pick, Omit
type PartialUser = Partial<User>; // all properties optional
type NameOnly = Pick<User, 'name'>; // only the 'name' property
type WithoutAge = Omit<User, 'age'>; // everything except 'age'
These derive new types from existing ones instead of manually rewriting similar interfaces — reducing duplication when types are related but not identical.
6. Type guards — narrow a type within a conditional block.
function isString(value: unknown): value is string {
return typeof value === 'string';
}
Especially useful with unknown (safer than any) — you can't use a value until you've proven its type.
7. Discriminated unions — a pattern for modeling state that can be one of several distinct shapes.
type Result =
| { status: 'success'; data: string }
| { status: 'error'; message: string };
TypeScript narrows the type automatically based on the status field, which eliminates a whole category of "accessing a property that doesn't exist on this branch" bugs.
8. Mapped types — transform an existing type's properties.
type Readonly<T> = { readonly [K in keyof T]: T[K] };
Advanced, but this is how many built-in utility types (like Partial and Readonly) are actually implemented under the hood.
9. unknown vs any
any disables type checking entirely. unknown also accepts anything, but forces you to narrow the type before using it — meaningfully safer for values from external sources (API responses, user input).
10. Template literal types — build string types from patterns.
type EventName = `on${Capitalize<'click' | 'hover'>}`;
// 'onClick' | 'onHover'
Useful for enforcing naming conventions at the type level, especially in prop interfaces for component libraries.
Why this matters beyond "fewer bugs":
Good types are documentation that can't go stale. A discriminated union tells the next developer exactly what shapes are possible without needing a comment — and the compiler enforces it stays accurate as the code changes.
Takeaway: If your TypeScript usage is mostly basic type annotations, you're using it as a linter, not a design tool. Union types, generics, and discriminated unions specifically are where TypeScript starts catching real logic errors, not just typos.




