What are Type Predicates in Typescript?

- 5 min read

An important piece of working with types in Typescript is the process known as type narrowing. What it is? Is the process of assert what type a variable is in a particular part of the data flow. In this article we will review one way to narrow types: Type Predicates Functions.

Type narrowing allows us to take a type that is too broad and refine it to a more specific type.

This is a critical concept because it can prevent errors and make our code more expressive and readable.

Understanding Type Predicate Functions

Type Predicate Functions are functions that return a boolean value and have a particular return type syntax. A type predicate is a type assertion that checks if an object has a specific property or set of properties. This allows TypeScript to narrow (or refine) the type of an object based on the result of the function.

sponsor

El contenido de este sitio es y será siempre gratuito para todos. Ayudame a mantenerlo así convirtiendote en auspiciador.
Matias Hernández Logo

Tu producto o servicio podría estar aquí

Here is an example of a Type Predicate Function:

ts
					
						
function isString(x: unknown): x is string {
  return typeof x === 'string';
}

					
				

In this example, the function isString takes an argument x of type unknown. The function then checks if the typeof x is 'string', and returns true or false accordingly.

The return type of the function is annotated as x is string, which asserts that x is of type string if the function returns true.

Then you can use the function inside your conditional blocks or in any other place of the code you need. The result of using will be that TS will recognize the argument used as the type asserted.

ts
					
						
function reverseString(x: unknown){
  // Here x is infered as unknown
  if (isString(x)) {
    // Here x is infered a string
    return x.split('').reverse().join('');
  }
	return null
}

					
				

In this example, the function reverseString takes an argument x of type unknown, which could be any value. The function then calls the Type Predicate Function isString to check if x is a string. If isString(x) returns true, then x is treated as a string and can be reversed using the split, reverse, and join string methods.

Pros and Cons of Using Type Predicate Functions for Type Narrowing

Ok, type predicates looks nice right? But as everything in life, there are pros and cons to every decision you take.

One of the major advantages of type predicate functions is that they provide a way to express complex type relationships in a readable and understandable way. They allow you to define custom functions that not only perform a specific task but also return a boolean value that tells TypeScript whether a variable is of a particular type. This can make your code more expressive and self-documenting.

They can also be useful when you need to perform dynamic type checks on an object. For example, imagine you have a function that takes an object as an argument, but you’re not sure whether the object has a specific property. With a type predicate function, you can check for the presence of that property and narrow the type of the object to include that property.

On the downside, type predicate functions can be more difficult to set up and use than conditional blocks. They require you to define custom functions and may require more code to get up and running.

But the most important problem of this functions is a risk, there is an easy way to introduce bugs to the process. You can wirte incorrect predicates leading to unexpected or undesired type narrowing. This can result in runtime errors or unexpected behavior, which can be difficult to diagnose and fix.

Type predicates ressembles the use (and pitfalls) of using as for type assertions, you can lie to the type system, it equals to say “I know more about this type than the compiler” and force the type to be the desired one, as an example:

ts
					
						
function isString(x: unknown): x is string {
  return typeof x === 'number';
}
					
				

The above example check if x is a number, and if that is true then the predicate say that the variable is a string. If later you use that type predicate, TS assume that the variable is an string and the type safety will be lost.

Best Practices for Using Type Predicate Functions for Type Narrowing

This is by no means an exhaustive list of “good ideas” to apply when working with type predicates, but are a good rule of thumb.

  • Define them carefully and ensure they are properly typed.
  • Use clear and descriptive names for Type Predicate Functions.
  • Use them only when they are appropriate and necessary.
  • Consider alternative approaches to type narrowing, such as conditional blocks or discriminated unions.
  • Use automated testing and code analysis tools to detect potential errors or inconsistencies in the program.

Conclusion

To sum up, Type Predicate Functions are a valuable asset for narrowing down types in TypeScript, which can enhance code readability, maintainability, and reduce errors. Nevertheless, developers should exercise caution and understand the potential drawbacks of using them. By adhering to recommended practices and guidelines, you can avoid compromising the type system’s integrity and create more dependable and resilient applications with TypeScript. Overall, Type Predicate Functions can be an excellent addition to your TypeScript toolbox if used judiciously and responsibly.

😃 Thanks for reading!

Did you like the content? Found more content like this by joining to the Newsletter or following me on Twitter

Follow the conversation on Twitter Share on Twitter
have liked ❤️ Matt Pocock

📖 Keep reading