Episode 1 — Fundamentals / 1.22 — Array Methods
1.22.b — filter()
In one sentence:
filter()tests every element against a condition and returns a new array containing only the elements that passed.
Navigation: ← 1.22.a — map() · 1.22.c — reduce() →
1. What filter() does
filter() iterates through every element, passes it to your callback, and keeps the element if the callback returns a truthy value. Elements whose callback returns falsy are excluded.
const nums = [1, 2, 3, 4, 5, 6];
const evens = nums.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6]
console.log(nums); // [1, 2, 3, 4, 5, 6] ← unchanged
2. Syntax
const newArray = arr.filter(callback(element, index, array));
| Parameter | Description |
|---|---|
element | The current element being tested |
index | (optional) The index of the current element |
array | (optional) The original array filter was called on |
| Return | A new array containing only elements where callback returned truthy |
const words = ["spray", "limit", "elite", "destruction", "present"];
// Keep words longer than 5 characters
const long = words.filter((word, index) => {
console.log(`Testing index ${index}: "${word}" (length ${word.length})`);
return word.length > 5;
});
console.log(long); // ["destruction", "present"]
3. Returns empty array if nothing passes
filter never returns null or undefined. If no element passes the test, you get an empty array. This is safe to chain on.
const nums = [1, 2, 3];
const big = nums.filter(n => n > 100);
console.log(big); // []
console.log(big.length); // 0
console.log(Array.isArray(big)); // true
// Safe to chain — no "cannot read property of null" errors
const result = nums.filter(n => n > 100).map(n => n * 2);
console.log(result); // []
4. Does NOT mutate the original
Like map, filter is a non-mutating method:
const scores = [85, 42, 93, 67, 55, 78];
const passing = scores.filter(s => s >= 60);
console.log(scores); // [85, 42, 93, 67, 55, 78] ← untouched
console.log(passing); // [85, 93, 67, 78] ← new array
5. Truthy vs falsy in filter callbacks
The callback does not have to return strictly true or false. Any truthy value keeps the element; any falsy value removes it.
| Truthy (keeps element) | Falsy (removes element) |
|---|---|
true, 1, "hello", {}, [] | false, 0, "", null, undefined, NaN |
const mixed = [0, 1, "", "hello", null, undefined, false, 42, NaN];
// Keep only truthy values
const truthy = mixed.filter(val => val);
console.log(truthy); // [1, "hello", 42]
6. Filtering objects by property
This is the most common real-world use:
const students = [
{ name: "Alice", grade: 92, active: true },
{ name: "Bob", grade: 45, active: true },
{ name: "Carol", grade: 78, active: false },
{ name: "Dave", grade: 88, active: true },
{ name: "Eve", grade: 34, active: true },
];
// Students who passed (grade >= 60) AND are active
const passingActive = students.filter(s => s.grade >= 60 && s.active);
console.log(passingActive);
// [
// { name: "Alice", grade: 92, active: true },
// { name: "Dave", grade: 88, active: true },
// ]
Filtering by multiple conditions
const products = [
{ name: "Laptop", price: 999, category: "electronics", inStock: true },
{ name: "Shirt", price: 29, category: "clothing", inStock: true },
{ name: "Phone", price: 699, category: "electronics", inStock: false },
{ name: "Book", price: 15, category: "books", inStock: true },
{ name: "Headphones", price: 149, category: "electronics", inStock: true },
];
// Affordable electronics that are in stock
const affordable = products.filter(p =>
p.category === "electronics" &&
p.price < 500 &&
p.inStock
);
console.log(affordable);
// [{ name: "Headphones", price: 149, category: "electronics", inStock: true }]
7. Chaining: filter(...).map(...)
The most common chain pattern is filter first (reduce the set), then map (transform the survivors):
const employees = [
{ name: "Alice", department: "Engineering", salary: 95000 },
{ name: "Bob", department: "Marketing", salary: 65000 },
{ name: "Carol", department: "Engineering", salary: 105000 },
{ name: "Dave", department: "Engineering", salary: 88000 },
{ name: "Eve", department: "Marketing", salary: 72000 },
];
// Get formatted names of engineers earning >= 90k
const seniorEngineers = employees
.filter(e => e.department === "Engineering" && e.salary >= 90000)
.map(e => `${e.name} ($${e.salary.toLocaleString()})`);
console.log(seniorEngineers);
// ["Alice ($95,000)", "Carol ($105,000)"]
Why filter before map? Fewer elements to transform = better performance and clearer intent.
8. Removing falsy values: filter(Boolean)
A very common JavaScript pattern:
const messy = ["Alice", "", null, "Bob", undefined, "Carol", 0, false, "Dave"];
const clean = messy.filter(Boolean);
console.log(clean); // ["Alice", "Bob", "Carol", "Dave"]
How it works: Boolean is a constructor function. When called as Boolean(value), it returns true for truthy values, false for falsy. filter uses this as its callback.
// These are equivalent:
messy.filter(Boolean);
messy.filter(val => Boolean(val));
messy.filter(val => !!val);
Practical use: cleaning up split results
const csv = "Alice,,Bob,,Carol";
const names = csv.split(",").filter(Boolean);
console.log(names); // ["Alice", "Bob", "Carol"]
Cleaning up optional fields
const parts = [user.firstName, user.middleName, user.lastName].filter(Boolean);
const fullName = parts.join(" ");
// If middleName is undefined: "Alice Smith"
// If middleName is "Marie": "Alice Marie Smith"
9. Using the index parameter
// Keep every other element (even indices)
const letters = ["a", "b", "c", "d", "e", "f"];
const everyOther = letters.filter((_, index) => index % 2 === 0);
console.log(everyOther); // ["a", "c", "e"]
Remove duplicates (keeping first occurrence)
const nums = [1, 3, 5, 3, 1, 5, 7];
const unique = nums.filter((val, index, arr) => arr.indexOf(val) === index);
console.log(unique); // [1, 3, 5, 7]
(For large arrays, prefer [...new Set(arr)] for performance.)
10. Real-world examples
Search functionality
const contacts = [
{ name: "Alice Johnson", email: "alice@example.com" },
{ name: "Bob Smith", email: "bob@example.com" },
{ name: "Carol Johnson", email: "carol@example.com" },
{ name: "Dave Williams", email: "dave@example.com" },
];
function searchContacts(query) {
const lower = query.toLowerCase();
return contacts.filter(c =>
c.name.toLowerCase().includes(lower) ||
c.email.toLowerCase().includes(lower)
);
}
console.log(searchContacts("johnson"));
// [{ name: "Alice Johnson", ... }, { name: "Carol Johnson", ... }]
console.log(searchContacts("bob"));
// [{ name: "Bob Smith", ... }]
Remove completed todos
const todos = [
{ id: 1, text: "Learn map", done: true },
{ id: 2, text: "Learn filter", done: false },
{ id: 3, text: "Learn reduce", done: true },
{ id: 4, text: "Practice chaining", done: false },
];
const pending = todos.filter(todo => !todo.done);
console.log(pending);
// [
// { id: 2, text: "Learn filter", done: false },
// { id: 4, text: "Practice chaining", done: false },
// ]
Filter students by grade bracket
const students = [
{ name: "Alice", marks: 92 },
{ name: "Bob", marks: 45 },
{ name: "Carol", marks: 78 },
{ name: "Dave", marks: 88 },
{ name: "Eve", marks: 55 },
{ name: "Frank", marks: 96 },
];
function getByGrade(students, grade) {
const brackets = {
A: s => s.marks >= 90,
B: s => s.marks >= 80 && s.marks < 90,
C: s => s.marks >= 70 && s.marks < 80,
D: s => s.marks >= 60 && s.marks < 70,
F: s => s.marks < 60,
};
return students.filter(brackets[grade] || (() => false));
}
console.log(getByGrade(students, "A"));
// [{ name: "Alice", marks: 92 }, { name: "Frank", marks: 96 }]
console.log(getByGrade(students, "F"));
// [{ name: "Bob", marks: 45 }, { name: "Eve", marks: 55 }]
Age-based access control
const users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 17 },
{ name: "Carol", age: 30 },
{ name: "Dave", age: 15 },
];
const adults = users.filter(u => u.age >= 18);
const minors = users.filter(u => u.age < 18);
11. filter vs manual loop
// Imperative approach
const result = [];
for (const num of nums) {
if (num > 10) {
result.push(num);
}
}
// Declarative approach — same result, less boilerplate
const result2 = nums.filter(n => n > 10);
The filter version is shorter, more readable, and less error-prone (no manual array management).
12. Edge cases
// Empty array — returns empty array
[].filter(n => n > 0); // []
// All pass — returns shallow copy
[1, 2, 3].filter(() => true); // [1, 2, 3] (new array, same elements)
// None pass — returns empty array
[1, 2, 3].filter(() => false); // []
// Sparse arrays — filter skips holes
const sparse = [1, , 3, , 5];
sparse.filter(n => true); // [1, 3, 5] — holes removed!
Key takeaways
filter()returns a new array containing only elements whose callback returned truthy.- It never mutates the original and never returns null — worst case is
[]. filter(Boolean)is the idiomatic way to remove falsy values from an array.- Chain filter before map — reduce the data set, then transform.
- The callback can test multiple conditions using
&&,||, etc. - For unique values,
filterwithindexOfworks butnew Set()is faster.
Explain-It Challenge
Explain without notes:
- What does
filterreturn if no elements pass the test? - Why is
arr.filter(Boolean)useful — what does it remove? - You need a list of engineer names from an array of employee objects. Would you use
filter,map, or both? In what order?
Navigation: ← 1.22.a — map() · 1.22.c — reduce() →