Often in typescript where we want to write some reusable code that can work with different types of data. We assume that this piece of code can accept arguments of different types and return results accordingly.

Let's say we write a function that returns any argument that it has received. ( Very bare example but stay with me. )

function retrieveArgs(arg:any){
return arg
}
 
const numberValue = retrieveArgs(40)
const stringValue = retrieveArgs("fourty"

The problem with this code is though it's able to work with different types but the return type is always any. Variables numberValue and stringValue will have type any . There is a relationship between the types of arguments you pass and the function's return type.

In order to improve type coverage we can use Generics. Generics allow you to pass “types as arguments to the function”. Go back and read the phrase again 😀. Yes, that is write . with typescript generics we state that we will dynamically pass type for the argument and we don't have to use any as a type

function retrieveArgs<T>(arg:T){
return arg
}
 
const numberValue = retrieveArgs<number>(40)
const stringValue = retrieveArgs<string>("fourty")

We are passing our types as arguments here and numberValue and stringValue has type string and number respectively

General Syntax:

function functionName<TypeParams>(FunctionParams){}  //     Declaration
 
functionName<Types>(arguments)   //     Call

Multiple Function Type Parameters

We can also pass multiple type parameters to a function as we do with function arguments

function makeTuples<T,K>(name:T,age:K){
    return [name,age] as const
}
 
makeTuples("Usman",27)

In the above example, we have not explicitly defined type params like makeTuples<string, number>("Usman",27) because typescript can infer them from function arguments as well.

Generic Interfaces

Like functions, you can declare generic interfaces as well. They follow same rules as generic functions. You can pass type arguments and use them to assign types to attributes

interface Human<T>{
    name: string
    age: T
}
 
const Luis: Human<string> = {name: "Luis", age: "23"}
const Martha: Human<number> = {name: "Luis", age: 23}

you can also implement these generic interfaces on classes as following

interface Humans<T>{
    name: string
    age: T
}
 
class People implements Humans<number>{
    name: string
     age: number
 
    constructor(name:string, age:number){
        this.name = name
        this.age = age
    }
 
    get getAge(){
        return this.age
    }
}
 
const jess = new People("Jess",23)
 
console.log(jess.getAge)

Generic Classes

We can have generic parameters on classes as well and these type parameters can then be used on class members type declarations

class Human<T>{
    name: string
    private age: T
 
    constructor(name:string, age:T){
        this.name = name
        this.age = age
    }
 
    get getAge():T{
        return this.age
    }
}
 
const jess = new Human("Jess",23)
 
console.log(jess.getAge)

Generic Type Literals

We can specify generic type literals as well

type Humans<T> = {name: string, age: T}
 
const Jess:Humans<string> =  {name:"Jess" , age:"23"}

Constrained Generic Types

one cool feature of generic types is that you can constrain your generic to specific types this means that if someone is passing type params other than what you specified it will scream. you constraint your generics with ‘extend’ keyword .

interface Human<T extends string | number>{
    name: string
    age: T
}
 
class People implements Human<number>{
    name: string
    age: number
 
    constructor(name:string, age:number){
        this.name = name
        this.age = age
    }
 
    get getAge(){
        return this.age
    }
}
 
const jess = new People("Jess",23)
 
console.log(jess.getAge)

now we can pass only type string or number to the Human interface . If we tried to send boolean it will give type error