Skip to main content

Command Palette

Search for a command to run...

Leveraging TypeScript Type Guards for Precise Filtering in Arrays

Updated
2 min read
Leveraging TypeScript Type Guards for Precise Filtering in Arrays

Since functional programming gained popularity in the JavaScript world, a trio of array methods—namely, map, filter, and reduce—have also risen to prominence. These methods became go-to tools for two primary reasons:

  1. They're simpler for doing basic looping with arrays.

  2. They allow method chaining, resembling the builder design pattern, enhancing coding flexibility.

Problem

However, there's a catch when using TypeScript's filter. Consider the following code:

const posts = [
    {id: 0, title: "Title 1", content: "Content 1"},
    {id: 1, title: "Title 2", content: "Content 2"},
    {id: 2, title: "Title 3", content: "Content 3"},
    null,
    undefined
];

const filteredPosts = posts.filter(post => post != null);

Surprisingly, TypeScript infers filteredPosts as a mix of valid posts and null values (i.e. ({id: number, title: string, content: string} | null)[]). How can we ensure TypeScript infers the correct type?

Type Guard

Fortunately, TypeScript offers a solution through type guards, expressions that perform runtime checks, ensuring correct type inference. Let's see it in action:

// Constructed this type for convenience
type Post = {id: number, title: string, content: string};

const posts = [
    {id: 0, title: "Title 1", content: "Content 1"},
    {id: 1, title: "Title 2", content: "Content 2"},
    {id: 2, title: "Title 3", content: "Content 3"},
    null,
    undefined
];

const filteredPosts = posts.filter((post): post is Post => post != null);

Now, TypeScript understands filteredPosts as an array containing only Post (i.e. {id: number, title: string, content: string}[]).

Improving Reusability

Let's level up our function's versatility using generics and TypeScript's NonNullable utility type:

const posts = [
    {id: 0, title: "Title 1", content: "Content 1"},
    {id: 1, title: "Title 2", content: "Content 2"},
    {id: 2, title: "Title 3", content: "Content 3"},
    null,
    undefined
];

const chooseNonNullable = <T>(arg: T): arg is NonNullable<T> => arg != null;

const filteredPosts = posts.filter(chooseNonNullable);

Now, you can export chooseNonNullable and use it as a callback to any filter methods to remove both null and undefined.

Addressing an Alternative

You might wonder about using the as keyword instead of type guards. While possible, it's less reliable as it assumes a type without verification, leading to potential errors. Stick with type guards for accuracy and safety!

More from this blog

// Sean's SWE Journey

19 posts

Love to learn, develop and share new ideas 👨‍💻