Week 3 - TypeScript

Type System

Interfaces vs Types

Generics

Type inference

Type Unions and Intersections

Function Types and Return Types

Utility Types

Type-Safe API Responses

Type Guards

HTML Element Types

Tooling

Practice

Assignment

Front end Track

Utility Types

TypeScript ships with built-in utility types that transform existing types. These save you from rewriting type definitions.

Partial<T>

Partial<User> takes the User interface and makes every property optional. This is useful for update operations where you only change some fields:

interface User {
  name: string;
  email: string;
  age: number;
}

function updateUser(id: number, changes: Partial<User>) {
  // changes can have any combination of name, email, age
}

updateUser(1, { email: '[email protected]' }); // valid

Without Partial, you'd have to pass all three properties every time. With Partial<User>, the changes parameter accepts an object with any subset of User's properties — just email, just name and age, or all three.

Required<T>

The opposite of Partial — makes all properties required, even ones that were originally optional:

interface Config {
  host?: string;
  port?: number;
  debug?: boolean;
}

const fullConfig: Required<Config> = {
  host: 'localhost',
  port: 3000,
  debug: false,
};

Config has all optional properties (note the ? on each). Required<Config> strips the ? from every property, so fullConfig must include all three.

Pick<T, Keys>

Creates a new type with only the specified properties from the original:

type UserSummary = Pick<User, 'name' | 'email'>;
// { name: string; email: string }

Pick<User, 'name' | 'email'> pulls out just the name and email properties from User, ignoring age. The result is a smaller type with only the fields you listed.

Omit<T, Keys>

The opposite of Pick — creates a type with all properties except the specified ones:

type PublicUser = Omit<User, 'email'>;
// { name: string; age: number }

Omit<User, 'email'> keeps name and age but removes email. This is useful when certain fields shouldn't be exposed or provided by the caller.

Record<Keys, Value>

Creates an object type where every key has the same value type. The first parameter defines the keys, the second defines the value type:

type UserRoles = Record<string, 'admin' | 'editor' | 'viewer'>;

const roles: UserRoles = {
  aisha: 'admin',
  ben: 'editor',
  carlos: 'viewer',
};

Record<string, 'admin' | 'editor' | 'viewer'> means: an object with string keys where every value must be one of those three role strings.

Record is also useful for lookup maps where the keys come from a union:

type Status = 'loading' | 'success' | 'error';
type StatusLabels = Record<Status, string>;

const labels: StatusLabels = {
  loading: 'Loading...',
  success: 'Complete',
  error: 'Failed',
};

Here Record<Status, string> requires one entry for every value in the Status union. If you forget one, TypeScript will flag it.

Combining Utility Types

These utility types compose naturally — you can nest and combine them:

// An update payload: all user fields optional, except id which is required
type UpdateUser = Partial<Omit<User, 'id'>> & Pick<User, 'id'>;

Reading from the inside out: Omit<User, 'id'> removes id, Partial<...> makes the remaining fields optional, and & Pick<User, 'id'> adds id back as required. The result is a type where id is mandatory but everything else is optional.

<aside> ⌨️

Hands on: Start with a User interface, then use Partial, Pick, and Omit to create derived types. Write small functions that use each one.

</aside>


The HackYourFuture curriculum is licensed under CC BY-NC-SA 4.0

CC BY-NC-SA 4.0 Icons

*https://hackyourfuture.net/*

Found a mistake or have a suggestion? Let us know in the feedback form.