isDefined Type Guard
Filtering an array of `(T | undefined)[]` should leave you with `T[]`, but `Array.prototype.filter(Boolean)` leaves the type as `(T | undefined)[]` because the compiler does not understand the predicate. A user-defined type guard with the `value is T` return type fixes the inference. This snippet covers the basic `isDefined`, an `isNonNullable` variant that also drops `null`, and a generic `compact` that uses the guard to narrow at the array level.
916 views
6
function isDefined<T>(value: T | undefined): value is T {
return value !== undefined;
}
const maybeStrings = ['a', undefined, 'b', undefined, 'c'];
const defined = maybeStrings.filter(isDefined);
console.log(defined.join(','));
console.log(`length ${defined.length}`);The value is T predicate is the magic clause: when isDefined(value) returns true, TypeScript narrows the variable's type from T | undefined to T in every downstream branch and in Array.prototype.filter. Without the predicate, filter keeps the input element type, so the output is still (string | undefined)[]. The function body is just a runtime check (value !== undefined); the predicate type is what tells the compiler the runtime check is sound. This is the smallest reusable type guard you should ship in every TypeScript codebase.
function isNonNullable<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
const rows = ['a', null, 'b', undefined, 'c'];
const kept = rows.filter(isNonNullable);
console.log(kept.join(','));Many APIs return T | null | undefined (database rows that can be missing, GraphQL fields that are nullable, optional config). isNonNullable narrows in one shot, so callers do not have to chain two filters or write value != null (which works at runtime but is harder to read). The predicate value is T strips both null and undefined because TypeScript knows the runtime check rules out both. Pair this with the built-in NonNullable<T> type when you need a type-only operation; the runtime guard is for value-level narrowing in arrays and conditionals.
function isNonNullable2<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
function compact<T>(items: Array<T | null | undefined>): T[] {
return items.filter(isNonNullable2);
}
const data = [1, null, 2, undefined, 3];
const clean = compact(data);
console.log(`compacted ${clean.join(',')}`);
console.log(`sum ${clean.reduce((a, b) => a + b, 0)}`);Wrapping the guard in a compact helper documents intent at the call site and reads cleaner than items.filter(isNonNullable) in code review. The generic <T> flows through naturally: the input is Array<T | null | undefined> and the output is T[], so callers get the right element type without any cast. Use this anywhere you map a possibly-empty list (database join results, optional API fields, parsed CSV rows) and want to drop the holes before further processing. For more advanced patterns (drop falsy values, drop empty strings), keep the same shape and swap the predicate.
