В чем разница между рекурсией и вызовом через setTimeout с точки зрения использования памяти
Что проверяют
Понимаете ли вы, как устроены стек вызовов и цикл событий в JavaScript, и видите ли, что setTimeout не экономит память, а переносит её из стека в кучу. Следом часто просят написать обход глубокого дерева без переполнения стека.
Как отвечать
- синхронная рекурсия: каждый вызов кладёт в стек фрейм с аргументами, локальными переменными и адресом возврата. Стек небольшой и фиксированный, в V8 это порядка десяти тысяч вызовов в зависимости от размера фрейма, дальше
RangeError: Maximum call stack size exceeded. Хвостовая оптимизация есть в спецификации, но реализована только в Safari, в Chrome и Node на неё рассчитывать нельзя; - вызов через
setTimeout: текущая функция завершается, стек раскручивается, а колбэк позже стартует с пустого стека как новая макрозадача. Переполнения нет при любой глубине; - где теперь память: в куче. Таймер держит ссылку на колбэк, колбэк держит замыкание. Если каждый шаг захватывает данные предыдущего или копит результат в массиве, память растёт так же, просто лимит у кучи больше и ошибка приходит позже;
- цена по времени: после пяти вложенных таймеров браузер ставит минимальную задержку около 4 мс, а в фоновой вкладке таймеры тормозятся до секунды и сильнее. Десять тысяч шагов займут минимум десятки секунд.
Хороший ответ заканчивается правильными инструментами. Для глубокой структуры это обход с явным стеком или очередью в цикле while либо трамплин для хвостовой рекурсии. А setTimeout или scheduler.yield() решают другую задачу: дробят длинную работу, чтобы не блокировать отрисовку и ввод.
Где спотыкаются
Говорят, что setTimeout «освобождает память». Освобождается стек, а замыкания и накопленные данные живут в куче, пока на них есть ссылки.
Путают макро- и микрозадачи. Рекурсия через queueMicrotask или Promise.then тоже не растит стек, но не отдаёт управление браузеру: страница зависает до конца цепочки.
Приводят цифры вроде «переполнение после 1000 вызовов». Глубина зависит от движка и размера фрейма, и тысяча вызовов обычно проходит спокойно.