How to Safely Filter null and undefined from Arrays in TypeScript
Remove nullish values, preserve valid falsy values, and let TypeScript infer the array you actually have.
The quick answer
Pass isNotNil directly to Array.filter.
import { isNotNil } from 'is-kit';const values: Array<string | null | undefined> = ['Ada',null,'Linus',undefined,];const names = values.filter(isNotNil);// ^? string[]
At runtime, null and undefined are removed. At compile time, the result narrows from Array<string | null | undefined> to string[].
That is the whole solution. But the common alternatives are worth understanding, because they do not all mean the same thing.
The tempting shortcut: filter(Boolean)
You may already have code like this:
const values: Array<string | null | undefined> = ['Ada',null,'',undefined,];const names = values.filter(Boolean);// Runtime result: ['Ada']// TypeScript type: Array<string | null | undefined>
This works.
But it solves a broader problem.
Boolean removes every falsy value, not only null and undefined. That includes empty strings, zero, and false. It also does not communicate a nullish-specific type predicate to Array.filter.
“Remove nullish values” and “remove every falsy value” are different requirements.
Preserve valid falsy values
A value does not become missing just because JavaScript considers it falsy.
import { isNotNil } from 'is-kit';const values: Array<string | number | boolean | null | undefined> = ['ready','',0,false,null,undefined,];const presentValues = values.filter(isNotNil);// ['', 0, false, 'ready'] are all valid non-nullish values.// Type: Array<string | number | boolean>
This distinction matters in real application data. A quantity of 0, a disabled flag set to false, or an empty user input may all be valid values.
isNotNil rejects exactly two values: null and undefined.
The explicit inline version
You do not need a library for a one-off check. An explicit type predicate is perfectly valid TypeScript.
const values: Array<string | null | undefined> = ['Ada',null,undefined,];const names = values.filter((value): value is string => value !== null && value !== undefined,);
The code is not broken. It is simply repetitive when the same rule appears across API adapters, selectors, and UI helpers.
isNotNil gives that rule one reusable name. Because it is generic, it preserves whatever non-nullish union each array already contains.
A practical object example
Nullable values often appear after mapping an object property.
import { isNotNil } from 'is-kit';type User = {id: string;nickname?: string | null;};const users: User[] = [{ id: '1', nickname: 'nyaomaru' },{ id: '2', nickname: null },{ id: '3' },];const nicknames = users.map((user) => user.nickname).filter(isNotNil);// nicknames: string[]
The guard is still just a function. It works naturally at the normal control-flow point where the nullable values appear.
Build small guards, then reuse them where TypeScript narrowing matters.
isNil and isNotNil
Use isNotNil when you want the non-nullish values. Use isNil when you want to handle the missing branch itself.
import { isNil, isNotNil } from 'is-kit';declare const value: string | null | undefined;if (isNil(value)) {// value: null | undefined}if (isNotNil(value)) {// value: string}
For the complete nullability API—including nullable, nullish, optional, and required—see the nullish API reference.
Which approach should you use?
| Approach | Best for | Watch out for |
|---|---|---|
| filter(Boolean) | Removing every falsy value | Also removes 0, false, and '' |
| Inline predicate | A local, one-off check | Repeats easily across a codebase |
| filter(isNotNil) | Reusable nullish filtering | Requires importing is-kit |
There is no need to turn every inline condition into an abstraction. Use isNotNil when the shared meaning and reusable narrowing make the call site clearer.
Summary
- Use
filter(isNotNil)to removenullandundefinedwhile narrowing the result. - Avoid
filter(Boolean)when zero, false, or empty strings are valid values. - An inline predicate is fine when the check is truly one-off.
- Prefer a named guard when the same runtime meaning appears in more than one place.
The goal is not shorter syntax alone. It is making “present value” mean the same thing everywhere your TypeScript application needs it.