426

I'm wondering if there's a known, built-in/elegant way to find the first element of a JS array matching a given condition. A C# equivalent would be List.Find.

So far I've been using a two-function combo like this:

// Returns the first element of an array that satisfies given predicate
Array.prototype.findFirst = function (predicateCallback) {
    if (typeof predicateCallback !== 'function') {
        return undefined;
    }

    for (var i = 0; i < arr.length; i++) {
        if (i in this && predicateCallback(this[i])) return this[i];
    }

    return undefined;
};

// Check if element is not undefined && not null
isNotNullNorUndefined = function (o) {
    return (typeof (o) !== 'undefined' && o !== null);
};

And then I can use:

var result = someArray.findFirst(isNotNullNorUndefined);

But since there are so many functional-style array methods in ECMAScript, perhaps there's something out there already like this? I imagine lots of people have to implement stuff like this all the time...

Jakub P.
  • 5,416
  • 2
  • 21
  • 21
  • 6
    There's not a built in method, but there are utility libraries that approximate this functionality such as http://documentcloud.github.com/underscore/ – kinakuta May 04 '12 at 23:19
  • Underscore.js looks very nice indeed! And it has find(). Thanks! – Jakub P. May 04 '12 at 23:23
  • 1
    Just so you know, you can reduce this: `return (typeof (o) !== 'undefined' && o !== null);` down to this `return o != null;`. They are exactly equivalent. – cliffs of insanity May 05 '12 at 00:08
  • 1
    Good to know. But you know, I mistrust the coercing operators like != or ==. I wouldn't even be able to easily test it, as I'd need to check somehow that there is no other value that is coerced to null in that fashion... :) So how lucky I am to have a library that allowed me to remove that function altogether... :) – Jakub P. May 05 '12 at 01:37
  • I'd honestly have to say this a fairly elegant solution. The closest thing I can find is Array.prototype.some which tries to find if some element satisfies a given condition you pass to it in the form of a function. Unfortunately, this returns a boolean instead of the index or the element. I would recommend your solution over using a library since libraries tend to be much larger and contain things you won't use and I prefer keeping things light (since you might only use the one function out of the suite it provides). – Graham Robertson Mar 12 '13 at 00:53
  • From my experience, finding the first match within a collection is usually buggy and causes inconsistent behavior (unless the elements are ordered according to some logic, which isn't the majority of cases). What you usually need is finding the only element that matches and for that there is no native JS method (which throws upon multiple matches) – Cesar Sep 09 '20 at 14:41

14 Answers14

510

Since ES6 there is the native find method for arrays; this stops enumerating the array once it finds the first match and returns the value.

const result = someArray.find(isNotNullNorUndefined);

Old answer:

I have to post an answer to stop these filter suggestions :-)

since there are so many functional-style array methods in ECMAScript, perhaps there's something out there already like this?

You can use the some Array method to iterate the array until a condition is met (and then stop). Unfortunately it will only return whether the condition was met once, not by which element (or at what index) it was met. So we have to amend it a little:

function find(arr, test, ctx) {
    var result = null;
    arr.some(function(el, i) {
        return test.call(ctx, el, i, arr) ? ((result = el), true) : false;
    });
    return result;
}
var result = find(someArray, isNotNullNorUndefined);
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 36
    Can't say I entirely understand all the dislike directed at filter(). It may be slower, but in reality; most cases where this is probably used, it is a small list to begin with, and the majority of JavaScript applications are not complicated enough to really worry about efficiency at this level. [].filter(test).pop() or [].filter(test)[0] are simple, native and readable. Of-course I am talking about businessy apps or websites not intensive apps such as games. – Josh Mc Feb 11 '14 at 04:04
  • 14
    Does filter solutions traverse all the array/collections? If so, filtering is very inefficient, because it runs over all the array even if the found value is the first one in the collection. `some()` on the other hand, returns immediately, which is much faster in almost all cases than filtering solutions. – AlikElzin-kilaka Jul 15 '15 at 06:13
  • @AlikElzin-kilaka: Yes, exactly. – Bergi Jul 15 '15 at 15:09
  • @AlikElzin-kilaka I think Josh's point was that it makes no real-world difference for smallish arrays outside of things like intensive game loops – Dominic Sep 24 '15 at 15:42
  • 18
    @JoshMc sure, but it makes sense not to be gratuitously inefficient when posting a solution to a simple problem somewhere like Stack Overflow. Lots of people will copy and paste the code from here into a utility function, and some of them will, at some point, end up using that utility function in a context where performance matters without thinking about the implementation. If you've given them something that has an efficient implementation to begin with, you've either solved a performance problem they'd otherwise not have, or saved them a bunch of dev time diagnosing it. – Mark Amery Sep 25 '15 at 09:29
  • @MarkAmery: Well said. Of course, anyone wanting a utility function should not use my `some` solution, but rather the `findFirst` posted by the OP in his question. – Bergi Sep 25 '15 at 13:07
  • Use ES6 or polyfill version of it https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find?v=control. You might even need to refer `some()`'s polyfill as this could also not be available in the browser – Alwin Kesler Apr 22 '17 at 04:03
136

As of ECMAScript 6, you can use Array.prototype.find for this. This is implemented and working in Firefox (25.0), Chrome (45.0), Edge (12), and Safari (7.1), but not in Internet Explorer or a bunch of other old or uncommon platforms.

For example, x below is 106:

const x = [100,101,102,103,104,105,106,107,108,109].find(function (el) {
    return el > 105;
});
console.log(x);

If you want to use this right now but need support for IE or other unsupporting browsers, you can use a shim. I recommend the es6-shim. MDN also offers a shim if for some reason you don't want to put the whole es6-shim into your project. For maximum compatibility you want the es6-shim, because unlike the MDN version it detects buggy native implementations of find and overwrites them (see the comment that begins "Work around bugs in Array#find and Array#findIndex" and the lines immediately following it).

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
  • 1
    `find` is better than `filter` since `find` stops immediately when it finds an element matched the condition, while `filter` loops through all the elements to give all matched elements. – Anh Tran Nov 12 '18 at 08:52
83

What about using filter and getting the first index from the resulting array?

var result = someArray.filter(isNotNullNorUndefined)[0];
Dan Dascalescu
  • 143,271
  • 52
  • 317
  • 404
Phil Mander
  • 1,819
  • 15
  • 20
  • 8
    Keep using es5 methods. var result = someArray.filter(isNotNullNorUndefined).shift(); – someyoungideas Jan 20 '16 at 19:21
  • 1
    Though i myself up-voted for find answer above @Bergi, i think with ES6 destructuring we can improve above a little bit: var [result] = someArray.filter(isNotNullNorUndefined); – Nakul Manchanda Apr 30 '18 at 01:23
  • 1
    @someyoungideas could you explain the benefit of using `.shift` over here? – jakubiszon Nov 05 '18 at 13:02
  • 13
    @jakubiszon The benefit of using `shift` is that it "looks smart" but is actually more confusing. Who would think that calling `shift()` with no arguments would be the same as taking the first element? It's unclear IMO. Array access is quicker anyway: https://jsperf.com/array-access-vs-shift – Josh M. Jan 29 '20 at 13:21
  • 1
    Also, square bracket notation is available for both objects and arrays in ES5 AFAIK, never seen a preference of `.shift()` over `[0]` explicitly stated like this. Despite that, it's an alternative you can choose to use or not, I'd stick with `[0]` though. – SidOfc Mar 16 '20 at 14:14
73

Summary:

  • For finding the first element in an array which matches a boolean condition we can use the ES6 find()
  • find() is located on Array.prototype so it can be used on every array.
  • find() takes a callback where a boolean condition is tested. The function returns the value (not the index!)

Example:

const array = [4, 33, 8, 56, 23];

const found = array.find(element => {
  return element > 50;
});

console.log(found);   //  56
Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
Willem van der Veen
  • 33,665
  • 16
  • 190
  • 155
  • 8
    If you do want the index instead of the value, use [`findIndex`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex) – Flimm Oct 01 '20 at 13:37
17

It should be clear by now that JavaScript offers no such solution natively; here are the closest two derivatives, the most useful first:

  1. Array.prototype.some(fn) offers the desired behaviour of stopping when a condition is met, but returns only whether an element is present; it's not hard to apply some trickery, such as the solution offered by Bergi's answer.

  2. Array.prototype.filter(fn)[0] makes for a great one-liner but is the least efficient, because you throw away N - 1 elements just to get what you need.

Traditional search methods in JavaScript are characterized by returning the index of the found element instead of the element itself or -1. This avoids having to choose a return value from the domain of all possible types; an index can only be a number and negative values are invalid.

Both solutions above don't support offset searching either, so I've decided to write this:

(function(ns) {
  ns.search = function(array, callback, offset) {
    var size = array.length;

    offset = offset || 0;
    if (offset >= size || offset <= -size) {
      return -1;
    } else if (offset < 0) {
      offset = size - offset;
    }

    while (offset < size) {
      if (callback(array[offset], offset, array)) {
        return offset;
      }
      ++offset;
    }
    return -1;
  };
}(this));

search([1, 2, NaN, 4], Number.isNaN); // 2
search([1, 2, 3, 4], Number.isNaN); // -1
search([1, NaN, 3, NaN], Number.isNaN, 2); // 3
Community
  • 1
  • 1
Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
10

If you're using underscore.js you can use its find and indexOf functions to get exactly what you want:

var index = _.indexOf(your_array, _.find(your_array, function (d) {
    return d === true;
}));

Documentation:

Matt Woelk
  • 1,940
  • 1
  • 20
  • 24
5

As of ES 2015, Array.prototype.find() provides for this exact functionality.

For browsers that do not support this feature, the Mozilla Developer Network has provided a polyfill (pasted below):

if (!Array.prototype.find) {
  Array.prototype.find = function(predicate) {
    if (this === null) {
      throw new TypeError('Array.prototype.find called on null or undefined');
    }
    if (typeof predicate !== 'function') {
      throw new TypeError('predicate must be a function');
    }
    var list = Object(this);
    var length = list.length >>> 0;
    var thisArg = arguments[1];
    var value;

    for (var i = 0; i < length; i++) {
      value = list[i];
      if (predicate.call(thisArg, value, i, list)) {
        return value;
      }
    }
    return undefined;
  };
}
Kevin Lee Garner
  • 857
  • 10
  • 12
4
foundElement = myArray[myArray.findIndex(element => //condition here)];
Dotista
  • 404
  • 3
  • 12
  • 4
    A real-life example with a condition clause and some explanatory words would make your answer more valuable and comprehensible. – SaschaM78 Apr 04 '19 at 09:54
3

Array.prototype.find() does just that, more info: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find

Dan Ochiana
  • 3,340
  • 1
  • 30
  • 28
3

Use findIndex as other previously written. Here's the full example:

function find(arr, predicate) {
    foundIndex = arr.findIndex(predicate);
    return foundIndex !== -1 ? arr[foundIndex] : null;
}

And usage is following (we want to find first element in array which has property id === 1).

var firstElement = find(arr, e => e.id === 1);
michal.jakubeczy
  • 8,221
  • 1
  • 59
  • 63
0

I have got inspiration from multiple sources on the internet to derive into the solution below. Wanted to take into account both some default value and to provide a way to compare each entry for a generic approach which this solves.

Usage: (giving value "Second")

var defaultItemValue = { id: -1, name: "Undefined" };
var containers: Container[] = [{ id: 1, name: "First" }, { id: 2, name: "Second" }];
GetContainer(2).name;

Implementation:

class Container {
    id: number;
    name: string;
}

public GetContainer(containerId: number): Container {
  var comparator = (item: Container): boolean => {
      return item.id == containerId;
    };
    return this.Get<Container>(this.containers, comparator, this.defaultItemValue);
  }

private Get<T>(array: T[], comparator: (item: T) => boolean, defaultValue: T): T {
  var found: T = null;
  array.some(function(element, index) {
    if (comparator(element)) {
      found = element;
      return true;
    }
  });

  if (!found) {
    found = defaultValue;
  }

  return found;
}
Henrik
  • 1,897
  • 1
  • 16
  • 9
0
      const employees = [
        {id: 1, name: 'Alice', country: 'Canada'},
        {id: 2, name: 'Bob', country: 'Belgium'},
        {id: 3, name: 'Carl', country: 'Canada'},
        {id: 4, name: 'Dean', country: 'Germany'},
      ];
    
      // ️ filter with 1 condition
      const filtered = employees.filter(employee => {
        return employee.country === 'Canada';
      });
      // ️ [{id: 1, name: 'Alice', country: 'Canada'},
      //     {id: 3, name: 'Carl', 'country: 'Canada'}]
      console.log(filtered);
    
      // ️ filter with 2 conditions
      const filtered2 = employees.filter(employee => {
        return employee.country === 'Canada' && employee.id === 3;
      });
    
      // ️ [{id: 3, name: 'Carl', country: 'Canada'}]
      console.log('filtered2: ', filtered2);
    
      const employee = employees.find(obj => {
    return obj.country === 'Canada';
  });

  // ️ {id: 1, name: 'Alice', country: 'Canada'}
  console.log(employee);
Engr.Aftab Ufaq
  • 3,356
  • 3
  • 21
  • 47
-2

There is no built-in function in Javascript to perform this search.

If you are using jQuery you could do a jQuery.inArray(element,array).

Eranga
  • 32,181
  • 5
  • 97
  • 96
PedroSena
  • 645
  • 6
  • 14
  • That would work, too, though I'll go with Underscore probably :) – Jakub P. May 04 '12 at 23:27
  • 3
    This doesn't satisfy the output of what the asker requires (needs the element at some index, not a boolean). – Graham Robertson Mar 12 '13 at 00:48
  • @GrahamRobertson `$.inArray` doesn't return a boolean, it (surprisingly!) returns the index of the first matching element. It still doesn't do what the OP asked for, though. – Mark Amery Aug 06 '15 at 12:05
-2

A less elegant way that will throw all the right error messages (based on Array.prototype.filter) but will stop iterating on the first result is

function findFirst(arr, test, context) {
    var Result = function (v, i) {this.value = v; this.index = i;};
    try {
        Array.prototype.filter.call(arr, function (v, i, a) {
            if (test(v, i, a)) throw new Result(v, i);
        }, context);
    } catch (e) {
        if (e instanceof Result) return e;
        throw e;
    }
}

Then examples are

findFirst([-2, -1, 0, 1, 2, 3], function (e) {return e > 1 && e % 2;});
// Result {value: 3, index: 5}
findFirst([0, 1, 2, 3], 0);               // bad function param
// TypeError: number is not a function
findFirst(0, function () {return true;}); // bad arr param
// undefined
findFirst([1], function (e) {return 0;}); // no match
// undefined

It works by ending filter by using throw.

Paul S.
  • 64,864
  • 9
  • 122
  • 138