There is no difference if this is identity function that just returns an argument and used without type restrictions:
const foo: any = fn(['whatever']);
And there is a difference for typed code:
const foo: string = fn('ok');
const bar: string = fn([{ not: 'ok' }]);
Also, the usage of generic type provides semantics. This signature suggests that the function is untyped and returns anything:
function fn(arg: any): any { ... }
This signature suggests that the function returns the same type as its argument:
function fn<T>(arg: T): T { ... }
Real functions are usually more meaningful than just return arg example. Generic type can benefit from type restrictions (while any obviously can’t):
function fn<T>(arg: T[]): T[] {
return arg.map((v, i) => arg[i - 1]);
}
But the benefits become more obvious when the function is used in conjunction with other generic classes and generic functions (and eliminated if non-generics are involved):
function fn<T>(arg: T[]): T[] {
return Array.from(new Set<T>(arg));
}
This allows to consistently maintain T type between input (argument) and output (returned value):
const foo: string[] = fn(['ok']);
const bar: string[] = fn([{ not: 'ok' }]);
There cannot be any difference in performance because TypeScript types exist only on design time.