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
nullandundefined
then filter(Boolean) is doing more than you asked.
Let's see why! π
π³οΈ 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
nullandundefined
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-kitpredicates - 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:
- GitHub: https://github.com/nyaomaru/eslint-plugin-is-kit
- npm: https://www.npmjs.com/package/eslint-plugin-is-kit
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! β
nyaomaru
/
is-kit
Build small guards. Compose them. Lightweight, zero-dependency TypeScript type guards for runtime validation and natural narrowing. Runtime-safe π‘οΈ, composable π§©, and ergonomic β¨.
is-kit
Build small guards. Compose them.
is-kit is a lightweight, zero-dependency toolkit for building reusable TypeScript type guards.
It helps you write small isFoo functions, compose them into richer runtime checks, and keep TypeScript narrowing natural inside regular control flow.
Runtime-safe π‘οΈ, composable π§©, and ergonomic β¨ without asking you to adopt a heavy schema workflow.
- Build and reuse typed guards
-
Compose guards with
and,or,not,oneOf - Validate object shapes and collections
-
Parse or assert
unknownvalues without a large schema framework
π Documentation Site Β· π§ Practical Guides
Best for app-internal narrowing, filtering, and reusable guards.
π€ Why use is-kit?
Tired of rewriting the same isFoo checks again and again?
is-kit is a good fit when you want to:
-
write reusable
isXfunctions instead of one-off inline checks - keep runtime validation lightweight and dependency-free
-
narrow values directly in
if,filterβ¦
Thanks for reading! π


Top comments (0)