Unlocking Generics
Write flexible functions, interfaces, and classes that work with multiple types while keeping TypeScript’s powerful type checking.
function identity<T>(value: T): T {
return value;
}
const result = identity<string>(
"Moe"
);
console.log(result);
Write Code That Works with Multiple Types
Imagine writing a function that returns whatever value you give it. You could make separate versions for strings, numbers, and other data types. But what if you could write that function just once?
Generics let you create reusable code while preserving information about the types being used.
function identity<T>(value: T): T {
return value;
}
console.log(identity<string>("Moe"));
console.log(identity<number>(9));The T is a type parameter. It acts as a placeholder for a type that will be supplied when the function is used.
Keep the Type Information You Need
You might wonder why we don’t simply use any. The difference is that any disables important type checks, while a generic preserves the relationship between inputs and outputs.
function unsafe(value: any): any {
return value;
}
function safe<T>(value: T): T {
return value;
}
const first = unsafe("Moe");
const second = safe("Moe");
// second is known to be a string.
// first has type any.With the generic version, TypeScript knows that the returned value has the same type as the input.
Let TypeScript Figure Out the Type
Most of the time, you don’t need to write the type argument explicitly. TypeScript can infer it from the value passed into the function.
function identity<T>(value: T): T {
return value;
}
const developer = identity("Moe");
const level = identity(9);
const completed = identity(true);
console.log(developer);
console.log(level);
console.log(completed);TypeScript infers string, number, and boolean types from the three arguments. You still get type safety without repeatedly writing type annotations.
Use Generics with Collections
You’ve already used generic types without necessarily realizing it. Array<T> is the generic form of an array type.
const skills: Array<string> = [
"HTML",
"CSS",
"TypeScript"
];
const levels: Array<number> = [1, 5, 9];
console.log(skills[2]);
console.log(levels[1]);Array<string> and string[] mean the same thing. Both describe arrays whose elements are strings.
Create Reusable Collection Helpers
Generics become especially useful when building functions that operate on arrays.
function firstItem<T>(items: T[]): T | undefined {
return items[0];
}
const firstSkill = firstItem(["HTML", "CSS"]);
const firstLevel = firstItem([3, 6, 9]);
console.log(firstSkill);
console.log(firstLevel);The same function works with different array element types. We include undefined in the return type because an empty array has no first element.
Work with Two Types at Once
A generic function can accept more than one type parameter. Common names include T, U, and K, but you can use descriptive names too.
function makePair<T, U>(
first: T,
second: U
): [T, U] {
return [first, second];
}
const pair = makePair("Moe", 9);
console.log(pair[0]);
console.log(pair[1]);Here, TypeScript remembers that the first item is a string and the second is a number. The return type is a tuple containing those two types in order.
Build Flexible Data Contracts
Interfaces can accept type parameters, too. This makes them useful for reusable response objects, records, and application data structures.
interface Result<T> {
success: boolean;
data: T;
}
const developerResult: Result<string> = {
success: true,
data: "Moe"
};
const levelResult: Result<number> = {
success: true,
data: 9
};
console.log(developerResult.data);
console.log(levelResult.data);The same Result<T> interface can describe responses containing different types of data.
Create Reusable Object Containers
Classes can also be generic. Imagine a container that holds one value, regardless of whether it’s a string, number, or another type.
class Box<T> {
constructor(public value: T) {}
getValue(): T {
return this.value;
}
}
const nameBox = new Box<string>("Moe");
const levelBox = new Box<number>(9);
console.log(nameBox.getValue());
console.log(levelBox.getValue());Each instance keeps its own type information. You can reuse the class while still getting accurate type checking.
Require Specific Capabilities
Sometimes your generic function needs to use a particular property. A constraint tells TypeScript what capabilities the type argument must have.
interface Named {
name: string;
}
function introduce<T extends Named>(
value: T
): void {
console.log("Hello, " + value.name);
}
introduce({
name: "Moe",
language: "TypeScript"
});
// TypeScript error:
// introduce({ level: 9 });The constraint T extends Named requires an object with a string name property. It can still contain additional properties.
extends means the type must satisfy the required structure. It doesn’t necessarily mean class inheritance.Create a Reusable Learning Record
Let’s combine generic interfaces, functions, and classes into a small program for tracking developer information.
interface LearningRecord<T> {
title: string;
value: T;
}
function displayRecord<T>(
record: LearningRecord<T>
): void {
console.log(record.title + ": " + record.value);
}
const developer: LearningRecord<string> = {
title: "Developer",
value: "Moe"
};
const level: LearningRecord<number> = {
title: "Level",
value: 9
};
displayRecord(developer);
displayRecord(level);One interface and one function handle multiple kinds of records while preserving the correct value types.
Build a Generic Learning Tracker
Create a reusable tracker that can store different kinds of developer progress data.
Your Mission
- Create a generic interface named
LearningRecord<T>. - Give it a
titleproperty of typestringand avalueproperty of typeT. - Create a generic function named
displayRecord<T>()that prints a record. - Create a record for Moe’s name using
string. - Create another record for the current lesson number using
number. - Display both records in the console.
- Try assigning a string to a
LearningRecord<number>value and observe the type error.
LearningRecord<boolean> to track whether a lesson is complete.Build Your Generic Learning Tracker!
Edit your records, experiment with type parameters, and run the program. TypeScript checks your code before the compiled JavaScript runs.
Preparing TypeScript sandbox…
What You’ve Learned
You’re Writing Flexible, Type-Safe Code!
You can now:
- Explain how generics preserve type information.
- Create generic functions with
<T>. - Use TypeScript’s generic type inference.
- Recognize
Array<T>as a generic type. - Write generic functions that work with arrays.
- Use multiple type parameters.
- Create generic interfaces and classes.
- Apply generic constraints using
extends.
Next up: Union Types & Type Guards — learn how to work safely with values that can have more than one type!
Your Learning Journey
0 / 11 CompletedNine lessons in! You’re approaching the finish line.
You’re Writing Smarter TypeScript!
You’ve learned how generics make functions, interfaces, and classes reusable without sacrificing type safety. You’re now ready to handle more complex data structures and application logic!
