Default parameters with generics in TypeScript

You could use a generic parameter default and a type assertion to tell the compiler that a lack of inference for A implies that A is Lion, and that it’s okay to make that assumption:

function createInstanceAssert<A extends Animal = Lion>(c: new () => A = Lion as any) {
  return new c();
}

This will work as desired in your use cases, I think:

createInstanceAssert(Lion).keeper.nametag;
createInstanceAssert(Bee).keeper.hasMask;
createInstanceAssert().keeper.nametag;

Although there is the possibility of someone manually specifying the generic parameter and causing trouble:

createInstanceAssert<Bee>().keeper.hasMask.valueOf(); // compiles, but error at runtime
// ---------------> ~~~~~ don't do this, okay?

That’s probably not likely, but you should be aware of it.


If you really want to prevent misuse you could use overloads to distinguish the two separate use cases:

function createInstanceOverload<A extends Animal>(c: new () => A): A;
function createInstanceOverload(): Lion;
function createInstanceOverload(c: new () => Animal = Lion) {
  return new c();
}

You can basically call that with a parameter, in which case it is generic and A is inferred, or you can call it with no parameter, in which case it is not generic, and a Lion comes out:

createInstanceOverload(Lion).keeper.nametag;
createInstanceOverload(Bee).keeper.hasMask;
createInstanceOverload().keeper.nametag;

Since there is no generic zero-arg call signature, the following is no longer possible without a big compiler error:

createInstanceOverload<Bee>().keeper.hasMask.valueOf(); // error at compile time

Playground link to code

Leave a Comment