To filter an array in TypeScript, call filter() with a function that returns true for the elements you want to keep: temperatures.filter(temp => temp > 80). It returns a new array and never changes the original, so the result of filtering a number[] is a number[]. The one place TypeScript surprises people is removing undefined or null, where the values disappear at runtime but the type does not change unless you use a type predicate. Everything below was compiled and run, including that case.
Every example was compiled and run with TypeScript 5.2.2 and Node.js v22.22.2 in the Windows Command Prompt, with tsc --target es2022 --lib es2023,dom --strict file.ts and then node file.js. The output is the real output. Reference: Array.prototype.filter() on MDN.
Filter arrays in TypeScript
The function you pass is called a predicate: it runs once per element and keeps the ones it answers true for. Nothing is removed from the original array:
const temperatures: number[] = [68, 102, 55, 91, 47];
const hot: number[] = temperatures.filter(temp => temp > 80);
console.log(hot, "original:", temperatures);
const cities: string[] = ["Austin", "Denver", "Seattle", "Atlanta"];
console.log(cities.filter(city => city.startsWith("A")));
console.log(cities.filter(city => city === "Chicago")); // no match: an empty array
Output:
[ 102, 91 ] original: [ 68, 102, 55, 91, 47 ]
[ 'Austin', 'Atlanta' ]
[]
filter() builds a new array; the original is untouched, and no match means [].| What you want | Use | You get back |
|---|---|---|
| Every element that matches | arr.filter(x => ...) | A new array, possibly empty |
| The first match only | arr.find(x => ...) | The element, or undefined |
| Just a yes or no | arr.some(x => ...) | true or false |
| The position of a match | arr.findIndex(x => ...) | The index, or -1 |
| Matches with a narrower type | arr.filter((x): x is T => ...) | T[] |
| Matching and changing in one pass | arr.reduce(...) or flatMap(...) | Whatever you build |
The predicate gets the index and the array too
The second argument is the index and the third is the array being filtered, which is handy for every-other-element rules or for comparing against something you worked out from the whole array. Because filter() creates a new array rather than editing the old one, it also works on a readonly array, unlike sort() or splice():
const readings: number[] = [12, 45, 7, 88, 23, 61];
// the predicate also receives the index and the whole array
console.log(readings.filter((value, index) => index % 2 === 0));
const average = readings.reduce((sum, n) => sum + n, 0) / readings.length;
console.log("average:", average.toFixed(1), readings.filter(value => value > average));
// filter() never changes the array, so a readonly array is fine
const limits: readonly number[] = [10, 20, 30];
const big: number[] = limits.filter(limit => limit >= 20);
console.log(big, limits);
Output:
[ 12, 7, 23 ]
average: 39.3 [ 45, 88, 61 ]
[ 20, 30 ] [ 10, 20, 30 ]
Filter an array of objects
With objects, the predicate tests a property, and TypeScript checks the property names as you type them. Combine conditions with && and ||, and lowercase both sides for a case-insensitive search. There is a deeper guide to filtering an array of objects and one for filtering by multiple properties:
interface Order {
id: string;
customer: string;
total: number;
status: "pending" | "shipped" | "cancelled";
}
const orders: Order[] = [
{ id: "ORD-1001", customer: "Emma Johnson", total: 249.99, status: "shipped" },
{ id: "ORD-1002", customer: "Michael Brown", total: 89.5, status: "pending" },
{ id: "ORD-1003", customer: "Olivia Davis", total: 430, status: "shipped" },
{ id: "ORD-1004", customer: "Noah Wilson", total: 15, status: "cancelled" },
];
console.log(orders.filter(o => o.status === "shipped").map(o => o.id));
console.log(orders.filter(o => o.status !== "cancelled" && o.total < 250).map(o => o.id));
const term = "OLIVIA";
console.log(orders.filter(o => o.customer.toLowerCase().includes(term.toLowerCase())).map(o => o.customer));
Output:
[ 'ORD-1001', 'ORD-1003' ]
[ 'ORD-1001', 'ORD-1002' ]
[ 'Olivia Davis' ]
Filtering out undefined does not change the type
This is the TypeScript-specific part, and the reason plain JavaScript tutorials cannot help you. On TypeScript 5.2, neither a plain comparison nor filter(Boolean) tells the compiler that the empty values are gone:
const rawEmails: (string | undefined)[] = ["emma@example.com", undefined, "noah@example.com"];
const emails: string[] = rawEmails.filter(email => email !== undefined);
const shorter: string[] = rawEmails.filter(Boolean); // does not narrow either
console.log(emails, shorter);
tsc output:
filter_narrowing.ts(3,7): error TS2322: Type '(string | undefined)[]' is not assignable to type 'string[]'.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
filter_narrowing.ts(4,7): error TS2322: Type '(string | undefined)[]' is not assignable to type 'string[]'.
(string | undefined)[].The fix is a type predicate: (email): email is string. Write it once as a reusable isPresent helper and every array in your codebase can use it. TypeScript 5.5 and later infer this from the function body, so on a newer compiler the first version above works on its own. For this one job there is also a focused guide to removing undefined values from an array:
const rawEmails: (string | undefined)[] = ["emma@example.com", undefined, "noah@example.com"];
// a type predicate: "email is string" tells the compiler what comes back
const emails: string[] = rawEmails.filter((email): email is string => email !== undefined);
console.log(emails.map(e => e.toUpperCase()));
// the same idea, reusable for any type
function isPresent<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
const totals: (number | null)[] = [249.99, null, 430];
const clean: number[] = totals.filter(isPresent);
console.log(clean, clean.reduce((sum, n) => sum + n, 0).toFixed(2));
Output:
[ 'EMMA@EXAMPLE.COM', 'NOAH@EXAMPLE.COM' ]
[ 249.99, 430 ] 679.99
string[], so toUpperCase() compiles.Narrow a union type with filter()
The same trick splits a union. Filtering a Vehicle[] with a predicate that returns vehicle is Car gives you a Car[], so the compiler lets you reach for properties that only cars have:
interface Car {
kind: "car";
plate: string;
}
interface Bike {
kind: "bike";
frame: string;
}
type Vehicle = Car | Bike;
const fleet: Vehicle[] = [
{ kind: "car", plate: "TX-4471" },
{ kind: "bike", frame: "alloy" },
{ kind: "car", plate: "CO-8820" },
];
const isCar = (vehicle: Vehicle): vehicle is Car => vehicle.kind === "car";
const cars: Car[] = fleet.filter(isCar);
console.log(cars.map(car => car.plate)); // .plate only exists on Car
Output:
[ 'TX-4471', 'CO-8820' ]
filter() vs find(), some() and findIndex()
Reach for filter() when you want all the matches. If you only need the first one, find() stops as soon as it hits it, and some() answers a yes-or-no question without building an array at all. Checking whether an array contains a value covers that side in more detail:
const ids: string[] = ["ORD-1001", "ORD-1002", "ORD-1003"];
console.log(ids.filter(id => id.endsWith("2"))); // every match, as an array
console.log(ids.find(id => id.endsWith("2"))); // the first match, or undefined
console.log(ids.find(id => id.endsWith("9"))); // no match: undefined
console.log(ids.some(id => id.endsWith("2"))); // is there at least one?
console.log(ids.filter(id => id.endsWith("9")).length === 0, "no matches");
Output:
[ 'ORD-1002' ]
ORD-1002
undefined
true
true no matches
Two things filter() does that surprise people
It skips holes in a sparse array, so the result can be shorter than the length suggests, and filter(Boolean) removes every falsy value, which includes 0, "" and NaN, not just null and undefined. If 0 is a real value in your data, that line quietly loses it. Use an explicit test instead, as the guide to removing empty strings does:
const readings: number[] = [10, 20, 30];
delete readings[1]; // a hole, length stays 3
console.log(readings.length, readings.filter(() => true), readings.filter(() => true).length);
const mixed: (string | number | null)[] = ["a", 0, null, "", 42, NaN];
console.log(mixed.filter(Boolean)); // drops 0, null, "" and NaN as well
console.log(mixed.filter(value => value !== null)); // exactly what you asked for
Output:
3 [ 10, 30 ] 2
[ 'a', 42 ]
[ 'a', 0, '', 42, NaN ]
filter(Boolean) dropped 0, "" and NaN along with null.Does chaining filter() and map() cost anything?
Chaining reads beautifully and walks the data more than once, building an intermediate array each time. Over a million records, against a single reduce() and a plain loop over the array:
const orders: number[] = Array.from({ length: 1_000_000 }, (_, i) => i);
function time(label: string, fn: () => number): void {
fn(); // warm-up run
const start = performance.now();
const total = fn();
console.log(label.padEnd(26), (performance.now() - start).toFixed(1).padStart(7), "ms", "total", total);
}
time("filter().map().reduce()", () =>
orders.filter(n => n % 2 === 0).map(n => n * 2).reduce((sum, n) => sum + n, 0));
time("one reduce()", () =>
orders.reduce((sum, n) => (n % 2 === 0 ? sum + n * 2 : sum), 0));
time("plain for loop", () => {
let sum = 0;
for (let i = 0; i < orders.length; i++) if (orders[i] % 2 === 0) sum += orders[i] * 2;
return sum;
});
For the list sizes most applications handle, the chain is the right choice: it says what you mean in one line. Collapse it into a single pass only when the array is genuinely large and the work shows up in a profile.
More TypeScript array guides to read next:
- Loop through an array in TypeScript
- Sort arrays in TypeScript
- Remove an item from an array
- Add or append elements to an array
- Get unique values from an array of objects
Frequently asked questions
How do I filter an array in TypeScript?
Call filter() with a predicate: numbers.filter(n => n > 10). It returns a new array with the elements the predicate answered true for.
Does filter() change the original array?
No. It always returns a new array, which is why it also works on readonly arrays, unlike sort() or splice().
How do I filter an array of objects in TypeScript?
Test a property: orders.filter(o => o.status === "shipped"). Combine conditions with &&, and the compiler checks every property name.
Why is my filtered array still typed string | undefined?
Because a plain predicate returns boolean. Use a type predicate, (v): v is string => v !== undefined. TypeScript 5.5 and later work it out for you.
What is the difference between filter() and find()?
filter() returns every match as an array. find() returns the first matching element, or undefined, and stops there.
Is filter(Boolean) safe to use?
Only when no valid value is falsy. It also removes 0, "" and NaN, and it does not narrow the type in TypeScript 5.2.
What does filter() return when nothing matches?
An empty array, never undefined, so you can safely call .length or .map() on the result.
Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn