Что хранится на стеке
Что проверяют
Знаете ли вы, что происходит при вызове метода, и умеете ли связать это с тем, что видите каждый день: стектрейсом в отчёте о падении, переполнением на глубокой иерархии представлений, разницей между обычной и приостанавливаемой функцией.
Как отвечать
Стек принадлежит потоку, и на каждый вызов в него кладётся фрейм: локальные переменные, аргументы, адрес возврата и стек операндов, на котором виртуальная машина считает выражения. Возврат уничтожает фрейм целиком, поэтому за эту память сборщик не отвечает.
Хранятся там примитивы и ссылки. Сам объект лежит в куче, которой управляет сборщик, и ссылка из фрейма как раз и делает объект достижимым. Отсюда следствие для Kotlin: Int в локальной переменной это значение во фрейме, а Int? уже объект в куче, потому что обнуляемый тип требует упаковки.
Дальше стоит сказать про размер. Стек ограничен, его размер задаётся при создании потока, и главный поток здесь не бесконечен. Глубокая рекурсия, обход вложенной разметки, сериализация циклического графа объектов дают StackOverflowError, и стектрейс в таком отчёте показывает повторяющийся участок.
Сильный ответ добавляет корутины. Приостанавливаемая функция не может держать фрейм, пока ждёт: её локальное состояние компилятор переносит в объект продолжения в куче, а стек освобождается. Поэтому тысяча ожидающих корутин стоит дёшево, а тысяча ожидающих потоков стоит тысячи стеков.
Где спотыкаются
Говорят, что доступ к куче медленнее из-за фрагментации. Дело не в ней: стек горячий в кеше процессора и не требует работы сборщика, а объекты в куче разбросаны, и цена это промахи кеша плюс паузы сборки.
Считают размер стека константой платформы. Он задаётся при создании потока и у главного отличается от фоновых, поэтому совет про уменьшение глубины рекурсии иногда заменяется созданием потока с большим стеком.
Путают стек вызовов со стеком экранов и задач в системе навигации. Названия похожи, сущности разные, и вопрос про память к переходам между экранами отношения не имеет.