%% generate tags start %%
#software-engineering
%% generate tags end %%
#software-engineering/typescript
## What is Typescript Generics?
![[#When to use generics]]
%% run start
```ts
const {LinkPreview} = customJS
return LinkPreview.getLinkPreviewFromUrl("https://www.typescriptlang.org/docs/handbook/2/generics.html")
```
%%
Loading...
%% run end %%
Here's a simple example of TypeScript generics:
```typescript
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("myString");
```
`<T>` basically mean `<T extends unknown>` and `<T extends any>`

In this example, `identity` is a generic function denoted by `<T>`. It allows you to pass in an argument of any type (`T`), and it returns a value of the same type. The `<string>` part specifies that in this particular call, the `T` is a string, making the function accept a string argument and return a string. This provides flexibility and type safety in functions that handle different data types.
```ts
function identity<T>(arg: T): T {
return arg;
}
let output = identity("myString"); // string
let output2 = identity(3); // number
```
## Why We Need Generics?
One area where TypeScript generics are highly useful is in creating reusable data structures, like a linked list. Without generics, you'd have to define a different class for each data type you want to store (e.g., `StringLinkedList`, `NumberLinkedList`), leading to code duplication and maintenance challenges.
Here's an example:
```typescript
class LinkedListNode<T> {
value: T;
next: LinkedListNode<T> | null = null;
constructor(value: T) {
this.value = value;
}
}
class LinkedList<T> {
head: LinkedListNode<T> | null = null;
add(value: T) {
if (this.head === null) {
this.head = new LinkedListNode(value);
} else {
let current = this.head;
while (current.next !== null) {
current = current.next;
}
current.next = new LinkedListNode(value);
}
}
}
```
In this example, generics allow you to create a single, flexible `LinkedList` class that can handle any data type, greatly simplifying code structure and reuse. Without generics, you'd need separate implementations for each data type or lose type safety by resorting to `any`, making the code more error-prone and less maintainable.
## How is Generics Compare to [[Typescript Abstraction | abstract class]]?
let's start with how to use the generic `LinkedList` class from the previous example. Then, I'll show you how an abstract class can be used to achieve a similar but structurally different approach.
### Using the Generic LinkedList:
Here's how you might use the generic `LinkedList` class to store different types of data:
```typescript
// Using LinkedList with number type
const numberList = new LinkedList<number>();
numberList.add(1);
numberList.add(2);
// Using LinkedList with string type
const stringList = new LinkedList<string>();
stringList.add("Hello");
stringList.add("World");
console.log(numberList); // LinkedList<number> with numbers
console.log(stringList); // LinkedList<string> with strings
```
This approach is straightforward and type-safe, ensuring that all elements in a particular list are of the same type.
### Using an Abstract Class:
Now, let's create a version using an abstract class for `LinkedListNode`. This will allow for different node classes that can have additional properties or methods.
```typescript
// Define an abstract class for LinkedListNode
abstract class AbstractLinkedListNode {
abstract value: any;
next: AbstractLinkedListNode | null = null;
}
// Concrete implementation for number
class NumberNode extends AbstractLinkedListNode {
value: number;
constructor(value: number) {
super();
this.value = value;
}
}
// Concrete implementation for string
class StringNode extends AbstractLinkedListNode {
value: string;
constructor(value: string) {
super();
this.value = value;
}
}
// Modified LinkedList that works with the abstract class
class LinkedList {
head: AbstractLinkedListNode | null = null;
add(node: AbstractLinkedListNode) {
if (this.head === null) {
this.head = node;
} else {
let current = this.head;
while (current.next !== null) {
current = current.next;
}
current.next = node;
}
}
}
// Using LinkedList with NumberNode and StringNode
const numberList = new LinkedList();
numberList.add(new NumberNode(1));
numberList.add(new NumberNode(2));
const stringList = new LinkedList();
stringList.add(new StringNode("Hello"));
stringList.add(new StringNode("World"));
console.log(numberList); // LinkedList with NumberNodes
console.log(stringList); // LinkedList with StringNodes
```
In this approach, `AbstractLinkedListNode` defines a common structure, and `NumberNode` and `StringNode` provide specific implementations. This allows for more complex and diverse node types but at the cost of type safety and simplicity, as the list can now technically contain a mix of different node types.
> [!info] Generics are just type magic, it doesn't say anything about the actual logic
## An Example when Generics is Highly Favoured
The scenario you're describing is a perfect use case for TypeScript's generics along with interface constraints. You want a function that accepts any object as long as it has a `run()` method and then returns an object of the same type. Here's how you could implement this:
### Step 1: Define the Interface
First, define an interface that requires a `run()` method. Any object you want to pass to the function must conform to this interface.
```typescript
interface Runnable {
run(): void;
}
```
### Step 2: Create the Function with Generics
Next, create a function that uses TypeScript generics to enforce that the input object conforms to the `Runnable` interface. The function will perform some calculations or operations and then return an object of the same type it received.
```typescript
function processAndReturn<T extends Runnable>(obj: T): T {
// Perform some calculations
console.log("Running the object's run method:");
obj.run();
// Perform more operations if necessary
// ...
// Return the object of type T
return obj;
}
// Example classes that implement the Runnable interface
class Runner implements Runnable {
run() {
console.log("Runner is running");
}
}
class Swimmer implements Runnable {
run() {
console.log("Swimmer is swimming");
}
}
// Using the function with different types
const runner = new Runner();
const returnedRunner = processAndReturn(runner);
console.log(`Returned object is of type: ${returnedRunner.constructor.name}`);
const swimmer = new Swimmer();
const returnedSwimmer = processAndReturn(swimmer);
console.log(`Returned object is of type: ${returnedSwimmer.constructor.name}`);
```
If we don't use generics, we will lost the type information. If we try to do it in the javascript level instead of the typescript level, the code will be clumsy.
```ts
function processAndReturn(obj: Runnable) {
obj.run();
return obj;
}
// ... Runner and Swimmer classes ...
const runner = new Runner();
const returnedRunner = processAndReturn(runner);
if (returnedRunner instanceof Runner) {
console.log("Returned object is a Runner");
// use Runner methods
}
const swimmer = new Swimmer();
const returnedSwimmer = processAndReturn(swimmer);
if (returnedSwimmer instanceof Swimmer) {
console.log("Returned object is a Swimmer");
// use Swimmer methods
}
```
> [!info] generics is good at static type inference
## Problem of Generics
1. generics are just type, they are not javascript
2. they can make the code less readable and increase mental overhead
## When to Use Generics
```ts
function processAndReturn<T extends Runnable>(obj: T): T {
obj.run();
return obj; // this will return T
}
function processAndReturn(obj: Runnable){
obj.run();
return obj; // this will return Runnable
}
```
==the ultimate reason why typescript generics is useful is that the typescript treat each "block" as a self-contained piece of code that has all the type information.==
==The first example is basically saying that "I know it is some Runnable but I am not sure exactly which Runnable it is" while the second example doesn't have this meaning.==
If typescript is smart, then it should know the following is a swimmer because the swimmer type is unknown in static time already. In other words, ==typescript generics is a manual fix to typescript stupidity==.
```ts
const swimmer = new Swimmer();
const returnedSwimmer = processAndReturn(swimmer);
```
And that’s why abstract class or other kind of typescript tricks are not the same.