Why filter(Boolean) Is Not a Nullish Filter in TypeScript πŸ”§

Why filter(Boolean) Is Not a Nullish Filter in TypeScript πŸ”§

●1 ●4 ●33
calendar_today ago β€’ schedule5 min read
β€” Originally published at dev.to

Hoi hoi! πŸ‘‹

I'm @nyaomaru, a frontend engineer building small TypeScript OSS with shoyu ramen. 😸🍜

Today, let's look at a very small line of JavaScript.

values.filter(Boolean);

You've probably seen it before.

  • It's short.
  • It's convenient.
  • And sometimes, it's exactly what you want.

But if your intention is

Remove only null and undefined

then filter(Boolean) is doing more than you asked.

Let's see why! πŸ‘€

Image description


πŸ•³οΈ The Small Trap in filter(Boolean)

Imagine we have this array.

const values = ["ready", "", 0, false, null, undefined];

Suppose we try to remove the missing values like this.

const result = values.filter(Boolean);

At runtime, what remains?

["ready"];

Wait.

We only wanted to remove

null;
undefined;

But we also lost

"";
0;
false;

Why?

Because Boolean converts its argument to a boolean and keeps only values
whose result is true.

It isn't checking whether a value is nullish.


πŸ€” Falsy and Nullish Are Different

JavaScript considers all of these values falsy

false;
0;
("");
null;
undefined;
NaN;

But these two requirements are different

Remove every falsy value

and

Remove null and undefined

In real applications, 0, false, and '' can all be completely valid data.

For example

type Settings = {
  retryCount: number;
  notificationsEnabled: boolean;
  nickname: string;
};

These can all be valid

retryCount = 0;
notificationsEnabled = false;
nickname = "";

Falsy does not mean missing.


🧠 There Is a TypeScript Difference Too

filter(Boolean) removes falsy values at runtime, but Boolean is not a type guard.

TypeScript therefore does not generally know that null and undefined are gone.

const values: Array<string | number | boolean | null | undefined> = [
  "ready",
  "",
  0,
  false,
  null,
  undefined,
];

const result = values.filter(Boolean);

// result: Array<string | number | boolean | null | undefined>

So filter(Boolean) can be right when truthiness is the runtime rule, but it doesn't express a nullish-removal rule to either JavaScript readers or the TypeScript type system. πŸ™€


βœ… Say What You Actually Mean

If the rule is

Keep everything except null and undefined

we can write exactly that

const values: Array<string | null | undefined> = [
  "Ada",
  null,
  "Linus",
  undefined,
];

const names = values.filter(
  (value): value is string => value !== null && value !== undefined,
);

Now

// names: string[]

This is perfectly good TypeScript.

You don't need a library for a one-off check.

⚠️ A browser edge case: avoid value != null

You may also see this shorter version

values.filter((value): value is string => value != null);

For ordinary values, it looks equivalent to checking both null and
undefined. In browsers, though, document.all is a historical compatibility exception.

document.all == null; // true
document.all != null; // false

document.all === null; // false
document.all === undefined; // false

document.all is an object, not null or undefined, but loose equalityγ€€treats it as nullish. So if the contract is only β€œremove null andγ€€undefined”, use strict comparisons.

(value): value is string => value !== null && value !== undefined;

You will rarely encounter this in application code, but the edge case is why a precise nullish guard should not be implemented with != null.

See MDN's equality operator reference for the compatibility rule behind this behavior.


πŸ” What If You Keep Writing It?

The interesting part starts when the same meaning appears repeatedly.

(value): value is string => value !== null && value !== undefined;

Maybe in

  • API adapters
  • selectors
  • UI helpers
  • mapped data
  • utility functions

At that point, the useful abstraction isn't really the syntax.

It's the meaning

This value is not nullish.

That's where I like using a named type guard.

With is-kit

import { isNotNil } from "is-kit";

const names = values.filter(isNotNil);

The result still narrows naturally

// names: string[]

And unlike Boolean, valid falsy values stay intact.

const values: Array<string | number | boolean | null | undefined> = [
  "ready",
  "",
  0,
  false,
  null,
  undefined,
];

const result = values.filter(isNotNil);

// ['ready', '', 0, false]
// result: Array<string | number | boolean>

🧩 A Practical Example

Nullable values often appear after map.

type User = {
  id: string;
  nickname?: string | null;
};

const users: User[] = [
  { id: "1", nickname: "nyaomaru" },
  { id: "2", nickname: null },
  { id: "3" },
];

We want only the existing nicknames.

const nicknames = users.map((user) => user.nickname).filter(isNotNil);

// string[]

This is the kind of place where a reusable guard feels natural to me.

The array transformation stays ordinary JavaScript, while TypeScript knows
that the nullish values are gone.


βš–οΈ Which One Should You Use?

I think the choice is pretty simple.

Use

filter(Boolean);

when you genuinely want

Keep only truthy values.

Use an inline predicate when the nullish check appears once:

values.filter(
  (value): value is string => value !== null && value !== undefined,
);

And use a reusable guard such as

values.filter(isNotNil);

when that same meaning appears repeatedly.

There is no need to turn every condition into an abstraction.


πŸ” Can ESLint Catch This?

I recently released eslint-plugin-is-kit, a type-aware ESLint plugin for TypeScript predicates.

One of its rules looks specifically for ambiguous filter(Boolean) calls.

declare const values: Array<string | null>;

values.filter(Boolean);

Because the element type contains both null and a non-nullish falsy value (""), the plugin can warn that Boolean may remove more than just the missing value.

But it does not simply ban filter(Boolean).

declare const values: number[];

values.filter(Boolean);

There is no null or undefined in the element type, so there is no evidence that nullish removal was intended.

The rule is deliberately conservative.

The initial v0.1.0 release includes four type-aware rules for:

  • ambiguous filter(Boolean) calls
  • redundant is-kit predicates
  • repeated inline nullish filters that could use isNotNil
  • inline predicates that can be expressed with reusable type guards

For example

values.filter((value) => typeof value === "string");

can be expressed as

values.filter(isString);

when doing so preserves the runtime behavior and useful TypeScript narrowing.

If this kind of check would be useful in your codebase:

It's still an early release, so false positives and ideas for useful rules are very welcome. 😸


🎯 The Important Part

The main point isn't really isNotNil.

It's this

Falsy values and missing values are not the same thing.

filter(Boolean) is not bad code.

It just expresses a different requirement.

So before writing

values.filter(Boolean);

ask

Do I want to remove falsy values, or only nullish values?

That tiny distinction can prevent valid data like 0, false, and '' from disappearing unexpectedly. 😸

I also wrote a more complete guide about nullish filtering on the is-kit
documentation site
, including isNil, isNotNil, and the different
approaches.

If you like small reusable TypeScript type guards, is-kit is open source too! And don't forget to put a star! ⭐

https://github.com/nyaomaru/is-kit

Thanks for reading! πŸ™Œ

Part 3 of 3 in is-kit
πŸ”₯ Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

TypeScript Complexity Has Finally Reached the Point of Total Absurdity

Karol Modelski - Apr 23

The Sovereign Vault β€” A Comprehensive Guide to Protocol-Driven AI

Ken W. Algerverified - Jun 4

How I Built a React Portfolio in 7 Days That Landed β‚Ή1.2L in Freelance Work

Dharanidharan - Feb 9

I’m a Senior Dev and I’ve Forgotten How to Think Without a Prompt

Karol Modelski - Mar 19

Merancang Backend Bisnis ISP: API Pelanggan, Paket Internet, Invoice, dan Tiket Support

Masbadar - Mar 13
chevron_left
624 Points β€’ 38 Badges
Heerlen, the Netherlands β€’ nyaomaru-portfolio.vercel.app
14Posts
12Comments
30Connections
Funny Frontend Engineer! ? Living in the Netherlands.

Related Jobs

View all jobs β†’

Commenters (This Week)

1 comment
1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!