Содержание
Вопросы на Middle Go повторяются от компании к компании. Меняются формулировки, но проверяют одно: понимаешь ли ты, что происходит под капотом, и можешь ли объяснить последствия для продакшена.
Ниже 15 вопросов, которые встречаются чаще всего. К каждому — короткий ответ, который можно развернуть на собесе.
Типы данных
1. Как устроен слайс и что будет при append внутри функции?
Слайс — это структура из трёх полей: указатель на массив, длина и ёмкость. В функцию она копируется целиком. Если ёмкости хватает, append пишет в тот же массив, и вызывающий код увидит изменённые элементы, но не новую длину. Если не хватает, выделяется новый массив, и связь с оригиналом теряется.
func add(s []int) { s = append(s, 4) }
s := make([]int, 3, 10)
add(s)
fmt.Println(len(s)) // 3: длина у вызывающего не изменилась
2. Почему порядок обхода map каждый раз разный?
Рантайм специально выбирает случайную стартовую позицию, чтобы никто не завязывался на порядок. Если он нужен, собери ключи в слайс и отсортируй: slices.Sorted(maps.Keys(m)).
3. Чем nil-интерфейс отличается от интерфейса с nil-указателем?
Интерфейс равен nil, только если пусты и тип, и значение. Вернёшь (*MyErr)(nil) как error — получишь непустой интерфейс, и проверка err != nil сработает.
func find() error {
var e *NotFound = nil
return e // err != nil будет true
}
4. Строка — это байты или руны?
Байты, причём неизменяемые. len(s) возвращает число байт, индекс s[i] возвращает байт, а range по строке идёт по рунам и декодирует UTF-8. Для кириллицы это разные числа.
Конкурентность
5. Что происходит с каналом в разных состояниях?
| Операция | nil-канал | Закрытый канал |
|---|---|---|
| Отправка | Блокируется навсегда | panic |
| Чтение | Блокируется навсегда | Нулевое значение, ok == false |
close | panic | panic |
6. Что выберет select, если готовы несколько веток?
Случайную, с равной вероятностью. Приоритета по порядку веток нет. Если он нужен, делают вложенный select или отдельную проверку перед основным.
7. Как найти утечку горутин?
Смотрим runtime.NumGoroutine() в метриках, снимаем профиль /debug/pprof/goroutine?debug=2 и ищем тысячи горутин с одинаковым стеком. В тестах помогает go.uber.org/goleak. Почти всегда причина — отправка или чтение канала без select на ctx.Done().
8. Mutex, RWMutex, atomic — когда что?
atomic — для одной переменной. Mutex — для инварианта из нескольких полей. RWMutex — только если чтений заметно больше записей и критическая секция не микроскопическая: он дороже обычного мьютекса.
9. Что будет при панике в горутине?
Упадёт весь процесс, даже если в main стоит recover. recover работает только в той горутине, где случилась паника, поэтому в долгоживущих воркерах его ставят в начале функции горутины.
Рантайм
10. Как устроен планировщик?
Модель GMP: G — горутина, M — поток ОС, P — логический процессор с локальной очередью горутин. Число P равно GOMAXPROCS. Свободный P ворует работу из чужих очередей. С Go 1.14 горутину можно вытеснить асинхронно, даже если она крутит цикл без вызовов функций.
11. Как работает сборщик мусора?
Конкурентный трёхцветный mark-and-sweep без перемещения объектов. Большая часть работы идёт параллельно с программой, короткие паузы нужны на старте и в конце разметки. Частоту сборок задаёт GOGC, а с Go 1.19 есть мягкий лимит памяти GOMEMLIMIT, который полезен в контейнерах.
12. Когда переменная уходит в кучу?
Когда компилятор не может доказать, что она не переживёт функцию: вернули указатель, сохранили в интерфейс, захватили в замыкание, размер неизвестен на этапе компиляции. Проверить: go build -gcflags=-m.
13. В каком порядке выполняются defer?
В обратном: последний отложенный — первым. Аргументы вычисляются в момент объявления defer, а не выполнения. Отложенная функция может изменить именованный возвращаемый результат, так часто оборачивают ошибки.
Продакшен
14. Зачем context и что в нём хранить?
Для отмены, дедлайнов и значений уровня запроса: trace id, данные аутентификации. Бизнес-параметры в context.WithValue не кладут: они прячут зависимости функции. Контекст передают первым аргументом и не хранят в структурах.
15. Сервис тормозит. С чего начнёшь?
С метрик и профиля, а не с гипотез. pprof по CPU и по памяти, go tool trace, если подозрение на планировщик или блокировки. Потом бенчмарк на узкое место через go test -bench и сравнение до и после с benchstat.

