The error you already know

In Python you would have reached for a TypeVar and moved on. def first(items: list[T]) -> T says the item you pull out is the item you put in, and you wrote lines like it for years without ever calling them generics. Crossing over, the nerve failed. Generics read like a TypeScript ceremony, so you typed the array as unknown to stay flexible and told yourself you would sort the type out later.

first.ts
// You skipped the type parameter and typed
// the array as unknown to stay flexible.
// The function hands back whatever
// it pulled out, which the compiler
// now calls unknown.
function first(items: unknown[]): unknown {
return items[0];
}
const subjects = [
"Invoice",
"Re: lunch",
"Standup notes",
];
const top = first(subjects);
// You know it is a string. The compiler
// only knows it is unknown.
top.toUpperCase'top' is of type 'unknown'.();

Later arrives on the next line. first returns unknown, and unknown refuses every method until you prove what it is. You know it is a string, but the compiler will not take your word, because you erased the one fact that tied the input to the output.

The flexible-looking escape hatch cost you the element type. The TypeVar you already write in Python keeps it. That is all a generic is here, the same promise with different punctuation.

first.ts 'top' is of type 'unknown'.

Watch the habit become the idiom

The fix is the concept you already use, spelled the TypeScript way. The TypeVar helper you would write in Python crosses over, the type parameter moving from its own line into the angle brackets at the function.

Step through it at your own pace. Each click is one move.

Step 1 of 3

Good Python. Declare a TypeVar once, then first says the item you take out is the item you put in.

first.pyPython
from typing import TypeVar

T = TypeVar("T")

def first(items: list[T]) -> T:
    return items[0]

Here's what each move does.

  • Start in Python, where a TypeVar is declared on its own line and def first(items: list[T]) -> T reuses it. The T going in is the T coming out.
  • Cross to TypeScript and that separate declaration folds into the signature. You write the parameter in angle brackets right after the name, function first<T>, and the rest reads exactly like the Python.
  • Call it. TypeScript reads the argument, fills in T from the array you passed, and the result lands as string at one call and number at the next, all from one definition.

Python's bounded TypeVar carries over too. Where you wrote TypeVar("T", bound=Message) to keep T inside a family, TypeScript writes the bound inline as <T extends Message>, the same fence sitting next to the parameter instead of on its own line.

The part TypeScript adds is the inference at the call. You could write first<string>(subjects) and pin T by hand, but you never do. The compiler fills the parameter in from the argument, so the angle brackets you were afraid of appear once, at the definition, and every caller stays bare.

Now make the compiler agree

Here are two helpers that do one job, split only by the element type they accept. Collapse them into a single generic function named first, declaring the type parameter in angle brackets at the function. The call sites below should then read string from a string array and number from a number array, with no angle brackets at the call and no any standing in for a real type parameter.

Loading...
type-checking…