In a `.tsx` file, `const identity = <T>(x: T) => x;` fails to compile even though the identical line is fine in a `.ts` file. Why, and how do you write a generic arrow function there?
answer
- the extension changes the grammar
- angle bracket has two meanings here
- the parser thinks it is a tag
- a comma cannot follow a tag name
- <T,> or a constraint, or use function
basics
~20 sIn .tsx files the parser reads <T> as the opening tag of a JSX element, not a type parameter list, so the line never parses as a generic arrow. Disambiguate with a trailing comma, <T,>, with a constraint, or by using a function declaration.
solid answer
~50 sIt is a parsing ambiguity, not a type error. In a `.tsx` file `<T>` at the start of an expression is a JSX element, so the compiler reads it as an unclosed tag and reports a JSX error rather than seeing a type parameter list. In a plain `.ts` file JSX syntax is off, so there is no ambiguity and the same line parses fine. Three fixes are common: write `<T,>(x: T) => x`, where the trailing comma cannot appear in JSX and forces the type-parameter reading; write `<T extends unknown>(x: T) => x`, where `extends` does the same job; or sidestep it entirely with `function identity<T>(x: T): T { return x; }`, since a `function` declaration is never JSX. All three produce exactly the same type and the same emitted JavaScript — the choice is purely stylistic.
code
typescript · 8 lines// All three declare the same generic identity function
// and all three parse correctly inside a .tsx file.
const withComma = <T,>(x: T) => x;
const withConstraint = <T extends unknown>(x: T) => x;
function asDeclaration<T>(x: T): T {
return x;
}go deeper
Recognise the JSX parse error for what it is and remember the trailing-comma form, <T,>, as the standard way to write a generic arrow function in a .tsx file.
Explain that the file extension selects the grammar, that <T> is read as an opening JSX tag in .tsx, and why a comma or an extends keyword removes the ambiguity.
Diagnose it quickly from the symptom: a cascade of nonsensical JSX errors after a .ts to .tsx rename usually traces to one generic arrow, and the first error is the real one.
Settle it as a convention rather than leaving it to taste — pick one form, encode it in lint and formatter config, so the workaround never becomes a recurring review conversation.
## Two grammars sharing one character Angle brackets are overloaded in TypeScript. They open a type parameter list (`function f<T>()`), they open an explicit type argument list at a call site (`f<string>(x)`), and — when JSX is enabled — they open a JSX element (`<Foo />`). The compiler decides which grammar applies from the file extension and the `jsx` compiler option: `.ts` files never parse JSX, `.tsx` files do. That makes one specific construct ambiguous. In ```tsx const identity = <T>(x: T) => x; ``` the parser is at the start of an expression and sees `<T>`. In a `.tsx` file the JSX grammar wins, so `<T>` is read as the opening tag of an element named `T`, `(x: T) => x` is read as its children, and the file ends without a closing `</T>`. The resulting diagnostic mentions JSX, not generics, which is what makes the error confusing the first time you hit it: nothing in the message points at the type parameter you were trying to declare. ## Why the same line is fine in `.ts` In a `.ts` file JSX parsing is simply not available, so `<` at the start of an expression can only begin a type parameter list (or, historically, an angle-bracket type assertion). No ambiguity, no error. This is why the bug appears the moment you move a helper out of a utility module into a component file, and why it never shows up in your unit-test file for the same helper. ## The three fixes **Trailing comma.** `const identity = <T,>(x: T) => x;` A JSX tag name cannot be followed by a comma, so the comma eliminates the JSX reading and the parser commits to a type parameter list. TypeScript allows a trailing comma in a type parameter list, so `<T,>` is exactly equivalent to `<T>`. This is the most compact fix and the one you will see most often in React codebases. **A constraint.** `const identity = <T extends unknown>(x: T) => x;` The keyword `extends` cannot appear inside a JSX tag either, so it disambiguates the same way. Constraining to `unknown` constrains nothing, since every type is assignable to `unknown` — it is present purely for the parser. Some teams prefer this because the trailing comma looks like a typo and formatters have historically been tempted to remove it. **Use a `function` declaration.** `function identity<T>(x: T): T { return x; }` A `function` keyword can never start a JSX element, so the ambiguity does not arise. If the value must be an expression, `const identity = function <T>(x: T): T { return x; };` works for the same reason. ```tsx const a = <T,>(x: T) => x; const b = <T extends unknown>(x: T) => x; function c<T>(x: T): T { return x; } ``` ## What does not change None of this touches the type. All three forms declare one type parameter inferred per call, and all three erase to the same JavaScript — the type parameter list, the trailing comma and the vacuous constraint all vanish at compile time. There is no runtime difference and no behavioural difference for callers, so this is not a design decision, it is a syntax workaround you should recognise instantly and move past. ## Practical notes The issue is scoped to *arrow function expressions* in JSX-enabled files. Generic classes, generic interfaces, generic type aliases and generic `function` declarations in `.tsx` files are all unaffected, because in those positions the parser is not at the start of an expression and the JSX grammar does not apply. Explicit type arguments at a call site — `identity<string>("hi")` — are also fine, since the `<` follows an expression rather than starting one. A related trap: because the failure is a *parse* error, editors often cascade it into a wall of unrelated squiggles further down the file, and the real cause is the first error, not the loudest one. When a `.tsx` file suddenly reports many nonsensical JSX errors, look for a generic arrow near the top. Finally, if you are converting a file from `.ts` to `.tsx` — a common step when a helper grows a small component — expect exactly this failure on every generic arrow in it, and expect nothing else about the generics to change.
- Does the trailing comma in `<T,>` change the type of the function or the emitted output in any way?No. A trailing comma is permitted in a type parameter list and carries no meaning, so `<T,>` and `<T>` declare exactly the same type parameter. Both erase completely at compile time, so the emitted JavaScript is identical. It exists solely to stop the parser from choosing the JSX reading.
- Why does `<T extends unknown>` also fix the parse?The `extends` keyword cannot appear inside a JSX opening tag, so its presence rules out the JSX reading and the parser commits to a type parameter list. Constraining to `unknown` is vacuous — every type satisfies it — so the constraint changes nothing about what callers may pass; it is there purely as a parser hint.
- Does the same problem affect a generic `class` or `interface` declared in a `.tsx` file?No. The ambiguity only arises where a `<` begins an expression, which is where JSX is legal. In a class, interface or type alias declaration the parser is in a declaration position, so `<T>` can only be a type parameter list. Generic `function` declarations are safe for the same reason.
saying these in an interview costs you the question
- Says arrow functions cannot be generic in TypeScript
- Thinks it is a type error rather than a parse error
- Claims the trailing comma changes the inferred type
- Assumes a compiler flag can turn the ambiguity off in .tsx
- Believes generic classes and interfaces hit the same problem