appropriate names
const checkArray = (word, arr) => {
This is just a terrible signature,
almost as meaningless as const foo = (bar, baz) ....
Tell the caller what's going to happen:
const checkWordMatchesLetters = (word, letters) => {
and then explain to the caller that we expect a vector of letters.
early exit
if (letterIdx === -1) good = false
There's no need to keep going after we encounter a missing letter.
This could just be return false.
In fact, there's no need for a good flag,
given that control flow within our function suffices.
@ggorlen helpfully points out that literally using that return
unfortunately just breaks out of that iteration of the forEach callback, and the return value is ignored. Really, forEach is inappropriate here -- .every or for .. of would allow early exit.
Indeed, quite right.
I was essentially looking for an all() helper here.
appropriate datastructure
arr.splice(letterIdx, 1)
The .findIndex() call had linear \$O(n)\$ time complexity cost,
and asking .splice() to shift elements to shrink the array has similar cost.
So operating on a word of modest length
like "supercalifragilisticexpialidocious"
would entail hundreds of operations,
where surely only a couple dozen should suffice.
In total we have quadratic \$O(n^2)\$ cost.
Consider implementing a multiset (counter) via an object mapping,
from letter to count.
Linear cost to construct it,
and constant cost to verify each positive count
while decrementing each given letter's count,
adding up to a total of linear cost.
Even if you don't go that far, we could have
a better (still quadratic) algorithm by writing a
tombstone
sentinel into an array of letters,
to show we have "used up" that particular letter.
Probably better to operate on a copy of caller's parameter,
so caller isn't surprised we trashed his array.
Going a bit further, if you invest \$O(n \log n)\$ effort in
sorting that copied array, you can enjoy \$O(\log n)\$
lookup times via binary search.