Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
LoginSign Up
menu
  1. Home
  2. /Web Development
  3. /Typescript
  4. /Utility Types
Your Progress0%
0 of 30 completed

TypeScript Topics

Getting Started

  • What is TypeScript?
  • TypeScript vs JavaScript
  • Setting Up TypeScript
  • Your First TypeScript Program

Basic Types

  • Primitive Types
  • Arrays and Tuples
  • Objects and Type Aliases
  • Enums
  • Any, Unknown, and Never

Functions & Interfaces

  • Function Types and Signatures
  • Interfaces
  • Optional and Default Parameters
  • Function Overloading

Advanced Types

  • Union and Intersection Types
  • Type Guards and Narrowing
  • Literal Types
  • Type Assertions and Casting

Generics

  • Introduction to Generics
  • Generic Constraints
  • Utility Types

Classes & OOP

  • Classes and Constructors
  • Access Modifiers
  • Inheritance and Polymorphism
  • Abstract Classes and Interfaces

Practical TypeScript

  • Working with Modules
  • TypeScript with React
  • Async/Await and Promises
  • Working with APIs and JSON
  • Type Declarations and DefinitelyTyped
  • TypeScript Best Practices

Utility Types

Built-in TypeScript types for common transformations

TypeScript provides powerful built-in utility types that transform existing types in useful ways. Instead of manually creating variations of types, you can use utilities like Partial, Pick, Omit, and Record to generate new types automatically. These utilities make your code more maintainable and reduce duplication. Let's master TypeScript's utility type toolkit!

Partial and Required

Partial<T>

Makes all properties of type T optional. Perfect for update operations where you only want to change some fields.

partial-type.ts
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  address: string;
}

// All properties become optional
type PartialUser = Partial<User>;

// Valid: can provide any subset of properties
const update1: PartialUser = { name: "Yusuf" };
const update2: PartialUser = { email: "yusuf@example.com", age: 26 };
const update3: PartialUser = {};  // Empty is valid too!

// Practical use: update function
function updateUser(id: number, updates: Partial<User>): User {
  const existingUser: User = {
    id: 1,
    name: "Yusuf Ibrahim",
    email: "yusuf@example.com",
    age: 25,
    address: "Lagos, Africa"
  };
  
  return { ...existingUser, ...updates, id };  // Preserve id
}

const updated = updateUser(1, { age: 26, address: "Abuja, Africa" });
console.log(updated);
// { id: 1, name: "Yusuf Ibrahim", email: "yusuf@example.com", age: 26, address: "Abuja, Africa" }

// Nested partial
interface Profile {
  user: User;
  preferences: {
    theme: string;
    language: string;
  };
}

// Only makes top-level properties optional
type PartialProfile = Partial<Profile>;

const profileUpdate: PartialProfile = {
  preferences: {  // Must provide complete preferences if included
    theme: "dark",
    language: "en"
  }
};

// For deep partial, need custom type
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

type DeepPartialProfile = DeepPartial<Profile>;

const deepUpdate: DeepPartialProfile = {
  preferences: {
    theme: "dark"  // Can provide partial preferences
  }
};

Required<T>

Makes all properties of type T required. Opposite of Partial.

required-type.ts
interface Config {
  host: string;
  port?: number;
  ssl?: boolean;
  timeout?: number;
}

// All properties become required
type RequiredConfig = Required<Config>;

// Must provide all properties
const config: RequiredConfig = {
  host: "localhost",
  port: 8080,
  ssl: false,
  timeout: 30000
};

// const invalid: RequiredConfig = {  // ❌ Error
//   host: "localhost"
//   // Missing: port, ssl, timeout
// };

// Practical use: validation
function validateConfig(config: Config): config is RequiredConfig {
  return (
    config.port !== undefined &&
    config.ssl !== undefined &&
    config.timeout !== undefined
  );
}

function startServer(config: Config): void {
  if (validateConfig(config)) {
    // TypeScript knows all properties are defined
    console.log(`Server on ${config.host}:${config.port}`);
    console.log(`SSL: ${config.ssl}`);
    console.log(`Timeout: ${config.timeout}ms`);
  } else {
    console.error("Incomplete configuration");
  }
}

startServer({ host: "localhost", port: 3000, ssl: true, timeout: 60000 });

Readonly<T>

Makes all properties of type T readonly. Prevents modification after creation.

readonly-type.ts
interface User {
  id: number;
  name: string;
  email: string;
}

// All properties become readonly
type ReadonlyUser = Readonly<User>;

const user: ReadonlyUser = {
  id: 1,
  name: "Yusuf Ibrahim",
  email: "yusuf@example.com"
};

// user.name = "Amina";  // ❌ Error: Cannot assign to 'name' because it is readonly
// user.id = 2;          // ❌ Error: Cannot assign to 'id' because it is readonly

console.log(user.name);  // ✅ Reading is fine

// Practical use: immutable data
function createUser(data: User): ReadonlyUser {
  return Object.freeze({ ...data });
}

const newUser = createUser({
  id: 2,
  name: "Amina Hassan",
  email: "amina@example.com"
});

// newUser.email = "new@example.com";  // ❌ Error

// Updating immutable data (create new object)
function updateReadonlyUser(
  user: ReadonlyUser,
  updates: Partial<User>
): ReadonlyUser {
  return { ...user, ...updates };
}

const updated = updateReadonlyUser(user, { email: "yusuf.new@example.com" });
console.log(user.email);     // "yusuf@example.com" (unchanged)
console.log(updated.email);  // "yusuf.new@example.com" (new object)

// Readonly array
const numbers: ReadonlyArray<number> = [1, 2, 3, 4, 5];
console.log(numbers[0]);  // ✅ Reading is fine
// numbers.push(6);       // ❌ Error: Property 'push' does not exist
// numbers[0] = 10;       // ❌ Error: Index signature is readonly

// Alternative syntax
const moreNumbers: readonly number[] = [6, 7, 8, 9, 10];
// moreNumbers[0] = 100;  // ❌ Error

Pick and Omit

Pick<T, K>

Creates a type by picking specific properties K from type T.

pick-type.ts
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  address: string;
  phone: string;
}

// Pick only name and email
type UserBasicInfo = Pick<User, "name" | "email">;

const basicInfo: UserBasicInfo = {
  name: "Yusuf Ibrahim",
  email: "yusuf@example.com"
  // Only these two properties
};

// Pick id and name
type UserIdentity = Pick<User, "id" | "name">;

const identity: UserIdentity = {
  id: 1,
  name: "Yusuf Ibrahim"
};

// Practical use: API responses
interface Product {
  id: number;
  name: string;
  description: string;
  price: number;
  stock: number;
  createdAt: Date;
  updatedAt: Date;
}

// Public API only shows subset
type ProductListItem = Pick<Product, "id" | "name" | "price">;

function getProducts(): ProductListItem[] {
  return [
    { id: 1, name: "Laptop", price: 450000 },
    { id: 2, name: "Mouse", price: 5000 },
    { id: 3, name: "Keyboard", price: 15000 }
  ];
}

const products = getProducts();
console.log(products[0].name);   // ✅ Available
console.log(products[0].price);  // ✅ Available
// console.log(products[0].stock);  // ❌ Error: Property doesn't exist

// Detailed view includes more
type ProductDetail = Pick<Product, "id" | "name" | "description" | "price" | "stock">;

function getProductDetail(id: number): ProductDetail {
  return {
    id: 1,
    name: "Laptop",
    description: "High-performance laptop",
    price: 450000,
    stock: 10
  };
}

Omit<T, K>

Creates a type by omitting specific properties K from type T. Opposite of Pick.

omit-type.ts
interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  createdAt: Date;
  updatedAt: Date;
}

// Omit sensitive data
type PublicUser = Omit<User, "password">;

const publicUser: PublicUser = {
  id: 1,
  name: "Yusuf Ibrahim",
  email: "yusuf@example.com",
  createdAt: new Date(),
  updatedAt: new Date()
  // No password property
};

// Omit multiple properties
type UserWithoutTimestamps = Omit<User, "createdAt" | "updatedAt">;

const user: UserWithoutTimestamps = {
  id: 1,
  name: "Yusuf Ibrahim",
  email: "yusuf@example.com",
  password: "hashed_password"
};

// Practical use: Create input type
type CreateUserInput = Omit<User, "id" | "createdAt" | "updatedAt">;

function createUser(input: CreateUserInput): User {
  return {
    id: Date.now(),  // Generated
    ...input,
    createdAt: new Date(),
    updatedAt: new Date()
  };
}

const newUser = createUser({
  name: "Amina Hassan",
  email: "amina@example.com",
  password: "secure_password"
});

console.log(newUser);

// Update input type
type UpdateUserInput = Partial<Omit<User, "id" | "createdAt">>;

function updateUser(id: number, input: UpdateUserInput): User {
  const existing: User = {
    id: 1,
    name: "Yusuf",
    email: "yusuf@example.com",
    password: "hashed",
    createdAt: new Date("2024-01-01"),
    updatedAt: new Date("2024-01-01")
  };
  
  return {
    ...existing,
    ...input,
    id,  // Preserve id
    updatedAt: new Date()  // Update timestamp
  };
}

const updated = updateUser(1, { name: "Yusuf Ibrahim" });
console.log(updated);

Record<K, T>

Creates an object type with keys of type K and values of type T.

record-type.ts
// Simple record
type UserRoles = Record<string, string>;

const roles: UserRoles = {
  admin: "Administrator",
  editor: "Content Editor",
  viewer: "Read-only Viewer"
};

console.log(roles.admin);   // "Administrator"
console.log(roles.editor);  // "Content Editor"

// Specific keys
type Status = "pending" | "approved" | "rejected";
type StatusMessages = Record<Status, string>;

const messages: StatusMessages = {
  pending: "Your request is being reviewed",
  approved: "Your request has been approved",
  rejected: "Your request has been rejected"
};

console.log(messages.pending);  // "Your request is being reviewed"

// Complex values
interface UserInfo {
  name: string;
  email: string;
  role: string;
}

type UserDatabase = Record<number, UserInfo>;

const users: UserDatabase = {
  1: { name: "Yusuf", email: "yusuf@example.com", role: "admin" },
  2: { name: "Amina", email: "amina@example.com", role: "editor" },
  3: { name: "Chidi", email: "chidi@example.com", role: "viewer" }
};

console.log(users[1].name);  // "Yusuf"
console.log(users[2].role);  // "editor"

// Practical use: Configuration
type Environment = "development" | "staging" | "production";

interface EnvConfig {
  apiUrl: string;
  debug: boolean;
  timeout: number;
}

type EnvironmentConfig = Record<Environment, EnvConfig>;

const config: EnvironmentConfig = {
  development: {
    apiUrl: "http://localhost:3000",
    debug: true,
    timeout: 60000
  },
  staging: {
    apiUrl: "https://staging.example.com",
    debug: true,
    timeout: 30000
  },
  production: {
    apiUrl: "https://api.example.com",
    debug: false,
    timeout: 30000
  }
};

const env: Environment = "development";
console.log(config[env].apiUrl);  // "http://localhost:3000"

// Translation dictionary
type Language = "en" | "fr" | "ha";
type TranslationKey = "greeting" | "farewell" | "thanks";
type Translations = Record<Language, Record<TranslationKey, string>>;

const translations: Translations = {
  en: {
    greeting: "Hello",
    farewell: "Goodbye",
    thanks: "Thank you"
  },
  fr: {
    greeting: "Bonjour",
    farewell: "Au revoir",
    thanks: "Merci"
  },
  ha: {
    greeting: "Sannu",
    farewell: "Sai anjima",
    thanks: "Na gode"
  }
};

function translate(lang: Language, key: TranslationKey): string {
  return translations[lang][key];
}

console.log(translate("ha", "greeting"));  // "Sannu"
console.log(translate("fr", "thanks"));    // "Merci"

Exclude and Extract

Exclude<T, U>

Excludes types in U from T.

exclude-type.ts
// Exclude specific types from union
type AllShapes = "circle" | "square" | "rectangle" | "triangle";
type QuadShapes = Exclude<AllShapes, "circle" | "triangle">;
// Result: "square" | "rectangle"

const shape: QuadShapes = "square";     // ✅ Valid
// const invalid: QuadShapes = "circle";  // ❌ Error

// Exclude primitives
type Mixed = string | number | boolean | null | undefined;
type NonNullable = Exclude<Mixed, null | undefined>;
// Result: string | number | boolean

const value: NonNullable = "hello";  // ✅ Valid
// const invalid: NonNullable = null;  // ❌ Error

// Practical use: Event types
type AllEvents = 
  | { type: "click"; x: number; y: number }
  | { type: "keypress"; key: string }
  | { type: "focus" }
  | { type: "blur" };

type MouseEvent = Extract<AllEvents, { type: "click" }>;
type KeyboardEvent = Exclude<AllEvents, { type: "click" }>;

// Remove function types
type MixedTypes = string | number | (() => void) | ((x: number) => number);
type NonFunctions = Exclude<MixedTypes, Function>;
// Result: string | number

const notFunc: NonFunctions = "hello";  // ✅ Valid
const notFunc2: NonFunctions = 42;      // ✅ Valid
// const invalid: NonFunctions = () => {};  // ❌ Error

Extract<T, U>

Extracts types from T that are assignable to U. Opposite of Exclude.

extract-type.ts
// Extract specific types from union
type AllTypes = string | number | boolean | object;
type Primitives = Extract<AllTypes, string | number | boolean>;
// Result: string | number | boolean

const primitive: Primitives = "hello";  // ✅ Valid
// const invalid: Primitives = {};      // ❌ Error: object not in extracted type

// Extract function types
type MixedTypes = string | number | (() => void) | ((x: number) => number);
type Functions = Extract<MixedTypes, Function>;
// Result: (() => void) | ((x: number) => number)

const func: Functions = () => console.log("hello");  // ✅ Valid
// const invalid: Functions = "string";              // ❌ Error

// Practical use: Filter event types
type AppEvent = 
  | { type: "user:login"; userId: number }
  | { type: "user:logout"; userId: number }
  | { type: "product:view"; productId: number }
  | { type: "product:purchase"; productId: number; amount: number };

type UserEvents = Extract<AppEvent, { type: `user:${string}` }>;
// Result: login and logout events

type ProductEvents = Extract<AppEvent, { type: `product:${string}` }>;
// Result: view and purchase events

function handleUserEvent(event: UserEvents): void {
  if (event.type === "user:login") {
    console.log(`User ${event.userId} logged in`);
  } else {
    console.log(`User ${event.userId} logged out`);
  }
}

// Extract by property
type WithId = Extract<AppEvent, { userId: number }>;
// Result: Only events with userId property

Other Useful Utilities

NonNullable<T>

nonnullable-type.ts
// Remove null and undefined from type
type MaybeString = string | null | undefined;
type DefiniteString = NonNullable<MaybeString>;
// Result: string

const value: DefiniteString = "hello";  // ✅ Valid
// const invalid: DefiniteString = null;  // ❌ Error

// Practical use: Array filter
function getDefinedValues<T>(arr: (T | null | undefined)[]): NonNullable<T>[] {
  return arr.filter((item): item is NonNullable<T> => item != null);
}

const mixed = [1, null, 2, undefined, 3, null, 4];
const defined = getDefinedValues(mixed);  // Type: number[]
console.log(defined);  // [1, 2, 3, 4]

const strings = ["hello", null, "world", undefined, "!"];
const definedStrings = getDefinedValues(strings);  // Type: string[]
console.log(definedStrings);  // ["hello", "world", "!"]

ReturnType<T>

returntype.ts
// Get return type of function
function createUser() {
  return {
    id: 1,
    name: "Yusuf",
    email: "yusuf@example.com"
  };
}

type User = ReturnType<typeof createUser>;
// Result: { id: number; name: string; email: string }

const user: User = {
  id: 2,
  name: "Amina",
  email: "amina@example.com"
};

// With generic function
function fetchData<T>(url: string): Promise<T> {
  return fetch(url).then(res => res.json());
}

type FetchResult = ReturnType<typeof fetchData>;
// Result: Promise<unknown>

// Async function
async function getProducts() {
  return [
    { id: 1, name: "Laptop", price: 450000 },
    { id: 2, name: "Mouse", price: 5000 }
  ];
}

type Products = ReturnType<typeof getProducts>;
// Result: Promise<{ id: number; name: string; price: number }[]>

// Unwrap promise
type ProductArray = Awaited<ReturnType<typeof getProducts>>;
// Result: { id: number; name: string; price: number }[]

Parameters<T>

parameters-type.ts
// Get parameter types of function
function createUser(name: string, email: string, age: number) {
  return { name, email, age };
}

type CreateUserParams = Parameters<typeof createUser>;
// Result: [name: string, email: string, age: number]

const params: CreateUserParams = ["Yusuf", "yusuf@example.com", 25];
const user = createUser(...params);

// Using with specific indices
type FirstParam = Parameters<typeof createUser>[0];   // string
type SecondParam = Parameters<typeof createUser>[1];  // string
type ThirdParam = Parameters<typeof createUser>[2];   // number

// Generic function helper
function callWithDefaults<F extends (...args: any[]) => any>(
  fn: F,
  ...args: Parameters<F>
): ReturnType<F> {
  return fn(...args);
}

const result = callWithDefaults(createUser, "Amina", "amina@example.com", 30);
console.log(result);  // { name: "Amina", email: "amina@example.com", age: 30 }

Awaited<T>

awaited-type.ts
// Unwrap Promise type
type PromiseString = Promise<string>;
type UnwrappedString = Awaited<PromiseString>;
// Result: string

// Nested promises
type NestedPromise = Promise<Promise<number>>;
type UnwrappedNumber = Awaited<NestedPromise>;
// Result: number (fully unwrapped)

// Practical use: Async function results
async function fetchUser(id: number) {
  return {
    id,
    name: "Yusuf",
    email: "yusuf@example.com"
  };
}

type UserPromise = ReturnType<typeof fetchUser>;
// Result: Promise<{ id: number; name: string; email: string }>

type User = Awaited<UserPromise>;
// Result: { id: number; name: string; email: string }

// Or directly
type UserDirect = Awaited<ReturnType<typeof fetchUser>>;
// Result: { id: number; name: string; email: string }

const user: User = {
  id: 1,
  name: "Yusuf",
  email: "yusuf@example.com"
};

Practical Examples

Example 1: Form State Management

form-state.ts
interface UserForm {
  username: string;
  email: string;
  password: string;
  confirmPassword: string;
  agreeToTerms: boolean;
}

// Form field states
type FormFields = keyof UserForm;
type FormValues = Record<FormFields, string | boolean>;
type FormErrors = Partial<Record<FormFields, string>>;
type FormTouched = Partial<Record<FormFields, boolean>>;

class FormState {
  private values: FormValues;
  private errors: FormErrors = {};
  private touched: FormTouched = {};
  
  constructor(initialValues: FormValues) {
    this.values = initialValues;
  }
  
  setValue<K extends FormFields>(field: K, value: FormValues[K]): void {
    this.values[field] = value;
    this.touched[field] = true;
  }
  
  setError<K extends FormFields>(field: K, error: string): void {
    this.errors[field] = error;
  }
  
  clearError<K extends FormFields>(field: K): void {
    delete this.errors[field];
  }
  
  getValues(): Readonly<FormValues> {
    return { ...this.values };
  }
  
  getErrors(): Readonly<FormErrors> {
    return { ...this.errors };
  }
  
  getTouched(): Readonly<FormTouched> {
    return { ...this.touched };
  }
  
  isValid(): boolean {
    return Object.keys(this.errors).length === 0;
  }
}

const form = new FormState({
  username: "",
  email: "",
  password: "",
  confirmPassword: "",
  agreeToTerms: false
});

form.setValue("username", "yusuf");
form.setValue("email", "yusuf@example.com");
form.setError("password", "Password must be at least 8 characters");

console.log(form.getValues());
console.log(form.getErrors());
console.log(form.isValid());  // false

Example 2: API Client with Utilities

api-client.ts
interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  role: string;
  createdAt: Date;
  updatedAt: Date;
}

// Public user data (no password)
type PublicUser = Omit<User, "password">;

// Create user input (no id, timestamps)
type CreateUserDTO = Omit<User, "id" | "createdAt" | "updatedAt">;

// Update user input (partial, no id, createdAt)
type UpdateUserDTO = Partial<Omit<User, "id" | "createdAt">>;

// User list item (minimal data)
type UserListItem = Pick<User, "id" | "name" | "email" | "role">;

class UserAPI {
  async create(data: CreateUserDTO): Promise<PublicUser> {
    // Simulate API call
    const user: PublicUser = {
      id: Date.now(),
      name: data.name,
      email: data.email,
      role: data.role,
      createdAt: new Date(),
      updatedAt: new Date()
    };
    return user;
  }
  
  async update(id: number, data: UpdateUserDTO): Promise<PublicUser> {
    // Simulate API call
    const existing: PublicUser = {
      id,
      name: "Yusuf",
      email: "yusuf@example.com",
      role: "user",
      createdAt: new Date("2024-01-01"),
      updatedAt: new Date("2024-01-01")
    };
    
    return {
      ...existing,
      ...data,
      updatedAt: new Date()
    };
  }
  
  async getAll(): Promise<UserListItem[]> {
    // Simulate API call
    return [
      { id: 1, name: "Yusuf", email: "yusuf@example.com", role: "admin" },
      { id: 2, name: "Amina", email: "amina@example.com", role: "editor" }
    ];
  }
  
  async getById(id: number): Promise<PublicUser> {
    // Simulate API call
    return {
      id,
      name: "Yusuf Ibrahim",
      email: "yusuf@example.com",
      role: "admin",
      createdAt: new Date("2024-01-01"),
      updatedAt: new Date("2024-12-25")
    };
  }
}

const api = new UserAPI();

// Create user
api.create({
  name: "Chidi Okafor",
  email: "chidi@example.com",
  password: "secure_password",
  role: "editor"
}).then(user => console.log("Created:", user));

// Update user
api.update(1, { name: "Yusuf A. Ibrahim" })
  .then(user => console.log("Updated:", user));

// Get all users
api.getAll().then(users => console.log("Users:", users));

Test Your Knowledge

Type Safety Check

Which utility type usage is correct?

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

// Option A
type PartialUser = Partial<User>;
const user: PartialUser = { name: "Yusuf" };

// Option B
type UserName = Pick<User, "name" | "email">;
const user: UserName = { name: "Yusuf", email: "yusuf@example.com", age: 25 };

// Option C
type WithoutAge = Omit<User, "age">;
const user: WithoutAge = { id: 1, name: "Yusuf", email: "yusuf@example.com" };

// Option D
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = { id: 1, name: "Yusuf", email: "yusuf@example.com", age: 25 };
user.name = "Amina";

Common TypeScript Error

Code with Error
interface Product {
  id: number;
  name: string;
  price: number;
  description: string;
}

// Trying to modify readonly type
type ReadonlyProduct = Readonly<Product>;

const product: ReadonlyProduct = {
  id: 1,
  name: "Laptop",
  price: 450000,
  description: "High-performance laptop"
};

// Error: Cannot assign to 'price' because it is a read-only property
product.price = 400000;
product.name = "Gaming Laptop";

❌ Cannot assign to 'price' because it is a read-only property. Cannot assign to 'name' because it is a read-only property.

What's Wrong?

Readonly<T> makes all properties in T readonly. Once set, they cannot be modified. This is useful for immutable data structures.

Corrected Code
interface Product {
  id: number;
  name: string;
  price: number;
  description: string;
}

type ReadonlyProduct = Readonly<Product>;

const product: ReadonlyProduct = {
  id: 1,
  name: "Laptop",
  price: 450000,
  description: "High-performance laptop"
};

// ✅ Create new object with changes
const updatedProduct: ReadonlyProduct = {
  ...product,
  price: 400000,
  name: "Gaming Laptop"
};

console.log(product.price);         // 450000 (unchanged)
console.log(updatedProduct.price);  // 400000 (new object)

// Or use Partial for updates
type ProductUpdate = Partial<Product>;

function updateProduct(
  original: ReadonlyProduct,
  updates: ProductUpdate
): ReadonlyProduct {
  return { ...original, ...updates };
}

const finalProduct = updateProduct(product, { price: 425000 });
console.log(finalProduct.price);  // 425000

Solution: Create a new object instead of modifying

Best Practices

  1. Use Partial for updates - makes all properties optional
  2. Use Readonly for immutable data - prevents modifications
  3. Use Pick/Omit for DTOs - create focused types
  4. Use Record for dictionaries - type-safe key-value pairs
  5. Combine utilities for complex transformations
  6. Use NonNullable after filtering - remove null/undefined
  7. Use ReturnType/Parameters for function-based types
  8. Use Awaited with async functions - unwrap Promises
best-practices-example.ts
interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  createdAt: Date;
}

// ✅ Good: Use Partial for updates
type UserUpdate = Partial<User>;

// ✅ Good: Combine utilities
type CreateUserInput = Omit<User, "id" | "createdAt">;
type PublicUser = Omit<User, "password">;
type UserSummary = Pick<User, "id" | "name">;

// ✅ Good: Readonly for immutable
type ImmutableUser = Readonly<User>;

// ✅ Good: Record for mappings
type UserRoles = Record<number, string>;

// ✅ Good: NonNullable after filtering
function getDefinedValues<T>(arr: T[]): NonNullable<T>[] {
  return arr.filter((item): item is NonNullable<T> => item != null);
}

// ✅ Good: ReturnType for derived types
async function fetchUser(id: number) {
  return { id, name: "Yusuf", email: "yusuf@example.com" };
}

type FetchedUser = Awaited<ReturnType<typeof fetchUser>>;

// ❌ Avoid: Manual property copying
type BadUserUpdate = {
  id?: number;
  name?: string;
  email?: string;
  password?: string;
  createdAt?: Date;
};

// ✅ Better: Use Partial
type GoodUserUpdate = Partial<User>;

Key Takeaways

  • Partial<T> makes all properties optional
  • Required<T> makes all properties required
  • Readonly<T> makes all properties readonly
  • Pick<T, K> selects specific properties
  • Omit<T, K> removes specific properties
  • Record<K, T> creates object types with specific keys
  • Exclude<T, U> removes types from union
  • Extract<T, U> extracts types from union
  • NonNullable<T> removes null/undefined
  • Utility types reduce duplication and improve maintainability

What's Next?

Congratulations! You've completed the TypeScript tutorial series! You've mastered types, functions, interfaces, generics, and utility types. You now have a solid foundation in TypeScript and can build type-safe applications with confidence.

Continue practicing with real projects, explore advanced patterns, and keep learning. TypeScript is a powerful tool that will make you a better developer. Happy coding!

Mastering TypeScript utility types! Check this out!

Previous
Generic Constraints
Next
Classes and Constructors

Never Miss a New TypeScript Tutorial

Join 1,000+ developers learning TypeScript step-by-step. Get new tutorials, type safety tips, and exclusive resources delivered to your inbox - completely FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

TypeScript Tutorials

0 of 30 completed

Your Progress0%

Topics

Getting Started

  • What is TypeScript?
  • TypeScript vs JavaScript
  • Setting Up TypeScript
  • Your First TypeScript Program

Basic Types

  • Primitive Types
  • Arrays and Tuples
  • Objects and Type Aliases
  • Enums
  • Any, Unknown, and Never

Functions & Interfaces

  • Function Types and Signatures
  • Interfaces
  • Optional and Default Parameters
  • Function Overloading

Advanced Types

  • Union and Intersection Types
  • Type Guards and Narrowing
  • Literal Types
  • Type Assertions and Casting

Generics

  • Introduction to Generics
  • Generic Constraints
  • Utility Types

Classes & OOP

  • Classes and Constructors
  • Access Modifiers
  • Inheritance and Polymorphism
  • Abstract Classes and Interfaces

Practical TypeScript

  • Working with Modules
  • TypeScript with React
  • Async/Await and Promises
  • Working with APIs and JSON
  • Type Declarations and DefinitelyTyped
  • TypeScript Best Practices
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo