JAVASCRIPT / ARRAYS
filter for keeping what matches
Use filter with a predicate to build a new array of only the elements that pass, and predict its length, contents, and cost.
What you will learn
- Write a predicate that returns truthy to keep an element and falsy to drop it
- Expect a fresh array each call, length 0 to n, with the source array unchanged
- Use the index argument to keep or drop elements by position
- Pick find for the first match, filter for every match, and reassign to shrink a list
Understanding filter for keeping what matches
filter walks the array from index 0 upward, calls your function with (element, index, array), and appends the element to a brand new array whenever that call returns a truthy value. Order is preserved, and the kept elements are the very same values as in the source, not copies of them. The result length can be anything from 0 to the source length, which is the structural difference from map: map always hands back an array of the same length, filter hands back a subset.
filter never looks at the element itself to decide anything; the only thing it inspects is what your callback returns, coerced to a boolean the same way an if condition is. That means n > 0 and n are both legal predicates but mean different things, and it means a callback that falls off the end without returning rejects everything, because undefined is falsy. It also means any single-argument function that answers a yes/no question can be passed directly, which is why words.filter(Boolean) drops empty strings, null, 0, and NaN in one step.
Because filter builds a new array, the original is untouched: calling items.filter(...) on its own line accomplishes nothing observable. To shrink a list you must capture the result. filter also has no early exit, unlike find or some, so the predicate runs exactly once per element even after the last match is found; that is the price of getting all matches instead of the first one, and it matters when the predicate is expensive.
const readings = [12, -3, 0, 47, -8, 31];
const positive = readings.filter((n) => n > 0);
console.log(positive);
console.log(readings.length, positive.length);
const evenIndexes = readings.filter((n, i) => i % 2 === 0);
console.log(evenIndexes);
const huge = readings.filter((n) => n > 1000);
console.log(huge, huge.length);filter asks a yes/no question of every element and returns a new array of the ones that answered yes, leaving the original array alone.
Worked examples
The missing return
Shows how a braced arrow body silently rejects every element, and how truthiness lets Boolean act as a predicate.
const words = ["ok", "", "fine", "", "good"];
const broken = words.filter((w) => { w.length > 0; });
console.log(broken.length);
const fixed = words.filter((w) => { return w.length > 0; });
console.log(fixed);
console.log(words.filter(Boolean));Example explained
Line 1The braces make the arrow a block body, so w.length > 0 is evaluated and thrown away and the function returns undefined.
Line 2undefined is falsy for all five elements, so filter keeps none of them and broken.length is 0.
Line 3Adding return turns the same comparison into the predicate's result, keeping the three non-empty strings.
Line 4Boolean works as a predicate because filter only needs truthiness; it ignores the extra index and array arguments filter passes.
Filtering objects keeps references
Demonstrates that the new array holds the same objects, while the source array's contents and length stay as they were.
const tasks = [
{ id: 1, title: "wash", done: true },
{ id: 2, title: "pay rent", done: false },
{ id: 3, title: "call bank", done: false },
];
const open = tasks.filter((t) => !t.done);
console.log(open.length);
open[0].done = true;
console.log(tasks[1].done);
console.log(tasks.length);Example explained
Line 1!t.done is truthy for the two unfinished tasks, so open is a two-element array.
Line 2open[0] and tasks[1] are the same object, so assigning done through open is visible through tasks.
Line 3tasks.length is still 3: filter copied references into a new array and removed nothing from the source.
Why filter costs more than find
Counts predicate calls to show that filter scans the whole array while find stops at the first match.
const nums = [5, 8, 12, 8, 20];
let visits = 0;
const firstBig = nums.find((n) => { visits++; return n > 10; });
console.log(firstBig, visits);
visits = 0;
const allBig = nums.filter((n) => { visits++; return n > 10; });
console.log(allBig, visits);Example explained
Line 1find stops as soon as the predicate is true, so it calls it 3 times and returns the element 12 itself.
Line 2filter cannot stop early, because a later element might also match, so it calls the predicate all 5 times.
Line 3filter returns an array even when exactly one element matches, which is why you cannot read properties off it directly.
Removing an item requires reassignment
Shows that a filter call whose result is not stored has no effect, and that the index argument can drop a position.
let queue = ["a", "b", "c", "d"];
queue.filter((s) => s !== "b");
console.log(queue);
queue = queue.filter((s) => s !== "b");
console.log(queue);
console.log(queue.filter((_, i) => i !== 2));Example explained
Line 1The first call builds a three-element array and immediately discards it, so queue still has four items.
Line 2Reassigning queue to the result is what actually removes "b" from the variable.
Line 3The last predicate ignores the element with _ and tests only the index, dropping the element at position 2.
Important notes
filter tests truthiness, not equality with true, so a predicate like (item) => item.qty silently drops items whose qty is 0 or ""; compare explicitly when the field can be falsy.
filter skips holes in sparse arrays and never creates holes, so [1, , 3].filter(() => true) has length 2, not 3.
Common mistakes
Calling list.filter(...) without using the return value and expecting list to shrink; the filtered array is thrown away and the original keeps every element.
Writing (n) => { n > 0 } with braces and no return, so the predicate returns undefined for every element and the result is always an empty array.
Treating the result as a single item, as in users.filter((u) => u.id === 3).name, which is undefined because filter returns an array; index it with [0] or use find.
Try it yourself
Change, predict, then run
With const nums = [4, 15, 8, 23, 42, 7], log one array holding only the values greater than 10 and a second array holding only the values at odd indexes, then log nums.length to confirm it is still 6.
Open the JavaScript workspaceCheck your understanding
A cart holds objects like { name: "pen", qty: 0 }. A developer writes cart.filter((item) => item.qty) meaning "keep items that have a qty property". What actually happens?
- Items whose qty is 0 are dropped, because filter treats the returned 0 as falsy
- Every item is kept, because qty exists on all of them and filter only checks that the return value is defined
- A TypeError is thrown, because a filter callback must return true or false
- Items whose qty is 0 are kept but moved to the end of the result
Show answer
filter coerces whatever the callback returns, so returning the number 0 rejects the item exactly as returning false would. Option two confuses "the property exists" with "the value is truthy": filter never sees the property, only the value handed back, so a present-but-zero qty fails. Use (item) => "qty" in item to test presence, or (item) => item.qty > 0 to test the amount.