Lesson 22 of 49 advanced

Generics & Utility Types

Writing Code That Works With Any Type

Open interactive version (quiz + challenge)

Real-world analogy

Generics are like a universal adapter plug you take on international trips. The adapter shape (generic type) stays the same, but you slot in different country prongs (concrete types) depending on where you are. ApiResponse<User> is the adapter configured for the User outlet. Same adapter, any country!

What is it?

Generics let you write flexible code that works with ANY type. Utility types transform existing types: Partial makes properties optional, Pick selects properties, Omit removes them. Learn these and you'll write DRY code!

Real-world relevance

React hooks use generics: useState<T>, useCallback<T>. API responses use generics: Promise<ApiResponse<T>>. Mastering generics = writing production-quality code!

Key points

Code example

// Generic function — T is a placeholder
function getFirst<T>(arr: T[]): T {
  return arr[0];
}

getFirst<string>(["a", "b"]);   // returns "a" (type: string)
getFirst<number>([1, 2, 3]);    // returns 1 (type: number)
getFirst(["a", "b"]);   // TS guesses T = string

// Generic interface
interface ApiResponse<T> {
  data: T;
  success: boolean;
  message: string;
}

const userResponse: ApiResponse<User> = {
  data: { id: "1", name: "John" },
  success: true,
  message: "Found"
};

const postsResponse: ApiResponse<Post[]> = {
  data: [/* array of posts */],
  success: true,
  message: "All posts"
};

// Constraints — T must have "length"
function logLength<T extends { length: number }>(item: T): void {
  console.log(item.length);
}

logLength("hello");     // 5 ✓
logLength([1, 2, 3]);   // 3 ✓
// logLength(123);      // ERROR! number has no length

// Utility Types ✨

// Partial<T> — all properties optional
type UpdateUserDto = Partial<User>;
// user can have any combination of User fields!

// Pick<T, K> — only selected properties
type UserPreview = Pick<User, "name" | "email">;
// { name: string; email: string; }

// Omit<T, K> — everything except selected
type UserPublic = Omit<User, "password" | "ssn">;
// Has everything but password and ssn!

// Record<K, V> — object with specific keys
type Scores = Record<string, number>;
const scores: Scores = {
  math: 95,
  science: 87,
  english: 92
};

// Readonly<T> — all properties readonly
type Config = Readonly<{
  apiUrl: string;
  port: number;
}>;

const config: Config = { apiUrl: "https://api.com", port: 3000 };
// config.port = 4000;  // ERROR!

// Enums — named constants
enum Role {
  ADMIN = "admin",
  USER = "user",
  MODERATOR = "moderator"
}

const myRole: Role = Role.ADMIN;

// Tuples — fixed-length arrays with types
type Pair = [string, number];
const pair: Pair = ["John", 25];
pair[0];  // "John" (string)
pair[1];  // 25 (number)
// pair[2];  // ERROR!

// Type assertions (type casting)
const input = document.getElementById("name") as HTMLInputElement;
input.value = "John";  // TS knows it's an input now

Line-by-line walkthrough

  1. 1. Generic function — T is a placeholder
  2. 2. Declaring a function
  3. 3. Returning a value
  4. 4. Closing block
  5. 5.
  6. 6.
  7. 7.
  8. 8.
  9. 9.
  10. 10. Generic interface
  11. 11. Defining an interface shape
  12. 12.
  13. 13.
  14. 14.
  15. 15. Closing block
  16. 16.
  17. 17. Declaring a variable
  18. 18.
  19. 19.
  20. 20.
  21. 21. Closing block
  22. 22.
  23. 23. Declaring a variable
  24. 24.
  25. 25.
  26. 26.
  27. 27. Closing block
  28. 28.
  29. 29. Constraints — T must have "length"
  30. 30. Declaring a function
  31. 31. Printing output to the console
  32. 32. Closing block
  33. 33.
  34. 34.
  35. 35.
  36. 36. logLength(123); // ERROR! number has no length
  37. 37.
  38. 38. Utility Types ✨
  39. 39.
  40. 40. Partial&lt;T&gt; — all properties optional
  41. 41. Defining a type alias
  42. 42. user can have any combination of User fields!
  43. 43.
  44. 44. Pick&lt;T, K&gt; — only selected properties
  45. 45. Defining a type alias
  46. 46. { name: string; email: string; }
  47. 47.
  48. 48. Omit&lt;T, K&gt; — everything except selected
  49. 49. Defining a type alias
  50. 50. Has everything but password and ssn!
  51. 51.
  52. 52. Record&lt;K, V&gt; — object with specific keys
  53. 53. Defining a type alias
  54. 54. Declaring a variable
  55. 55.
  56. 56.
  57. 57.
  58. 58. Closing block
  59. 59.
  60. 60. Readonly&lt;T&gt; — all properties readonly
  61. 61. Defining a type alias
  62. 62.
  63. 63.
  64. 64.
  65. 65.
  66. 66. Declaring a variable
  67. 67. config.port = 4000; // ERROR!
  68. 68.
  69. 69. Enums — named constants
  70. 70. Defining an enum
  71. 71.
  72. 72.
  73. 73.
  74. 74. Closing block
  75. 75.
  76. 76. Declaring a variable
  77. 77.
  78. 78. Tuples — fixed-length arrays with types
  79. 79. Defining a type alias
  80. 80. Declaring a variable
  81. 81.
  82. 82.
  83. 83. pair[2]; // ERROR!
  84. 84.
  85. 85. Type assertions (type casting)
  86. 86. Declaring a variable
  87. 87.

Spot the bug

function getFirst<T>(arr: T[]): T {
  return arr;
}
Need a hint?
The return type is T (one item) but what is actually being returned?
Show answer
The function returns the entire array (T[]) instead of a single item (T). Fix: change 'return arr' to 'return arr[0]'.

Explain like I'm 5

Generics are like a magic lunchbox that can hold anything you choose! You decide what goes inside - sandwiches, fruit, or candy. The lunchbox doesn't care what's inside, but once you pick, it remembers. Same lunchbox shape, different food!

Fun fact

Utility types are so powerful that some developers write entire applications using just Partial, Pick, and Omit to manage types!

Hands-on challenge

Create a generic ApiResponse&lt;T&gt; interface. Use Partial, Pick, Omit with it. Make POST endpoint with Partial, GET with Pick!

More resources

Open interactive version (quiz + challenge) ← Back to course: Full-Stack Playbook