Что происходит с памятью при бесконечной рекурсии
Что проверяют
Различаете ли вы стек и кучу и понимаете ли, что в браузере это не падение вкладки, а обычное исключение. Вопрос простой, но по деталям видно, открывал ли человек панель памяти хоть раз.
Как отвечать
Каждый вызов кладёт на стек кадр: аргументы, локальные переменные, адрес возврата. Без условия выхода кадры не снимаются, стек упирается в свой предел, и движок бросает RangeError о превышении размера стека вызовов. Это обычное исключение, оно ловится try/catch, вкладка остаётся живой.
Важные уточнения:
- предел измеряется не числом вызовов, а объёмом: функция с десятком локальных переменных исчерпает стек заметно раньше, чем функция без аргументов, поэтому глубина рекурсии плавает от движка к движку;
- куча растёт не от самой рекурсии, а от объектов, созданных внутри и достижимых из кадров: сборщик не может их забрать, пока кадры на месте;
- отдельный случай это асинхронная рекурсия через
setTimeout,queueMicrotaskилиawait. Стек там каждый раз свежий, переполнения не будет, вместо этого разрастаются очередь и куча, страница перестаёт отвечать, и вкладка падает уже по нехватке памяти.
const depth = (node) => (node ? 1 + depth(node.parent) : 0);
// цикл в ссылках даёт RangeError, а не бесконечный ответГде спотыкаются
Называют переполнение стека утечкой памяти. Это исчерпание фиксированной области, после исключения кадры снимаются и память возвращается. Утечка это удержание ненужных объектов в куче, у неё другие симптомы и другой инструмент поиска.
Рассчитывают на оптимизацию хвостовых вызовов. В спецификации языка она описана, но из распространённых движков её выполняет один, так что рекурсия, переписанная в хвостовую, в остальных упадёт ровно так же.
Пытаются поднять предел вместо того, чтобы убрать рекурсию. Размер стека из кода не настраивается: обход переписывают в цикл с явным массивом узлов или добавляют защиту от повторного посещения, если в данных возможны циклические ссылки.