Search code examples
javascriptarraysbrowserv8javascript-engine

Time complexity of Javascript Array.find() in modern browsers


Since array.find() iterates over an array, if I handle (potentially) large arrays, I always make sure to have an indexed object like so:

{ [id:string]: Item }

if I need to look up items by id in these arrays.

However, living in a time of V8 (and comparable engine optimisations for Safari and Firefox), I'm wondering if maybe under the hood, a simple array.find() is already optimized for it? Or will optimize for it (create such an indexed object) at runtime as soon as it has to perform this operation once?

Is it true that modern browsers already have some kind of optimization for O(N) type algorithms that could become O(1) with the proper implementation? Or am I thinking too much of what these browsers actually can / will do under the hood?


Solution

  • V8 developer here. The time complexity of Array.prototype.find is O(n) (with n being the array's length), and it's fair to assume that it will remain that way.

    Generally speaking, it's often impossible for engines to improve the complexity class of an operation. In case of Array.prototype.find, the predicate function you pass might well care how often it gets called:

    [1, 2, 3].find((value, index, object) => {
      console.log(`Checking ${value}...`);  // Or any other side effect.
      return value === 42;
    });
    

    In such a case, the engine has no choice but to iterate over the entire array in exactly the right order, because anything else would observably break your program's behavior.

    In theory, since JS engines can do dynamic optimizations, they could inspect the predicate function, and if it has no side effects, they could use it to build up some sort of index/cache. Aside from the difficulty of building such a system that works for arbitrary predicates, this technique even when it does work would only speed up repeated searches of the same array with the same function, at the cost of wasting time and memory if this exact same scenario will not occur again. It seems unlikely that an engine can ever make this prediction with sufficient confidence to justify investing this time and memory.

    As a rule of thumb: when operating on large data sets, choosing efficient algorithms and data structures is worth it. Typically far more worth it than the micro-optimizations we're seeing so much in SO questions :-)

    A highly optimized/optimizing engine may be able to make your O(n) code somewhere between 10% and 10x as fast as it would otherwise be. By switching to an O(log n) or O(1) solution on your end, you can speed it up by orders of magnitude. That's often accomplished by doing something that engines can't possibly do. For example, you can keep your array sorted and then use binary search over it -- that's something an engine can't do for you automatically because obviously it's not allowed to reorder your array's contents without your approval. And as @myf already points out in a comment: if you want to access things by a unique key, then using a Map will probably work better than using an Array.

    That said, simple solutions tend to scale better than we intuitively assume; the standard warning against premature optimizations applies here just as everywhere else. Linearly searching through arrays is often just fine, you don't need a (hash) map just because you have more than three items in it. When in doubt, profile your app to find out where the performance bottlenecks are.