Advanced Types

By the end of this lesson you'll be able to combine types with unions and intersections, lock values to exact literals, safely narrow a value to one type before using it, write reusable generic functions, and reshape any type with TypeScript's built-in utility types — the toolkit behind every well-typed codebase.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

Part of the free TypeScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

1. Unions, Intersections & Literal Types

A union type, written A B , means a value is one of several types — like an id that could be a string or a number . A literal type goes further and pins a value to an exact value, so "up" "down" allows only those two strings. An intersection , written A B , does the opposite of a union: the value must have everything from both types combined.

This is TypeScript-only syntax (it disappears when the code runs), so read it here rather than running it:

A handy way to remember it: union uses the OR bar and gives you fewer guarantees (you don't know which type yet); intersection uses the AND symbol and gives you more (you get all properties at once).

2. Type Narrowing & Guards

Once a value is a union, you can't immediately call methods that only exist on one member — TypeScript will stop you. Narrowing is the act of proving, with a runtime check, which type you actually have. Three everyday guards do most of the work: typeof for primitives, in for "does this object have that property", and instanceof for classes. Read this worked example, run it, then you'll write your own.

Your turn. The function below already has its structure — just fill in the two blanks marked ___ using the hints, then run it and check the output.

3. Discriminated Unions (the pro pattern)

The cleanest way to model "this is one of several shapes" is a discriminated union : every member shares one common property — the discriminant , usually a literal like status: "loading" . When you check that property in a switch , TypeScript narrows to the exact shape and lets you safely reach the fields unique to that branch. This single pattern replaces a pile of optional fields and if checks.

Now you try. Finish the payment handler by naming the discriminant property and returning the cash message. Fill in the two blanks:

4. Generics & Constraints

A generic is a type variable — written — that lets one function work with any type while keeping it type-safe. You don't lose information: if you pass a string in, you get a string back, not a vague "any". A constraint , written , narrows what T is allowed to be, so you can safely use the properties the constraint promises. This is TypeScript-only syntax, so read it here:

Think of as a blank you fill in at the call site . Most of the time TypeScript infers it for you from the argument, so you rarely type the angle brackets yourself.

5. The Essential Utility Types

TypeScript ships with built-in utility types that reshape an existing type so you never repeat yourself. The six you'll reach for daily: Partial (all optional), Required (all required), Pick (keep some), Omit (drop some), Record (build a key→value map), and Readonly (freeze it). Define your type once, then derive the variations:

This small program combines a generic with a discriminated union and narrowing — exactly the pattern real apps use to handle "this might have succeeded or failed". Read it line by line; you understand every piece now.

A B (union) means the value is one of A or B, so you can only use what they share until you narrow. A B (intersection) means the value is both at once , so it has every property from A and B combined.

Q: Why does TypeScript say a property "does not exist" on my union?

Because that property only exists on some members of the union, and TS can't yet prove you have the right one. Narrow first with typeof , in , instanceof , or a discriminant check, then the property becomes available inside that branch.

Q: When should I reach for a generic instead of any ?

Almost always. any switches off type checking entirely; a generic keeps the relationship between input and output, so you stay type-safe and get autocomplete. Use a generic whenever a function should work for many types but preserve which one it got.

Q: Do utility types create new objects at runtime?

No. They are purely compile-time tools that reshape types , then vanish when TypeScript compiles to JavaScript. Partial produces zero runtime code — it just changes what the compiler allows.

No blanks this time — just a brief and an outline to keep you on track. Write the describeNotification function yourself, switching on the discriminant, then run it and check your output against the example in the comments.

Practice quiz

What does a union type A | B mean?

  • The value has everything from both A and B
  • The value is neither A nor B
  • The value is one of A or B
  • The value is converted from A to B

Answer: The value is one of A or B. A union A | B means the value is one of the listed types - you only know which after narrowing.

What does an intersection type A & B require?

  • The value has everything from both A and B combined
  • The value is either A or B
  • The value matches only the shared properties
  • The value is the smaller of A and B

Answer: The value has everything from both A and B combined. An intersection A & B means the value has all properties of A AND B at once.

What does the literal union type "up" | "down" allow?

  • Any string
  • Any two-character string
  • Numbers 1 and 2
  • Only the exact strings "up" or "down"

Answer: Only the exact strings "up" or "down". Literal types pin a value to exact values, so only "up" or "down" are accepted - "left" would be an error.

Which guard is used to narrow primitives like string and number?

  • instanceof
  • typeof
  • in
  • extends

Answer: typeof. typeof narrows primitives ("string", "number", "boolean"); in checks for a property and instanceof narrows by class.

Which operator narrows an object by checking whether a property exists?

  • in
  • typeof
  • instanceof
  • keyof

Answer: in. The in operator checks a property exists on an object, narrowing the union to the member that has it.

What is the discriminant in a discriminated union?

  • A randomly chosen property
  • The longest property name
  • A shared literal-typed property like status or kind
  • An optional field unique to one member

Answer: A shared literal-typed property like status or kind. Every member shares one common literal property (the discriminant); checking it in a switch narrows to the exact shape.

What does a constraint like <T extends { length: number }> guarantee?

  • T must be a number
  • T must have a length property, so reading it is safe
  • T can only be a string
  • T is converted to an array

Answer: T must have a length property, so reading it is safe. The constraint limits T to types with a length property, so .length is safe to read inside the function.

Which utility type makes every property of a type optional?

  • Required<T>
  • Pick<T, K>
  • Readonly<T>
  • Partial<T>

Answer: Partial<T>. Partial<T> turns every property optional - perfect for update payloads. Required<T> does the opposite.

What does Omit<User, "email"> produce?

  • Only the email property
  • User with every property except email
  • User with email made optional
  • A union of User and email

Answer: User with every property except email. Omit<T, Keys> keeps everything except the listed keys, so Omit<User, "email"> is User without email.

Do utility types like Partial<User> produce runtime code?

  • Yes, they create new objects at runtime
  • Only when used with classes
  • No, they are compile-time only and produce zero runtime code
  • Only in strict mode

Answer: No, they are compile-time only and produce zero runtime code. Utility types reshape types at compile time and vanish when TypeScript compiles to JavaScript - no runtime output.