TypeScript Filter Array of Objects by Property (filter())

Filter an array of objects by property in TypeScript with filter(): single and multiple conditions, lists of values, nested and optional fields, a typed filterBy() helper and type narrowing.

angular-typescript

To filter an array of objects by a property value in TypeScript, we call filter() with a callback that compares the property, for example people.filter((p) => p.city === “Delhi”). The method calls the callback once for every element and returns a new array with the elements for which the callback returned true. The original array is not changed, and when nothing matches, the result is an empty array.

We filter arrays whenever a page shows only part of the data, such as the products in one category or the orders of one customer.

The following example shows the most common filters on an array of objects, and each comment lists the names that remain after the filter.

interface Person { name: string; age: number; city: string; }

const people: Person[] = [
  { name: "Lokesh", age: 37, city: "Delhi" },
  { name: "Raj", age: 35, city: "Pune" },
  { name: "John", age: 40, city: "Delhi" },
  { name: "Amit", age: 17, city: "Pune" },
];

// 1. By one property value
const fromDelhi = people.filter((p) => p.city === "Delhi");   // Lokesh, John

// 2. By a range
const adults = people.filter((p) => p.age >= 18);   // Lokesh, Raj, John

// 3. Two conditions
const olderInDelhi = people.filter((p) => p.city === "Delhi" && p.age > 38);   // John

// 4. Property in a list of values
const wanted = ["Raj", "Amit"];
const picked = people.filter((p) => wanted.includes(p.name));   // Raj, Amit

// 5. Text search, ignoring case
const withO = people.filter((p) => p.name.toLowerCase().includes("o"));   // Lokesh, John

// 6. Array of numbers
const evens = [1, 2, 3, 4].filter((n) => n % 2 === 0);   // evens = [2, 4]

// 7. Remove null and undefined
const fruits = ["apple", null, "banana", undefined].filter((f) => f != null);   // ["apple", "banana"], string[]

Notice that every filter returns a new array, so people still holds all four persons after these lines.

The filter() method is the standard JavaScript Array.prototype.filter(). TypeScript adds type checking of the callback and, since TypeScript 5.5, type narrowing, so filtering out null values from a (string | null)[] gives a string[].

Next, we filter by one or more properties, by a list of allowed values, by nested and optional properties, and by text. We also build a reusable typed helper and look at how filter() narrows types, followed by the mistakes that compile without errors but return the wrong result.

1. How filter() Works

The callback is a predicate, which is a function that answers yes or no for one element. The method filter() calls it with three arguments, namely the element, its index and the whole array, and most callbacks need only the element. The return value is treated as a condition, so any truthy value keeps the element and any falsy value drops it.

const nums = [10, 15, 20, 25];

// 1. Keep elements for which the callback returns true
const big = nums.filter((n) => n > 12);       // big = [15, 20, 25]

// 2. The callback also receives the index
const evenPositions = nums.filter((n, i) => i % 2 === 0);   // evenPositions = [10, 20]

// 3. No match: an empty array, never undefined
const none = nums.filter((n) => n > 100);     // none = []

// nums is unchanged: [10, 15, 20, 25]

The type declarations in lib.es5.d.ts contain two versions (overloads) of filter().

  • The first accepts a type predicate, a function whose return type is written as value is S, and returns S[].
  • The second accepts any callback and returns the same element type T[].

We come back to the first overload in section 5, where it narrows types.

When the same condition is used in several places, we give it a name and pass the function itself.

const nums = [1, 2, 3, 4];

const isEven = (n: number): boolean => n % 2 === 0;
const evens = nums.filter(isEven);            // evens = [2, 4]

The new array holds the same object references as the original, not copies, so changing a property on an object in the filtered array also changes it in the source array.

2. Filtering Objects by Property Value

Filtering by a property is a comparison inside the callback. We compare with === and combine conditions with && (both must be true) or || (one is enough). When the callback reads only one or two fields, we can destructure the property in the parameter list. For example, a store locator page shows only the shops in the city that the user selected. We use the people array from the first snippet in all the remaining examples.

// 1. Equal to a value
const inPune = people.filter((p) => p.city === "Pune");   // Raj, Amit

// 2. AND: both conditions
const adultsInPune = people.filter((p) => p.city === "Pune" && p.age >= 18);   // Raj

// 3. OR: either condition
const youngOrOld = people.filter((p) => p.age < 18 || p.age >= 40);   // John, Amit

// 4. Destructuring in the parameter
const thirties = people.filter(({ age }) => age >= 30 && age < 40);   // Lokesh, Raj

Because people is typed as Person[], the compiler knows the type of p inside the callback. A typo such as p.ctiy is a compile error, and comparing p.age with the string “35” is reported as a comparison that can never be true.

2.1. A Reusable, Type-Safe filterBy() Helper

When the property name comes from a variable, for example a column chosen in a UI, we can write one generic helper. K extends keyof T limits the key to the property names of the element type, and T[K] is the type of that property, so the value must match it.

function filterBy<T, K extends keyof T>(items: T[], key: K, value: T[K]): T[] {
  return items.filter((item) => item[key] === value);
}

const fromDelhi = filterBy(people, "city", "Delhi");   // Lokesh, John
const aged35 = filterBy(people, "age", 35);   // Raj

A wrong property name or a value of the wrong type no longer compiles.

const wrongKey = filterBy(people, "town", "Pune");
const wrongValue = filterBy(people, "age", "35");
src/err.ts(1,35): error TS2345: Argument of type '"town"' is not assignable to parameter of type 'keyof Person'.
src/err.ts(2,44): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

The same pattern works for any generic function that takes a property name, such as a sort helper or a group helper.

3. Lists of Values, Nested and Optional Properties

A frequent request is “keep the objects whose property is one of these values”. For a short list, includes() reads well, whereas for a long list a Set is faster, because has() finds the value without scanning the list for every element.

// 1. Small list: includes()
const cities = ["Pune", "Mumbai"];
const fromCities = people.filter((p) => cities.includes(p.city));   // Raj, Amit

// 2. Large list: a Set
const blocked = new Set(["Raj", "John"]);
const allowed = people.filter((p) => !blocked.has(p.name));   // Lokesh, Amit

Real data is rarely flat, so the condition often needs to look inside a nested object or an array property. For an optional property, the optional chaining operator ?. returns undefined when the property is missing, and undefined counts as false, so those objects are dropped.

interface User {
  name: string;
  address: { city: string };
  tags: string[];
  email?: string;
}

const users: User[] = [
  { name: "Lokesh", address: { city: "Delhi" }, tags: ["admin"], email: "lokesh@example.com" },
  { name: "Raj", address: { city: "Pune" }, tags: ["dev", "admin"] },
  { name: "John", address: { city: "Delhi" }, tags: ["dev"] },
];

// 1. Nested property
const delhi = users.filter((u) => u.address.city === "Delhi");   // Lokesh, John

// 2. Array property contains a value
const admins = users.filter((u) => u.tags.includes("admin"));   // Lokesh, Raj

// 3. Optional property
const withEmail = users.filter((u) => u.email?.endsWith("@example.com"));   // Lokesh

4. Filtering Strings

Text filters often need to ignore case, so we convert both sides with toLowerCase() before comparing, and then we match with a method such as startsWith() or includes(). For example, a search box above a contact list filters the names as the user types. Negating the condition with ! keeps everything that does not match.

const names = ["Lokesh", "raj", "testUser", "John", "TestAdmin"];

// 1. Starts with, ignoring case
const tests = names.filter((s) => s.toLowerCase().startsWith("test"));   // ["testUser", "TestAdmin"]

// 2. Everything else
const real = names.filter((s) => !s.toLowerCase().startsWith("test"));   // ["Lokesh", "raj", "John"]

// 3. Search text anywhere
const query = "OH";
const found = names.filter((s) => s.toLowerCase().includes(query.toLowerCase()));   // ["John"]

5. Narrowing Types With filter()

Filtering often removes values of one type, such as null, from a mixed array. Before TypeScript 5.5, the result kept the original wide type, so we had to write a type guard by hand. Since TypeScript 5.5, the compiler infers a type predicate from simple callbacks such as (n) => n != null, and filter() returns the narrower type.

// 1. Inferred type predicate (TypeScript 5.5+)
const raw = [1, null, 2, undefined, 0];
const nums = raw.filter((n) => n != null);    // nums = [1, 2, 0], typed number[]

// 2. filter(Boolean) drops 0 and keeps the wider type
const truthy = raw.filter(Boolean);           // truthy = [1, 2], typed (number | null | undefined)[]

// 3. Discriminated union
type Shape = { kind: "circle"; radius: number } | { kind: "square"; side: number };
const shapes: Shape[] = [{ kind: "circle", radius: 1 }, { kind: "square", side: 2 }];
const circles = shapes.filter((s) => s.kind === "circle");   // { kind: "circle"; radius: number }[]
const radius = circles[0].radius;             // radius = 1

The != null check (with two equal signs) is true for everything except null and undefined, so it removes both and keeps 0. The popular shortcut filter(Boolean) removes every falsy value, including 0 and the empty string, and TypeScript does not narrow its result. In step 3, the compiler knows that only circles remain, so reading radius compiles without a cast.

When the inference does not apply, for example for more complex callbacks, we write the type predicate ourselves. The return type value is T defines what a true result means for the type of the element.

const isDefined = <T>(value: T | null | undefined): value is T => value != null;

const names = ["Lokesh", undefined, "Raj", null];
const valid = names.filter(isDefined);        // valid = ["Lokesh", "Raj"], typed string[]

The compiler does not check that the body of a hand-written type guard matches its claim. If the body returned true for null, the result would still be typed string[] and the error would appear later at runtime. Type guards also narrow union types outside of filter(), for example in an if statement.

6. Mistakes That Compile but Filter Wrongly

The callback type in the second overload returns unknown, which means TypeScript accepts any return value. Accepting any return value lets two common bugs through without a compile error.

const nums = [1, 2, 3];

// 1. Braces without return: the callback returns undefined
const none = nums.filter((n) => { n > 1; });  // none = []
const fixed = nums.filter((n) => { return n > 1; });   // fixed = [2, 3]

// 2. async callback: a Promise is always truthy
const all = nums.filter(async (n) => n > 5);  // all = [1, 2, 3]

Three mistakes with filter() compile without errors and return the wrong result.

  • An arrow function with braces needs an explicit return; without it, every call returns undefined and the result is always empty.
  • An async callback returns a Promise, and filter() does not wait for it; a Promise object is truthy, so every element is kept.
  • Expecting filter() to change the array is also a mistake, because it returns a new one, so its result must be assigned to a variable.

For an asynchronous condition, for example a stock check that calls an inventory API for each product, we first compute the results with await Promise.all(nums.map(check)) and then filter by index.

7. filter() vs find(), some() and map()

The method filter() always walks the whole array and returns every match. When we need only the first match or only a yes/no answer, other methods stop as soon as they find one and say more clearly what the code is doing.

We needMethodReturns
All matching elementsfilter()A new array, possibly empty
The first matching elementfind()The element or undefined
The position of the first matchfindIndex()The index or -1
Whether any element matchessome()true or false
Whether all elements matchevery()true or false

A common shortcut, people.filter(…)[0], works but checks every element and hides the intent, so find() is the better choice. The method filter() is also often chained with map(), for example people.filter((p) => p.age >= 18).map((p) => p.name), which reads well and is fast enough for most arrays. The method flatMap() can filter and map in one pass. To remove elements from an existing array in place, we remove the items with splice() instead.

8. Running the filter() Examples

All snippets are part of the array-filter project in the TypeScript-Examples repository. The people array is in src/people.ts, each section has its own file, and src/index.ts prints the names that each filter keeps. TypeScript 7.0.2 is pinned in package.json, the snippets compile in strict mode, and Node.js 22 or newer is required.

npm install
npm start

9. Conclusion

To filter an array of objects by property in TypeScript, we pass filter() a callback that compares the property and returns true for the objects to keep. Conditions combine with && and ||, and lists of allowed values work with includes() or a Set. A generic helper with keyof makes the property name itself type-safe. Since TypeScript 5.5, simple null checks narrow the result type, whereas filter(Boolean) does not. The two bugs to watch for are a callback with braces but no return, and an async callback.

10. References

The MDN page describes the runtime behavior, and the TypeScript documentation covers type predicates and their inference.

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.