Reach to the last index starting at index 0 The task:


  Given an integer list where each number represents the number of hops
  you can make, determine whether you can reach to the last index
  starting at index 0.
  
  For example, [2, 0, 1, 0] returns true while [1, 1, 0, 1] returns
  false.


My solutions:

// functional:
const reachLastIndexOf = (arr, start) =&gt; {
  if (arr[start] === undefined) return false; 
  const next = start + arr[start];
  if (start === next || next &gt;= arr.length) return false;
  if (next === arr.length -1) return true;
  return reachLastIndexOf(arr, next);
}
console.log(reachLastIndexOf([2, 0, 1, 0], 0)

// imperative:
const reachLastIndexOf2 = arr =&gt; {
  let start = 0;
  while(start &lt; arr.length) { // I'm tempted to write `while(true)` here
    if (arr[start] === undefined) return false;
    const next = start + arr[start];
    if (start === next || next &gt;= arr.length) return false;
    if (next === arr.length - 1) return true;
    start = next;
  }
}
console.log(reachLastIndexOf2([1, 1, 0, 1]));
Default Parameters

When can compare the method signatures of the iterative approach and the functional one, we can see, that for the functional one we have to append the 0 on the first call.

If you want to avoid this - and in this case I think it makes sense - you can add a default parameter into the method signature: const reachLastIndexOf = (arr, start = 0)

Long Condition &amp; Pattern Matching

The condition start === next || next &gt;= arr.length is currently in on if-statement. The ||-operator operates short-circuit - that means if the first operation is true, the compiler don't validates the second condition. When we split the disjuncture into to two if-statements, we can keep the same short-circuit behavior and create a more pattern-matching look and feel:

if (start === next) return false 
if (next &gt;= arr.length) return false


The advantage is that you have a less logical operator. The less code, the fewer errors and at the same time the readability is increased.
Please note that I have meant this more generally for conditions, even if in this context only a Boolean operator is avoided, the code is still more readable in my view than before.

Readable Method Names

For this algorithm we need to check if:


an index exists (arr[start] === undefined)
an index stays on the same place (start === next)
an index is out of the array bound (next &gt;= arr.length) 
an index reached the end (next === arr.length - 1)


We all now what these checks are meaning but it would be much better if we wrap these into their own functions

const reachLastIndexOf = (arr, start = 0) =&gt; {
    if (isIndexDefined(arr, start)) return false; 

    const next = start + arr[start];

    if (isStayingInTheSamePosition(start, next)) return false
    if (isOverArrayBound(next, arr)) return false
    if (isReachingTheEnd(next, arr)) return true
    return reachLastIndexOf(arr, next)
}






const reachLastIndexOf = (arr, start = 0) =&gt; {
  if (isIndexDefined(arr, start)) return false;

  const next = start + arr[start];

  if (isStayingInTheSamePosition(start, next)) return false
  if (isOverArrayBound(next, arr)) return false
  if (isReachingTheEnd(next, arr)) return true
  return reachLastIndexOf(arr, next)
}


const isIndexDefined = (array, index) =&gt; array[index] === undefined
const isStayingInTheSamePosition = (start, next) =&gt; start === next
const isOverArrayBound = (next, array) =&gt; next &gt;= array.length
const isReachingTheEnd = (next, array) =&gt; next === array.length - 1

console.log(reachLastIndexOf([2, 0, 1, 0], 0));