Artur Grigio
Artur Grigio

Reputation: 5553

for loop with break vs find() in JavaScript

Just saw someone write this:

let id = 1;
...
let employee = null;

for (const e of employees) {
    if (e.id === id) {
        employee = e;
        break;
    }
}

seems like an overcomplicated way of writing this:

let id = 1;
...
let employee = employees.find(e => e.id === id);

Is there any benefit to using a loop with a break vs a find()?

What is find()'s implementation behind the curtain?

Upvotes: 11

Views: 20233

Answers (6)

Alexandr
Alexandr

Reputation: 56

If you know, with some degree of certainty, that the element you seek is closer to the end (right side) of the array, using for() loop instead of find could be beneficial. You would construct your for() loop to start in reverse, hence it will travel only the length from the end to the element. E.g.looking for 99 in [1..100]. The for() will only compare 2 elements, whereas find will compare 99 elements.

Elaboration on Performance. I found it odd that everything I read so far was stating that find() is faster than for() [in forward search], because when I ran it few times I observed the opposite, i.e. find() was about 30%-50% slower. Like this:

enter image description here

So I ran it 1 million times, then I averaged the performance time. To clarify the premise:

I created an array of 100,000 elements. Each elements is an object with 2 elements, with key = number from 0 to 100,000, and the array was sorted by the key. From this array I attempted to find the object with key=97900, which is towards the end.

let arr = [{key: 1, value: 1},...]

Results (average time in ms) a) Find: 0.078452628 ms b) For: 0.217990078 ms c) For [reversed]: 0.000223515 ms

From the plot we can see that for() has more unpredictable outliers. We can also observe that at the beginning find() has slower performance.

Scatter Plot

If you want to run tests by yourself, here is some starter code:

let arr = [];
for (let i = 0; i < 100000; i++) {
  arr.push({
    key: i,
    value: i ^ 2
  });
}
console.log("find() vs for() vs reverse for() 97,900 in 100,000, array of objects")
console.time("find")
arr.find(i => {
  return i.key === 97900
})
let find = console.timeEnd("find")
console.time("for")
for (let i = 0; i < arr.length; i++) {
  if (arr[i].key === 97900) {
    break
  }
}
console.timeEnd("for")
console.time("for-rev")
for (let i = arr.lenght - 1; i > 0; i--) {
  if (arr[i].key === 97900) {
    break
  }
}
console.timeEnd("for-rev")

Upvotes: 0

dhilt
dhilt

Reputation: 20844

I implemented both approaches as two methods with the same signature (forBreakMethod(x) and findMethod (x)) and passed them through a simple performance test spec.

(() => {

const test = (DATA_LENGTH = 1000, INDEX = 9, TESTS_COUNT = 10000) => {
  // data initialization
  const employees = [];
  for (let i = 1; i <= DATA_LENGTH; i++){
    employees.push({ id: i });
  }
  
  // methods initialization
  const forBreakMethod = (x) => {
    const length = employees.length;
    for (let i = 0; i < length; i++) {
      if (x === employees.id) {
        return employees[i];
      }
    }
  }
  const findMethod = (x) => {
    return employees.find(item => x === item.id);
  }
  
  // for-break test
  const time1 = performance.now();
  for (let i = 0; i < TESTS_COUNT; i++) {
    forBreakMethod(INDEX);
  }
  const time2 = performance.now();
  console.log(`[for-break] find ${INDEX} from ${DATA_LENGTH}: ${time2 - time1}`);
  
  // find test
  const time3 = performance.now();
  for (let i = 0; i < TESTS_COUNT; i++) {
    findMethod(INDEX);
  }
  const time4 = performance.now();
  console.log(`[Array.find] find ${INDEX} from ${DATA_LENGTH}: ${time4 - time3}`);

  console.log('---------------');
};

test(10, 1, 1000000);
test(10, 5, 1000000);
test(10, 9, 1000000);
console.log('\n');
test(100, 10, 100000);
test(100, 50, 100000);
test(100, 99, 100000);
console.log('\n');
test(1000, 10, 10000);
test(1000, 500, 10000);
test(1000, 999, 10000);
console.log('\n');
test(10000, 10, 10000);
test(10000, 5000, 10000);
test(10000, 9999, 10000);

})();

A conclusion I see is that the Array.find approach has an advantage if the item we want to find lives in the left part of the data array, but its performance getting low when the result index goes to the right. The for-break approach seems more stable, since its performance does not depend on the index we want to find, but its cost is significant.

So very rough, I would say that the Array.find approach could be considered as more performant if we are going to walk through the first half of the data array, otherwise I would use the for-break approach.

PS Chrome, Safari, Firefox, 2018.

Upvotes: 3

MarkHu
MarkHu

Reputation: 1869

It is fairly obvious that the native find() function is faster than a loop algorithm. But the OP asked "Is there any benefit to using a loop...?" A theoretical reason someone might want to loop is if they needed to process non-matching elements along the way.

Upvotes: 2

Matt Kuhns
Matt Kuhns

Reputation: 1368

Tried this:

var startTime, endTime;

function start() {
  startTime = new Date();
};

function end() {
  endTime = new Date();
  var timeDiff = endTime - startTime; //in ms
  console.log(timeDiff + " milliseconds");
}

let employees = [];
for (var i = 10000; i > 0; i--){
  let thisEmployee = {
    id: i,
    name: "Person" + i
  }
  employees.push(thisEmployee);
}

let id = 1;
let employee1 = null;
start();
for (const e of employees) {
    if (e.id === id) {
        employee1 = e;
        break;
    }
}
end();
console.log("Method1: ", JSON.stringify(employee1));
start();
let employee2 = employees.find(e => e.id === id);
end();
console.log("Method2: ", JSON.stringify(employee2));

First method is much slower:

"12 milliseconds"
"Method1: "
"{\"id\":1,\"name\":\"Person1\"}"
"0 milliseconds"
"Method2: "
"{\"id\":1,\"name\":\"Person1\"}"

Upvotes: 4

Perfomance

.find() is faster than for...break.

Check this link for test results. for...break is 30% slower than .find()


.find() source code can be found here

.find() is not supported in older browsers like IE11 and below. You need to use a polyfill instead.


Opinion

.find() is better due to complexity level and the internal algorithm used. Using for...break you will always doing a linear search which means n * n repetitions. The bigger the array, the slower the function.

Upvotes: 15

obermillerk
obermillerk

Reputation: 1580

So I just tried this:

const array = [0, 1, 2, 3, 4, 5,];

for (const i of array) {
  console.log(i);
  if (i === 3)
    break;
}

array.find(i => {
  console.log(i);
  return i === 3;
});

Both of them output

0
1
2
3

So, they both short circuit on the first answer they find as I would expect, but as for specific performance I can't say for certain whether one is better than the other. I imagine the performance would be comparable if not identical.
The one big difference that stands out to me is that find returns the value, but the for loop you have to handle the value in the loop, otherwise assign it to a variable to use later. A minor detail but it could potentially make find much more readable for someone else looking at your code.

Upvotes: 0

Related Questions