Ваша функция не имеет хвостового вызова и не будет при всех обстоятельствах, потому что вы вызываете рекурсивный вызов более одного раза: Помните, что оптимизация хвостового вызова в основном означает, что функция превращается в цикл, ... который невозможно в этом случае.
Тем не менее, вместо того, чтобы рекурсивно находить все вложенные элементы и многократно повторять массив, используйте id для объекта Map, тогда вам просто нужно выполнить итерацию дважды: один раз для построения карты и второй раз связать каждый элемент с его родителем. Отличную реализацию этого можно найти здесь .
Здесь была бы версия с хвостовым вызовом (я бы просто использовал цикл здесь):
function listToTree([el, ...rest], parent = new Map, roots = []) {
if(el.parentID)
parent.get(el.parentID).children.push(el);
else roots.push(el);
parent.set(el.id, el);
el.children = [];
if(!rest.length) return roots;
return listToTree(rest, parent, roots); // A proper tail call: This can be turned into a loop
}