Why .every() returns true on an empty array
Looking deeply into a seemingly absurd occurrence in JavaScript
Table of contents
No headings in the article.
The array methods are non-arguably one of the most important things to learn in JavaScript. They make array manipulation a breeze when coding in the language. If you code regularly in JavaScript, there's no way you won't have gotten used to two or more of these methods.
Some of them take values of very specific types as arguments e.g the join
method which joins all items in the array it's called on, separating them by the first argument passed to it (which is expectedly a string).
let dateValues = ["10", "09", "2022"]
let date = dateValues.join("-")
console.log(date) // → 10-09-2022
Most other array methods on the other hand are higher-order functions. They take other functions as arguments. An example of such a method is the every
method, which returns true
if all the items in the array pass the test function given to it, and false
otherwise.
function isLessThanSix(value) {
return value < 6
}
let firstList = [4, 2, 3, 5]
let secondList = [4, 2, 8, 1]
console.log(firstList.every(isLessThanSix)) // → true
console.log(secondList.every(isLessThanSix)) // → false
In JavaScript, functions are like any other values, they can be assigned as values to variables, passed to other functions as arguments, e.t.c. In this example, we defined a function named isLessThanSix
which returns true
or false
depending on whether the value passed to it is less than 6
or not. We then passed it to the every
method twice as a test function, for each of the firstList
and secondList
arrays.
The every
method will cause each of the items in the array to be passed to isLessThanSix
, and if the function returns true
for all the items, every
also returns true
. Otherwise, it returns false
. All the numbers in the firstList
array are less than 6
so every
returns true
. In the second case, the secondList
array contains 8
which is greater than 6
, so false
is returned by the method.
We could decide to define and pass the function directly to the method without assigning it to a variable, and it would still work.
console.log(
firstList.every(function (value) {
return value < 6
})
) // → true
But surprisingly, calling every
on an empty array returns true
regardless of what the check is.
let arr = []
console.log(arr.every(isLessThanSix)) // → true
arr
is empty, but every
still returns true
. Even if we always return false
from the test function, the result won't change.
console.log(
arr.every(function () {
return false
})
) // → true
This should be expected. There're no items in the array, so the function that we've passed as the first argument won't be called at all. The number of calls that'll be made to the function depends directly on the number of items in the array. If there're two items in the array, the function will be called twice (each item will be passed to it each time). If there're four, then it'll be called four times.
So, why does the every
method return true
every time it is called on an empty array?
It is because of the way it has been implemented. But before we talk more about that, let's quickly see another array method that has similar behavior.
The some
method, when called on an array, returns true
if some items in the array pass a test i.e the test function passed to it as the first argument returns true
for one or more items in the array.
let firstList = [7, 9, 8, 11]
let secondList = [9, 12, 3, 5]
console.log(firstList.some(isLessThanSix)) // → false
console.log(secondList.some(isLessThanSix)) // → true
(We'll use isLessThanSix
throughout this article.)
The method works like every
, but it requires just one of the items in the array to pass the test.
some
returns false
whenever it is called on an empty array regardless of the test function passed to it, which is the opposite of what happens with every
.
let arr = []
console.log(arr.some(isLessThanSix)) // → false
Again, this is because of how the method has been implemented. To understand what this means, let's look at a scenario.
You're given a pile of books and asked to determine if every one of the books contains up to 1000 pages or not.
You're not required to give the number of books that contains that many pages and neither are you mandated to give the number of pages in each book. All that's needed from you is to determine if every book in the pile contains up to 1000 pages or not, and then give the answer which is either "true" or "false". How would you go about this?
Most probably, you would pick up one book at a time, and check if it has up to 1000 pages. Once you come across a book that has less than that number of pages, you would give an answer which would be "false".
You don't have to look through each and every one of the books. You just have to find one that has less than 1000 pages, and you would have an answer. If there're 400 books in total to check, and the third book you checked has less than 1000 pages, you can just conclude that not every one of the books has up to 1000 pages without looking through the remaining 397 books.
If a person says "every one of the boys is tall". Then he also means to say that "none of the boys is short". Assuming that there are three boys in total if we decide to examine the boys one after the other, and it happens that the first boy we examine is short, we don't have to examine the other two to conclude that the statement that "every one of the boys is tall" is not true.
This is the same for the every
method. It does not have to look through every item in the array to perform its job. It just has to look through the items one after the other, and once it finds one that doesn't pass the test, it returns false
. In the case above, where it has to determine if all the numbers in the array are less than 6
, it looks through them one after the other, and whenever it comes across a number that's not less than 6
, it returns false. To understand this better, we'll write our own version of every
as a standalone function.
Unlike the every
that's an array method, our every
will take two compulsory parameters. Instead of calling every
like this (which uses the built-in every
method):
let firstList = [4, 2, 3, 5]
console.log(firstList.every(isLessThanSix))
we'll pass the array directly to our function as the first argument (since it will be standing alone), and the test function as the second argument.
let firstList = [4, 2, 3, 5]
console.log(every(firstList, isLessThanSix))
It might be better to name the function everyItemIn
so it sounds more natural. 😀
let firstList = [4, 2, 3, 5]
console.log(everyItemIn(firstList, isLessThanSix))
We can now read the second part (from everyItemIn...
) as "every item in first numbers is less than six", which sounds cool to me. 😅
Let's now define the function. The first parameter which is expected to be an array will be named array
, and the second parameter which is expected to be a function (for the test) will be named passesTheTest
.
function everyItemIn(array, passesTheTest) {
}
As we saw earlier, in order to determine if every book in a pile has up to 1000 pages, you'll probably want to go through each book one at a time. Our function, everyItemIn
will do the same with items in the array by looping through them.
function everyItemIn(array, passesTheTest) {
for (let i = 0; i < array.length; i++) {
const item = array[i]
}
}
As you pick up each book, you'll check if it has up to 1000 pages. The result of your examining the book is either "true" or "false". Our function will do the same by passing the item to the test function which we've named passesTheTest
, and expecting it to return a boolean value that is also either true
or false
.
function everyItemIn(array, passesTheTest) {
for (let i = 0; i < array.length; i++) {
const item = array[i]
const itemPassesTheTest = passesTheTest(item)
}
}
Lastly, if after examining one of the books, you find out that it has less than 1000 pages, you'll stop checking and just give an answer which is "false", meaning that not every book in the pile has up to 1000 pages. Our function will do the same. After carrying out the test, if any item does not pass the test, it'll return false
and stop checking. The return
statement will cause the loop to stop.
function everyItemIn(array, passesTheTest) {
for (let i = 0; i < array.length; i++) {
const item = array[i]
const itemPassesTheTest = passesTheTest(item)
if (!itemPassesTheTest) return false
}
}
(Note the !
in the if
condition.)
Lastly, if after you look through all of the books and none of them has less than 1000 pages, you'll give an answer which is "true", meaning that every book in the pile has up to 1000 pages. Our function will do this too. It'll return true
after the loop. As such, if any of the items fail the test, false
would have been returned while the loop was running. Else, if none of them fail it, the loop will run completely and the function will return true
.
function everyItemIn(array, passesTheTest) {
for (let i = 0; i < array.length; i++) {
const item = array[i]
const itemPassesTheTest = passesTheTest(item)
if (!itemPassesTheTest) return false
}
return true
}
(Note that return true
is after the loop, not inside it.)
Now we can call everyItemIn
with an array and a test function, and it'll give the appropriate value.
let firstList = [4, 2, 3, 5]
let secondList = [4, 8, 2, 1]
console.log(everyItemIn(firstList, isLessThanSix)) // → true
console.log(everyItemIn(secondList, isLessThanSix)) // → false
But if we pass an empty array to everyItemIn
, the loop will not run at all, since the length of the array will be 0
, and i < array.length
will equal false
(both the initial value of i
and array.length
are 0
). Therefore, the function will just return true
as written on the last line.
let arr = []
console.log(everyItemIn(arr, isLessThanSix)) // → true
This is why the array every
method returns true
when called on an empty array.
You could argue that we can just add a simple check to see if the array has no item and return another value. For example, we could add the following at the top of the function.
if (array.length === 0) return false
But doing this makes no sense, and this is why.
Let's assume that you are now asked to determine if every book in a box has up to 1000 pages. You can only give a boolean answer, which is either "true" or "false" The box is locked and you have the key. On opening the box, you find that there's no book in the box. What answer would you give? Remember that you can only give "true" or "false".
Whatever answer you give would make no sense. Saying "true" would mean that every book in the box has up to 1000 pages, and saying "false" would mean that not every book in the box has up to 1000 pages. But there are no books in the box, and you didn't look through any books, so how could you say "true" or "false"?
Since you must give either "true" or "false", giving any of the two would do just fine. But in such a situation, it's better to go with the default value, if there's one. Therefore, in our function, we won't do any special handling of the case of an empty array. We'll just go with the default value, and that value is true
which is returned on the last line.
A default value is basically what will happen if none of the conditions is true. In the case of our function, false
will be returned only if one of the items in the array happens to fail the test, meaning that it's conditional. That which is not conditionally returned (is a straightforward implementation) is true
.
Now you know why we should not especially return false
if the array is empty. It's not necessarily the correct value our function should return, so we'll leave the function to handle it normally without special checks, and whatever value it returns, as far as it is true
or false
will do just fine.
As we've done with every
, we can write a custom implementation of some
too. We'll name it someItemsIn
. It'll be almost like everyItemIn
. Let's look at it as we did every
.
When you're given a pile of books and you're to determine if some (not every anymore) of the books have up to 1000 pages, you would go over the books one at a time, and once you find one that has up to 1000 pages, you can give an answer which is "true" meaning that some of the books do have up to a thousand pages. Our function will do the same by looping through every item in the array and returning true
once it finds one item that passes the test since some means one or more.
function someItemsIn(array, passesTheTest) {
for (let i = 0; i < array.length; i++) {
const item = array[i]
const itemPassesTheTest = passesTheTest(item)
if (itemPassesTheTest) return true
}
}
If after looking through all the books, none has up to 1000 pages, then you'll give an answer which is "false". Our function will do the same. It'll return false
if none of the items in the array passes the test (after the loop).
function someItemsIn(array, passesTheTest) {
for (let i = 0; i < array.length; i++) {
const item = array[i]
const itemPassesTheTest = passesTheTest(item)
if (itemPassesTheTest) return true
}
return false
}
We can then call it like this:
let firstList = [14, 12, 8, 7]
let secondList = [4, 8, 2, 1]
console.log(someItemsIn(firstList, isLessThanSix)) // → false
console.log(someItemsIn(secondList, isLessThanSix)) // → true
For fun, we could write a wrapper function around isLessThanSix
named areLessThanSix
to make the flow more natural. 😋
function areLessThanSix (value) {
return isLessThanSix(value)
}
console.log(someItemsIn(firstList, areLessThanSix)) // → false
console.log(someItemsIn(secondList, areLessThanSix)) // → true
Now we can read the part of the snippet above from someItemsIn...
as "some items in first list are less than six". Makes sense to me. (You shouldn't do this in a real program. 😃) I might an article later about naming variables and functions. It'll be an interesting one.
Anyway, if we pass an empty array to our function, it'll return false
, since the loop won't run at all, and the code on the last line of the function will just run.
console.log(someItemsIn(arr, areLessThanSix)) // → false
Again, we have no reason to change this default behavior. Both true
and false
make no real sense. Returning true
means that some items passed the test, and returning false
means no item passed the test. But there are no items in the list, so how could we especially return true
or false
?
Since our function must return a boolean value, we'll let it return the default value (which is false
) when passed an empty array.
Internally, the every
and some
array methods work like our functions. They, therefore, return true
and false
respectively when called on empty arrays, since they must return a boolean value, and those are the default values.
Note that the every
and some
methods could have been configured to return a custom value that is not boolean if the array is empty, but that's not the case. Plus, normally, as in natural language, "every" and "some" are expected to resolve to either true or false.
- Every human being is an animal => true (we're higher animals)
- Every animal is a human being => false
- Some people are stupid => true
- Some people are trees => false
- Every human being in the sun has a hand => true / false
- Some human being in the sun have hands => true / false
There're no human beings in the sun (it's a damn hot star), so how can we say human beings in the sun have hands or that they do not have one. If we say true, then that means we're confirming that there are human beings in the sun and that (every/some of them) have hands. If we say false, then we're still confirming that there are human beings in the sun, but that (every/some of them) do not have hands. But since "every" and "some" must resolve to true or false, we'll just go with any one of the two. In the case of our function, the one we'll go with is the default value that's not returned conditionally.
Hopefully, this article helped you get a better understanding of the every
and some
methods, and roughly, how they function internally.
If you enjoyed this, do follow me on Hashnode @abdulramonjemil for more of my content. And if you'll like to reach out to me, I'm also available on Twitter @abdulramonjemil. You can send a DM or say "hi".
I'm planning to start a newsletter, but it's not getting enough signups. I'm afraid I'll be wasting my efforts. If you'll like to join, do so via this link.
Thanks for reading!!! 🥰