JavaScript Array Filter Method: Complete Tutorial
Master the JavaScript filter() method for selecting array elements by condition. Covers syntax, callback mechanics, real-world filtering patterns, chaining with map and reduce, performance considerations, and common mistakes.
The filter() method creates a new array containing only the elements that pass a test you define. It is the standard way to select, search, and narrow down data in JavaScript without mutating the original array. If map() transforms every element, filter() decides which elements survive.
What filter() Does
filter() calls a test function on every element. If the function returns true (or any truthy value), the element is included in the result. If it returns false (or falsy), the element is excluded:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evens = numbers.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]
// Original unchanged
console.log(numbers); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]Syntax
const newArray = array.filter(callback(element, index, array), thisArg)| Parameter | Description |
|---|---|
element | The current element being tested |
index | Index of the current element (optional) |
array | The original array (optional) |
thisArg | Value to use as this inside the callback (optional) |
Return Value
A new array containing only the elements for which the callback returned a truthy value. If no elements pass, returns an empty array []. The original array is never modified.
Basic Examples
Filtering Strings by Length
const words = ["a", "hello", "hi", "extraordinary", "yes", "no"];
const longWords = words.filter(word => word.length > 3);
console.log(longWords); // ["hello", "extraordinary"]Filtering Objects by Property
const products = [
{ name: "Laptop", price: 999, inStock: true },
{ name: "Mouse", price: 29, inStock: false },
{ name: "Keyboard", price: 79, inStock: true },
{ name: "Monitor", price: 349, inStock: true },
{ name: "Webcam", price: 59, inStock: false },
];
const available = products.filter(p => p.inStock);
console.log(available.length); // 3
const affordable = products.filter(p => p.price < 100);
console.log(affordable.map(p => p.name)); // ["Mouse", "Keyboard", "Webcam"]Filtering with Multiple Conditions
const employees = [
{ name: "Alice", department: "Engineering", salary: 95000 },
{ name: "Bob", department: "Marketing", salary: 72000 },
{ name: "Carol", department: "Engineering", salary: 110000 },
{ name: "Dave", department: "Engineering", salary: 88000 },
{ name: "Eve", department: "Marketing", salary: 85000 },
];
const seniorEngineers = employees.filter(
emp => emp.department === "Engineering" && emp.salary >= 90000
);
console.log(seniorEngineers.map(e => e.name)); // ["Alice", "Carol"]The Index Parameter
The second callback argument gives you the element's position, useful for position-based filtering:
const items = ["a", "b", "c", "d", "e", "f"];
// Keep only even-indexed elements
const evenIndex = items.filter((_, index) => index % 2 === 0);
console.log(evenIndex); // ["a", "c", "e"]
// Keep every third element
const everyThird = items.filter((_, index) => index % 3 === 0);
console.log(everyThird); // ["a", "d"]Real-World Patterns
Search Implementation
Filtering a dataset by a search term:
function searchProducts(products, query) {
const lowerQuery = query.toLowerCase().trim();
if (!lowerQuery) return products;
return products.filter(product =>
product.name.toLowerCase().includes(lowerQuery) ||
product.category.toLowerCase().includes(lowerQuery) ||
product.tags.some(tag => tag.toLowerCase().includes(lowerQuery))
);
}
const catalog = [
{ name: "MacBook Pro", category: "Laptops", tags: ["apple", "pro"] },
{ name: "ThinkPad X1", category: "Laptops", tags: ["lenovo", "business"] },
{ name: "Magic Mouse", category: "Accessories", tags: ["apple", "wireless"] },
{ name: "MX Master", category: "Accessories", tags: ["logitech", "ergonomic"] },
];
console.log(searchProducts(catalog, "apple").map(p => p.name));
// ["MacBook Pro", "Magic Mouse"]
console.log(searchProducts(catalog, "laptop").map(p => p.name));
// ["MacBook Pro", "ThinkPad X1"]Removing Duplicates
Filter combined with indexOf to keep only the first occurrence:
const withDupes = ["JavaScript", "Python", "JavaScript", "Rust", "Python", "Go"];
const unique = withDupes.filter((item, index) => withDupes.indexOf(item) === index);
console.log(unique); // ["JavaScript", "Python", "Rust", "Go"]For complex objects, use a Set with a key:
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 1, name: "Alice" }, // Duplicate
{ id: 3, name: "Carol" },
];
const seen = new Set();
const uniqueUsers = users.filter(user => {
if (seen.has(user.id)) return false;
seen.add(user.id);
return true;
});
console.log(uniqueUsers.length); // 3Removing Falsy Values
const messy = [0, "hello", "", null, undefined, 42, false, "world", NaN];
const clean = messy.filter(Boolean);
console.log(clean); // ["hello", 42, "world"]The Boolean Trick
Passing Boolean as the callback is shorthand for item => Boolean(item). It removes all falsy values: 0, "", null, undefined, NaN, and false. Be careful: this also removes 0 and "", which might be valid in your data.
Form Validation Errors
function validateForm(fields) {
const errors = [
!fields.name && "Name is required",
!fields.email && "Email is required",
fields.email && !fields.email.includes("@") && "Email must be valid",
!fields.password && "Password is required",
fields.password && fields.password.length < 8 && "Password must be at least 8 characters",
].filter(Boolean); // Remove false entries
return errors;
}
const result = validateForm({ name: "Alice", email: "bad", password: "123" });
console.log(result);
// ["Email must be valid", "Password must be at least 8 characters"]Date Range Filtering
const events = [
{ name: "Conference", date: new Date("2026-03-15") },
{ name: "Workshop", date: new Date("2026-04-20") },
{ name: "Meetup", date: new Date("2026-02-10") },
{ name: "Hackathon", date: new Date("2026-05-01") },
{ name: "Webinar", date: new Date("2026-03-28") },
];
const march = events.filter(event => {
const month = event.date.getMonth(); // 0-indexed: March = 2
return month === 2;
});
console.log(march.map(e => e.name)); // ["Conference", "Webinar"]
// Events after today
const upcoming = events.filter(event => event.date > new Date());Chaining filter() with Other Methods
filter() + map(): Select and Transform
The most common chain in JavaScript:
const orders = [
{ id: 1, status: "completed", total: 150 },
{ id: 2, status: "pending", total: 80 },
{ id: 3, status: "completed", total: 220 },
{ id: 4, status: "cancelled", total: 50 },
{ id: 5, status: "completed", total: 175 },
];
const completedTotals = orders
.filter(order => order.status === "completed")
.map(order => `Order #${order.id}: $${order.total}`);
console.log(completedTotals);
// ["Order #1: $150", "Order #3: $220", "Order #5: $175"]filter() + reduce(): Select and Aggregate
const expenses = [
{ category: "food", amount: 45 },
{ category: "transport", amount: 30 },
{ category: "food", amount: 62 },
{ category: "entertainment", amount: 100 },
{ category: "food", amount: 28 },
];
const totalFood = expenses
.filter(e => e.category === "food")
.reduce((sum, e) => sum + e.amount, 0);
console.log(totalFood); // 135filter() + sort(): Select and Order
const students = [
{ name: "Alice", gpa: 3.9 },
{ name: "Bob", gpa: 2.8 },
{ name: "Carol", gpa: 3.5 },
{ name: "Dave", gpa: 3.7 },
{ name: "Eve", gpa: 2.5 },
];
const deansList = students
.filter(s => s.gpa >= 3.5)
.sort((a, b) => b.gpa - a.gpa)
.map(s => `${s.name} (${s.gpa})`);
console.log(deansList); // ["Alice (3.9)", "Dave (3.7)", "Carol (3.5)"]filter() vs Other Selection Methods
| Method | Returns | Best for |
|---|---|---|
filter(fn) | All matching elements (as new array) | Getting all matches |
find(fn) | First matching element | Getting one match |
findIndex(fn) | Index of first match | Finding position |
some(fn) | true if any match | Existence check |
every(fn) | true if all match | Validation |
includes(value) | true if value exists | Simple value check |
const scores = [85, 92, 67, 78, 95, 88, 72];
// All passing scores
const passing = scores.filter(s => s >= 80); // [85, 92, 95, 88]
// First passing score
const first = scores.find(s => s >= 80); // 85
// Does any score pass?
const hasPass = scores.some(s => s >= 80); // true
// Do all scores pass?
const allPass = scores.every(s => s >= 80); // falsefilter() vs splice() for Removal
When removing elements from an array, you have two approaches:
const items = ["keep", "remove", "keep", "remove", "keep"];
// filter: creates new array (immutable)
const filtered = items.filter(item => item !== "remove");
console.log(filtered); // ["keep", "keep", "keep"]
console.log(items); // ["keep", "remove", "keep", "remove", "keep"] — original safe
// splice: modifies in place (mutation)
const items2 = ["keep", "remove", "keep", "remove", "keep"];
for (let i = items2.length - 1; i >= 0; i--) {
if (items2[i] === "remove") {
items2.splice(i, 1);
}
}
console.log(items2); // ["keep", "keep", "keep"] — original changed| Aspect | filter() | splice() |
|---|---|---|
| Creates new array | Yes | No (modifies original) |
| Removes by condition | Natural (callback) | Requires manual index tracking |
| Safe for React state | Yes | No |
| Performance (large arrays) | O(n) always | O(n) per splice, O(n^2) in loops |
| Readability | High (declarative) | Lower (imperative, backward loop) |
Performance Considerations
filter() iterates the entire array and creates a new one. For most applications this is fast:
// Filtering 100K items: ~2-5ms
const large = Array.from({ length: 100_000 }, (_, i) => ({
id: i,
active: Math.random() > 0.5,
}));
console.time("filter-100k");
const active = large.filter(item => item.active);
console.timeEnd("filter-100k"); // ~2-5msFor chained operations on very large arrays, combine into a single pass:
// Two passes (filter + map)
const result1 = data.filter(d => d.active).map(d => d.value);
// Single pass (reduce)
const result2 = data.reduce((acc, d) => {
if (d.active) acc.push(d.value);
return acc;
}, []);Optimize Only When Needed
Chain filter().map() freely in most code. The two-pass approach is clearer and easier to maintain. Only combine into reduce() if profiling shows the chain is a bottleneck (rare for arrays under 100K elements).
Common Mistakes
Forgetting filter returns a new array (not modifying original):
const items = [1, 2, 3, 4, 5];
// Bug: developer expects items to be modified
items.filter(n => n > 3);
console.log(items); // [1, 2, 3, 4, 5] — still the same!
// Fix: assign the result
const big = items.filter(n => n > 3);
console.log(big); // [4, 5]Using filter when you only need one result:
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Carol" },
];
// Wasteful: filters the entire array to find one item
const found = users.filter(u => u.id === 2)[0];
// Better: stops at first match
const found2 = users.find(u => u.id === 2);Mutating objects inside filter:
const items = [
{ name: "A", processed: false },
{ name: "B", processed: false },
];
// Anti-pattern: using filter for side effects
const unprocessed = items.filter(item => {
item.processed = true; // Mutation inside filter!
return !item.processed; // Always false now
});
console.log(unprocessed); // [] — everything was marked processed before the testComparing objects by reference:
const target = { id: 1 };
const arr = [{ id: 1 }, { id: 2 }, { id: 3 }];
// Bug: objects are compared by reference, not value
const found = arr.filter(item => item === target);
console.log(found); // [] — no match because different object references
// Fix: compare by property
const found2 = arr.filter(item => item.id === target.id);
console.log(found2); // [{ id: 1 }]Rune AI
Key Insights
- Non-mutating selection:
filter()returns a new array with only the elements that pass the test - Truthy/falsy evaluation: The callback does not need to return exactly
true; any truthy value includes the element - filter(Boolean) shortcut: Removes all falsy values (
null,undefined,0,"",false,NaN) in one call - Chain with map():
filter().map()is the standard pattern for selecting then transforming data - Use find() for single items: When you need one result,
find()is more efficient and expressive thanfilter()[0]
Frequently Asked Questions
Does filter() modify the original array?
What does filter(Boolean) do?
How do I filter and transform at the same time?
Can filter() return an empty array?
What is the difference between filter() and find()?
Conclusion
The filter() method is the standard tool for selecting elements from an array by condition. It creates a new array without modifying the original, making it safe for React state, Redux reducers, and any immutable data pattern. Combined with map() for transformation and reduce() for aggregation, filter() forms the core of declarative data processing in JavaScript. The critical points to remember are that it always returns a new array (never modifies the original), the Boolean shortcut removes falsy values, and find() is the better choice when you only need a single result.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.