Generics in TypeScript

Generics, also known as type parameters, are a powerful feature in TypeScript that allow for the creation of reusable components.

As an example of a generic function, slicesIndex takes an array of any type and an element of that type and returns the index of the first occurrence of v in s, or -1 if not present.

function slicesIndex<T>(s: T[], v: T): number {
    for (let i = 0; i < s.length; i++) {
        if (v === s[i]) {
            return i;
        }
    }
    return -1;
}

As an example of a generic type, List is a singly-linked list with values of any type.

class List<T> {
    private head: Element<T> | null = null;
    private tail: Element<T> | null = null;

    push(v: T): void {
        if (this.tail === null) {
            this.head = new Element(v);
            this.tail = this.head;
        } else {
            this.tail.next = new Element(v);
            this.tail = this.tail.next;
        }
    }

    allElements(): T[] {
        const elems: T[] = [];
        let e = this.head;
        while (e !== null) {
            elems.push(e.val);
            e = e.next;
        }
        return elems;
    }
}

class Element<T> {
    next: Element<T> | null = null;
    constructor(public val: T) {}
}

Here’s how we can use these generic types and functions:

function main() {
    const s = ["foo", "bar", "zoo"];

    // When invoking generic functions, we can often rely
    // on type inference. Note that we don't have to
    // specify the type for T when calling slicesIndex - 
    // the compiler infers it automatically.
    console.log("index of zoo:", slicesIndex(s, "zoo"));

    // ... though we could also specify it explicitly.
    slicesIndex<string>(s, "zoo");

    const lst = new List<number>();
    lst.push(10);
    lst.push(13);
    lst.push(23);
    console.log("list:", lst.allElements());
}

main();

This code will output:

index of zoo: 2
list: [10, 13, 23]

In TypeScript, generics provide a way to create reusable components that work with a variety of types rather than a single one. This allows for writing flexible, reusable functions and classes without compromising type safety.