Start learning free

LESSON 53 OF 348 · FUNCTIONS I

Generics in TypeScript: One Function, Any Array

Lesson Read the full explanation

You already know how to write a function that works on a number[]. Say you want the first element of an array:

function firstNumber(arr: number[]): number | undefined {
    return arr[0];
}

It works — but only for numbers. The moment you need the same thing for strings, you have a problem.


The trap:

Hand your number-only function a string[]:

const words: string[] = ["alpha", "beta"];
firstNumber(words);

...and the compiler stops you cold:

error TS2345: Argument of type 'string[]' is not assignable to parameter of type 'number[]'.
  Type 'string' is not assignable to type 'number'.

Read it bottom-up: a string can't live where a number is required, so the whole array is rejected. Your two escape routes both hurt: copy-pasting a firstString twin duplicates code forever, and switching the parameter to any[] compiles — but the result becomes any and TypeScript stops checking anything you do with it.


The fix: a type placeholder.

Write <T> after the function name to declare a generic type parameter — a placeholder that gets filled in fresh at every call:

function first<T>(arr: T[]): T | undefined {
    return arr[0];
}

first([7, 14, 21]);        // T becomes number — returns number | undefined
first(["alpha", "beta"]);  // T becomes string — returns string | undefined

You never write the type argument yourself — TypeScript infers T from whatever array you pass. One function, full type safety for every element type.

And why T | undefined instead of just T? Because arr[0] on an empty array evaluates to undefined — the signature is honest about that possibility instead of promising a value it might not have.


Assignment
Follow the Coding Tutorial and build it yourself:

  • define function first<T>(arr: T[]): T | undefined that returns arr[0],
  • create const nums: number[] = [7, 14, 21]; and const words: string[] = ["alpha", "beta"];,
  • print "first number = " + first(nums) and "first word = " + first(words) with console.log.

The exact expected output is:

first number = 7
first word = alpha

Hint
You could call first<number>(nums) with an explicit type argument, but you almost never need to — inference fills in T from the argument.


STEP 1 OF 6
    Ctrl + Enter to check

    TYPESCRIPT · FUNCTIONS I

    Generics Lite in TypeScript, explained

    The problem generics solve

    Suppose you write a function that returns the first element of an array:

    function firstNumber(arr: number[]): number | undefined {
        return arr[0];
    }
    

    It works, but only for arrays of numbers. Hand it a string[] and the compiler refuses:

    const words: string[] = ["alpha", "beta"];
    firstNumber(words);
    
    error TS2345: Argument of type 'string[]' is not assignable to parameter of type 'number[]'.
      Type 'string' is not assignable to type 'number'.

    There are two obvious ways around it, and both are poor. You can copy the function into a firstString twin, then another for every element type, and keep them all in sync forever. Or you can type the parameter as any[], which compiles for everything but also switches off checking for whatever comes out:

    function firstAny(arr: any[]): any {
        return arr[0];
    }
    
    const x = firstAny(["a"]);
    console.log(x.toFixed(2));
    

    This compiles without a complaint, because x is any and anything is allowed on it. At run time it crashes with TypeError: x.toFixed is not a function, since x is a string. The types stopped helping exactly where you needed them.

    A type parameter

    A generic function takes a type as well as values. You declare it with angle brackets after the function name:

    function first<T>(arr: T[]): T | undefined {
        return arr[0];
    }
    

    T is a placeholder, a name for "whatever the element type turns out to be". It is a normal identifier; T is only a convention, and longer names such as Item work as well. Inside the function you use T like any other type. The parameter is an array of T, and the result is a T or undefined.

    Each call fills the placeholder in separately:

    first([7, 14, 21]);        // T is number, result is number | undefined
    first(["alpha", "beta"]);  // T is string, result is string | undefined
    

    You do not write the type yourself. TypeScript infers T from the argument, looking at what the array holds. You may write it explicitly, as in first<number>(nums), but you almost never need to.

    What the checking gives you

    Because the result type follows the argument, mistakes are caught again. Assigning the first element of a string array to a number is an error:

    const wrong: number = first(["alpha"]);
    
    error TS2322: Type 'string' is not assignable to type 'number'.

    One function now has full type safety for every element type, with no copies.

    Why T | undefined

    Reading an index that does not exist never throws in JavaScript. On an empty array, arr[0] evaluates to undefined. The return type T | undefined records that possibility. In the compiler settings used in this lesson, undefined is quietly allowed in every type, so the compiler won't force you to check for it. Writing the type honestly still documents what callers should expect, and with stricter settings the compiler would insist on the check.

    The exercise, step by step

    1. Declare the generic function. Write the header with <T> after the name, a parameter arr of type T[], and the return type T | undefined.
    2. Return the first element. The body is return arr[0];.
    3. Create a number array. nums is a number[] holding 7, 14 and 21.
    4. Create a string array. words is a string[] holding "alpha" and "beta". The function needs no change to accept it, which is the point of the lesson.
    5. Print the number. Join the label "first number = " with first(nums). Here T is inferred as number, and the + turns the number into text.
    6. Print the word. Do the same with "first word = " and first(words), where T is string.

    The full solution

    function first<T>(arr: T[]): T | undefined {
        return arr[0];
    }
    const nums: number[] = [7, 14, 21];
    const words: string[] = ["alpha", "beta"];
    console.log("first number = " + first(nums));
    console.log("first word = " + first(words));
    

    The output is:

    first number = 7
    first word = alpha

    Gotchas

    • Generics exist only while compiling. The type parameter is erased, so the JavaScript that runs is just function first(arr) { return arr[0]; }. A generic does not change what the code does, only what the compiler checks.
    • Empty arrays. first([]) gives undefined at run time. The text "first = " + first(e) for an empty e would print first = undefined.
    • Mixed arrays. first([1, "a"]) is accepted: T becomes string | number.
    • Prefer inference. Writing first<string>(nums) by hand with the wrong type is an error, and inference makes it unnecessary.

    Key takeaways

    • <T> after a function name declares a type placeholder that is filled in at every call.
    • TypeScript infers T from the arguments, so callers normally write nothing extra.
    • A generic keeps type safety for every element type, which any[] does not.
    • Indexing an empty array gives undefined, so T | undefined describes the result accurately.
    Keep going with TypeScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free