How to make Generic Functions that accept a value or an object with certain properties in TypeScript?

January 16, 2022 - 5 min read

To make generic functions that accept a value or an object as a parameter but with certain properties, you have to use the extends keyword on the generic type parameter and add some constraints on the generic type parameter for the function in TypeScript.

TL;DR

// an interface
interface ShouldHaveLengthProp {
  length: number;
}

// a generic function that returns the
// value of the `length` property of
// the object passed to it
// with constraints for generic parameter type
function getLength<Type extends ShouldHaveLengthProp>(data: Type): number {
  return data.length; // Valid ✅
}

// call the function
getLength(1); // Error ❌. Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
getLength(true); // Error ❌. Argument of type 'boolean' is not assignable to parameter of type '{ length: number; }'.
getLength([1, 2, 3, 4]); // Valid ✅. Returns => 4
getLength(["Roy", "Lily"]); // Valid ✅. Returns => 2
getLength({ name: "John", length: 5 }); // Valid ✅. Returns => 5

For example, let's say we need to make a generic function where it will return the value of the length property of whatever type is passed to the function as an argument like this,

// a generic function that returns the
// value of the `length` property of
// the object passed to it
function getLength<Type>(data: Type): number {
  return data.length; // Error ❌. Property 'length' does not exist on type 'Type'.
}

If you want to know more on creating Generic functions in TypeScript, See the blog on How to make Generic Functions that change return value type according to the parameter values type in TypeScript?.

As soon as you write the function code, the TypeScript compiler will start showing an error on accessing data.length saying that Property 'length' does not exist on type 'Type'..

This is expected since the function can accept values of any kind of type and not all type in TypeScript has the length property attached to them. The length property is available to values of the array, string, and any object type with a property called length in it.

So what we need to do is add constraints to the generic type parameter using the extends keyword after the generic type parameter name and then add the constraints we need to add there.

It can be done like this,

// a generic function that returns the
// value of the `length` property of
// the object passed to it
// with constraints for generic parameter type
function getLength<Type extends { length: number }>(data: Type): number {
  return data.length; // Valid ✅
}

As you can see that we have added the constraint of {length: number} after the extends keyword. This will check the object that is passed to the getLength function for the length property which also has a type of number. Adding constraints is like an if conditional checking for the generic type parameter in TypeScript. Cool. Right!? 😃.

Now let's call the getLength function and try to call the funtion with various value types like this,

// a generic function that returns the
// value of the `length` property of
// the object passed to it
// with constraints for generic parameter type
function getLength<Type extends { length: number }>(data: Type): number {
  return data.length; // Valid ✅
}

// call the function
getLength(1); // Error ❌. Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
getLength(true); // Error ❌. Argument of type 'boolean' is not assignable to parameter of type '{ length: number; }'.
getLength([1, 2, 3, 4]); // Valid ✅. Returns => 4
getLength(["Roy", "Lily"]); // Valid ✅. Returns => 2
getLength({ name: "John", length: 5 }); // Valid ✅. Returns => 5

As you can see that the first 2 function calls with number and boolean values types have error since both these type doesn't have the property of length in it and the last 3 functions with array and an object type are valid since both these values have the property of length attached to it.

For better readability let's write the generic type parameter constraint as a separate type called ShouldHaveLengthProp like this,

// an interface
interface ShouldHaveLengthProp {
  length: number;
}

// a generic function that returns the
// value of the `length` property of
// the object passed to it
// with constraints for generic parameter type
function getLength<Type extends ShouldHaveLengthProp>(data: Type): number {
  return data.length; // Valid ✅
}

// call the function
getLength(1); // Error ❌. Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
getLength(true); // Error ❌. Argument of type 'boolean' is not assignable to parameter of type '{ length: number; }'.
getLength([1, 2, 3, 4]); // Valid ✅. Returns => 4
getLength(["Roy", "Lily"]); // Valid ✅. Returns => 2
getLength({ name: "John", length: 5 }); // Valid ✅. Returns => 5

We have successfully made a generic function that accepts an object with a certain property, in our case (the length property). Yay 🥳!

See the above code live in codesandbox.

That's all 😃!

Feel free to share if you found this useful 😃.