

0 / 2 embers
0 / 3000 xp
click for more info
Complete a lesson to start your streak
click for more info
Still calibrating
click for more info
Not enough gems
Cost: 6 gems
1: Generics
incomplete
2: Multiple Type Parameters
incomplete
3: Generic Constraints
incomplete
4: Type Parameters for Types
incomplete
5: Generic Type Inference
incomplete
6: Generic Classes
incomplete
Back
ctrl+,
Next
ctrl+.
This lesson's interactive features are locked, please to keep using them
Generics are one of TypeScript's most powerful features. They allow you to create reusable logic that works with many types rather than a single one. Think of a data structure like a Queue or a Stack. They can hold any type of data, so it would be really annoying to reimplement them for every type:
NumberQueueStringQueueUserQueueetc.Generics let us create a single Queue<T> type that can work with any type T. The best part is that when we use that queue with a specific type, TypeScript won't lose that type information! Generics are a way to reuse behavior across types without resorting to any.
You may have already noticed, but TypeScript's utility types are all generics! For example, Partial<T> is a generic type that takes a type T and returns a new type with all properties of T set to optional.
The ExampleGeneric<T> syntax is an example of a generic type parameter. ExampleGeneric is the name of the generic type (e.g. Array, Promise, etc., basically anything that can operate on the various types), and T is the name of the type parameter. T is just a variable name, we could call it anything, but T is a common convention.
Say we're building some client-side code (frontend, I know, ewww) and we make tons of fetch requests to a backend server. We might have a lot of code that looks like this:
async function fetchFromAPI(url: string) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("Network response was not ok");
}
return await response.json();
} catch (error) {
console.error("Error fetching data:", error);
return undefined;
}
}
We could lazily leave this as-is, but then it will always return a Promise<any>, which has no useful type information. Instead, let's make it generic:
async function fetchFromAPI<T>(url: string): Promise<T | undefined> {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("Network response was not ok");
}
return await response.json();
} catch (error) {
console.error("Error fetching data:", error);
return undefined;
}
}
Now whenever we call it, we just specify the type we expect to get back:
const comments = await fetchFromAPI<Comment[]>(
"https://api.example.com/posts/1/comments",
);
const user = await fetchFromAPI<User>("https://api.example.com/user/1");
const posts = await fetchFromAPI<Post[]>("https://api.example.com/posts");
At Support.ai, we store all support requests in a queue. We also store emails, numbers, and payments in queues... it's a legacy thing.
Complete the getFirst utility function. It should be a generic function that returns the first item in a list – or undefined if the list is empty.