differenceBy
Returns items from array a
that are not present in array b
. Performs deep comparison, or comparison by a specific key if provided.
1/**
2 * Returns items from array 'a' that are not present in array 'b'.
3 * Performs deep comparison, or comparison by a specific key if provided.
4 */
5export function differenceBy<T>(
6 a: T[],
7 b: T[],
8 keyOrFn?: keyof T | ((item: T) => any)
9): T[] {
10 const getKey = typeof keyOrFn === 'function'
11 ? keyOrFn
12 : keyOrFn
13 ? (item: T) => item[keyOrFn]
14 : (item: T) => item;
15
16 const bKeys = new Set(b.map(getKey));
17 return a.filter(item => !bKeys.has(getKey(item)));
18}
Supports flexible comparison
Allows comparison by:
A specific key (
keyof T
)A custom function (
(item: T) => any
)Full value (default case)
Efficient key-based lookup
Internally uses Set for
O(1)
lookups, resulting in an overallO(n)
performance for typical use cases.Generic and reusable
Works with any array of objects or primitives due to TypeScript generics and flexible key extraction.
Non-mutating and pure
Always returns a new array without changing the original inputs.
Readable and extendable
Easy to understand logic and flexible structure allows for enhancements like case-insensitive matching or transformation pipelines.
Tests | Examples
1test('performs deep comparison when no keyOrFn is provided (primitive values)', () => {
2 expect(differenceBy([1, 2, 3], [2])).toEqual([1, 3]);
3});
4
5test('compares objects by a given key', () => {
6 const a = [{ id: 1 }, { id: 2 }, { id: 3 }];
7 const b = [{ id: 2 }];
8 expect(differenceBy(a, b, 'id')).toEqual([{ id: 1 }, { id: 3 }]);
9});
10
11test('compares objects using a mapping function', () => {
12 const a = [{ name: 'Alice' }, { name: 'Bob' }];
13 const b = [{ name: 'Bob' }];
14 expect(differenceBy(a, b, item => item.name)).toEqual([{ name: 'Alice' }]);
15});
16
17test('returns empty array if all elements are excluded', () => {
18 const a = [{ id: 1 }, { id: 2 }];
19 const b = [{ id: 1 }, { id: 2 }];
20 expect(differenceBy(a, b, 'id')).toEqual([]);
21});
22
23test('returns original array if b is empty', () => {
24 const a = [{ id: 1 }, { id: 2 }];
25 const b: any[] = [];
26 expect(differenceBy(a, b, 'id')).toEqual(a);
27});
28
29test('handles duplicate keys in array A', () => {
30 const a = [{ id: 1 }, { id: 2 }, { id: 2 }];
31 const b = [{ id: 2 }];
32 expect(differenceBy(a, b, 'id')).toEqual([{ id: 1 }]);
33});
34
35test('works with undefined keyOrFn and complex objects (compares by reference)', () => {
36 const obj1 = { a: 1 };
37 const obj2 = { a: 1 };
38 expect(differenceBy([obj1], [obj2])).toEqual([obj1]); // obj1 !== obj2 by reference
39});
Common Use Cases
Removing previously matched objects
Useful in UI filtering (e.g., removing already-displayed suggestions, hidden rows).
Data diffing
Determine what records were removed when comparing old and new states (e.g., users, items, transactions).
Selective cleanup
Remove duplicates or irrelevant entries based on an object property or dynamic condition.
Normalization
Identify items in one dataset that are missing in another, based on ID, slug, or any other distinguishing attribute.