Skip to main content

Command Palette

Search for a command to run...

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.

Updated
3 min readView as Markdown
10 TypeScript Types Every Frontend Dev Should Know
J
Jayesh Sojitra | AI & Frontend

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.

T

Good list. The one thing I would add to the discriminated union point is the exhaustiveness check. A default case that assigns the value to a const of type never turns "I added a new variant and forgot to handle it" from a runtime surprise into a compile error. Without that the union narrows fine but nothing forces you to cover every branch. Also worth flagging that narrowing only works while the discriminant stays a literal type, one spread or a widened assignment upstream and status becomes plain string and the whole thing quietly stops narrowing.

30 Days of AI

Part 3 of 35

A 30-day series breaking down AI concepts, tools, and prompts in plain, jargon-free language — for beginners and professionals who want to actually understand and use AI, not just talk about it.

Up next

Why Your Component Re-Renders (And How to Stop It)

Your component isn't slow because React is slow. It's slow because it's re-rendering for reasons you haven't actually diagnosed yet.