Подготовка к собеседованию — Go-разработчик
Колода из 333+ карточек с вопросами для собеседования по направлению «Go-разработчик» — по темам и уровню сложности, с возвратом ровно перед тем, как вы забудете. Посмотрите несколько карточек ниже, затем выберите доступ, чтобы учить всё направление по расписанию в стиле Anki (SM-2).
7 дней бесплатно на месяц или год · все функции включены.
Что внутри
Все темы направления, сгруппированные так, как вы будете их учить.
Основы языка: слайсы, мапы и строки
15 карточекИнтерфейсы и система типов
6 карточекГорутины, каналы и select
12 карточекsync, atomic и context
12 карточекПланировщик, GC и память
8 карточекОшибки, defer и panic
7 карточекПаттерны конкурентности (live-coding)
8 карточекТестирование, тулинг и дженерики
7 карточекРаспределённые системы
12 карточекKafka и очереди
12 карточекКэширование вглубь
10 карточекПроизводительность и конкурентность
11 карточекНадёжность и инциденты
10 карточекБазы данных
104 карточкиDevOps и инфраструктура
35 карточекСистемный дизайн
29 карточекПоведенческое интервью
35 карточекПримеры вопросов
Несколько карточек из колоды — откройте ответ, затем выберите доступ, чтобы учить весь набор по расписанию.
Чем слайс отличается от массива и как слайс устроен под капотом?
Чем слайс отличается от массива и как слайс устроен под капотом?
Короткий ответ: Массив — это value-тип фиксированного размера, причём размер зашит в сам тип ([3]int и [4]int — разные типы), и он копируется целиком при присваивании и передаче. Слайс — это лёгкий дескриптор поверх массива: заголовок из трёх полей {указатель на backing array, len, cap}. Копируется только заголовок, данные остаются общими.
Подробно:
- Массив —
[N]T, длина — часть типа, данные лежат единым непрерывным блоком прямо по месту объявления (в стеке, структуре или куче) и копируются побайтово. Передал в функцию — получил копию всех элементов. - Слайс —
[]T, три машинных слова:ptrна элемент backing array,len(сколько видно) иcap(сколько влезет до реаллокации). - Срез —
arr[1:3]не копирует данные, а строит новый заголовок, указывающий внутрь того же массива.
s := arr[1:3]
slice header backing array [5]int
┌──────────┐ ┌───┬───┬───┬───┬───┐
│ ptr ────┼────────► │ 0 │ 1 │ 2 │ 3 │ 4 │
│ len = 2 │ ▲ ▲
│ cap = 4 │ └─ ptr └─ len
└──────────┘
⚠️ Частая ошибка: думать, что s2 := s1 даёт независимую копию. Копируется только 24-байтовый заголовок — оба слайса смотрят в один и тот же backing array, и запись s2[0] = x видна через s1.
Как в Go реализован полиморфизм без классов и наследования?
Как в Go реализован полиморфизм без классов и наследования?
Короткий ответ: Полиморфизм даёт неявная (структурная) реализация интерфейсов: тип удовлетворяет интерфейсу автоматически, если у него есть нужный набор методов — никакого implements. Вместо наследования — композиция через встраивание (embedding), а инкапсуляция — регистром первой буквы имени.
Подробно:
- Структурная типизация (duck typing) — «если крякает как утка». Тип и интерфейс нигде явно не связывают; связь проверяется по сигнатурам методов на этапе компиляции.
- Композиция вместо наследования — встраивание продвигает методы встроенного типа наружу, но это
has-a, а неis-a. - Инкапсуляция — имя с заглавной буквы экспортируется, со строчной — приватно внутри пакета.
type Stringer interface{ String() string }
type Point struct{ X, Y int }
func (p Point) String() string { // Point реализует Stringer
return fmt.Sprintf("(%d,%d)", p.X, p.Y)
} // без "implements Stringer" — достаточно совпадения метода
⚠️ Частая ошибка: называть embedding «наследованием». Здесь нет подтипов и виртуальных вызовов: внешний тип не приводится к встроенному, а метод встроенного типа не «переопределяется» и не видит полей внешнего.
Чем горутина отличается от потока ОС?
Чем горутина отличается от потока ОС?
Короткий ответ: Горутина — это лёгкая единица выполнения, которой управляет рантайм Go, а не ядро ОС. Её стек стартует с ~2 КБ и растёт динамически, тогда как поток ОС резервирует мегабайты фиксированного стека. Рантайм мультиплексирует тысячи горутин на небольшое число потоков по модели M:N, поэтому в одном процессе спокойно живут сотни тысяч горутин.
Подробно:
- Стек — горутина: ~2 КБ с ростом/сжатием по мере надобности; поток ОС: фиксированные 1–8 МБ, зарезервированные заранее.
- Планировщик — горутины планирует рантайм Go (модель GMP: G — горутина, M — поток ОС, P — процессор-контекст) в user space; потоки планирует ядро.
- Стоимость переключения — переключение горутины не заходит в ядро и на порядки дешевле переключения контекста потока.
- Масштаб — потоков реалистично тысячи, горутин — сотни тысяч и больше.
| Критерий | Горутина | Поток ОС |
|---|---|---|
| Стек | ~2 КБ, растёт | 1–8 МБ, фиксирован |
| Кто планирует | рантайм Go (M:N) | ядро ОС |
| Переключение | user space, дёшево | системный вызов, дорого |
| Сколько на процесс | сотни тысяч | тысячи |
⚠️ Частая ошибка: ответить «это лёгкий поток» и остановиться. Без упоминания планировщика рантайма и модели M:N ответ звучит по-джуновски — интервьюер ждёт именно механику.
В чём разница между sync.Mutex и sync.RWMutex и когда RWMutex реально оправдан?
В чём разница между sync.Mutex и sync.RWMutex и когда RWMutex реально оправдан?
Короткий ответ: Mutex даёт эксклюзивный доступ — одна горутина в критической секции в любой момент. RWMutex разделяет блокировку на две: под RLock() читатели заходят параллельно, а Lock() писателя эксклюзивен и ждёт, пока все читатели выйдут. RWMutex оправдан только там, где чтений заметно больше, чем записей.
Подробно:
- Mutex — простой замок:
Lock()/Unlock(), всегда один владелец. Дефолтный выбор. - RWMutex —
RLock()для читателей (можно много одновременно),Lock()для писателя (один, эксклюзивно). - Когда переходить на RWMutex — только если профилирование показало contention на чтениях и соотношение read:write велико (условно 10:1 и выше), а критическая секция не микроскопическая.
| Критерий | sync.Mutex | sync.RWMutex |
|---|---|---|
| Читатели | по одному | параллельно |
| Писатель | эксклюзивно | эксклюзивно |
| Накладные расходы | ниже | выше (сложнее внутри) |
| Когда | по умолчанию | чтений >> записей |
⚠️ Частая ошибка: ставить RWMutex «на всякий случай» и пугать голоданием писателей. В Go его нет: RWMutex write-preferring — ожидающий Lock() блокирует новых читателей (побочный эффект: рекурсивный RLock() в одной горутине может дать дедлок). Реальная цена — более дорогой захват: на коротких секциях обычный Mutex часто быстрее.
Что задаёт GOMAXPROCS и чему он равен по умолчанию?
Что задаёт GOMAXPROCS и чему он равен по умолчанию?
Короткий ответ: GOMAXPROCS задаёт число P — максимум потоков ОС, одновременно исполняющих Go-код. По умолчанию он равен числу логических CPU (runtime.NumCPU()) — так с Go 1.5.
Подробно:
- Что именно ограничивает. Только параллельное исполнение Go-кода. Горутины, заблокированные на syscall или сетевом I/O, в лимит не входят — потоков в процессе может быть куда больше, чем
GOMAXPROCS. - Как менять. Через переменную окружения
GOMAXPROCSили в рантаймеruntime.GOMAXPROCS(n). - Ловушка контейнеров. Исторически дефолт брал число ядер всей ноды, игнорируя CPU-лимит cgroup: на 64-ядерной ноде с лимитом «2 CPU» рантайм поднимал 64 P — лишние переключения контекста и троттлинг. Классическое лечение —
uber-go/automaxprocs. - Свежий Go. С Go 1.25 рантайм по умолчанию учитывает CPU-лимит cgroup — на Linux дефолт округляется вверх от лимита, поэтому проверяйте версию, прежде чем тащить костыль.
// GOMAXPROCS=4 ./app — через переменную окружения
runtime.GOMAXPROCS(4) // или в рантайме
n := runtime.GOMAXPROCS(0) // 0 — прочитать текущее значение, не меняя
⚠️ Частая ошибка: на Go < 1.25 положиться на дефолт в Kubernetes с CPU-лимитом. Без automaxprocs рантайм видит все ядра ноды, а не выделенную квоту.
Как обернуть ошибку, добавив контекст, но сохранив цепочку для errors.Is/As?
Как обернуть ошибку, добавив контекст, но сохранив цепочку для errors.Is/As?
Короткий ответ: Через fmt.Errorf с глаголом %w: fmt.Errorf("read config: %w", err). Именно %w вшивает исходную ошибку в цепочку Unwrap, так что errors.Is/errors.As продолжают её видеть. Глаголы %v и %s вставляют только текст и обрывают цепочку.
Подробно:
%wсохраняет цепочку — оборачивает ошибку, оставляя доступ к оригиналу черезUnwrap. Добавляй короткий контекст: что делал код, а не пересказ ошибки.%v/%sрвут цепочку — годятся только когда исходную ошибку намеренно скрываешь от вызывающего (граница абстракции).- Контекст по слоям — на каждом уровне добавляй свой префикс; получается читаемый след
open user file: read config: permission denied.
if err != nil {
return fmt.Errorf("read config %q: %w", path, err)
}
// errors.Is(err, os.ErrPermission) по-прежнему работает
⚠️ Частая ошибка: обрабатывать ошибку дважды — и залогировать, и вернуть её наверх. Каждый слой либо оборачивает и возвращает, либо (на самом верху) логирует — не одновременно, иначе логи задваиваются.
Готовы закрепить навсегда?
Первая сессия — меньше минуты. Ваше будущее «я» на собеседовании скажет спасибо.
Вопросы об этом направлении
Как готовиться к собеседованию на «Go-разработчик»?
Учите концепции, которые придётся объяснять, а не только те, что умеете кодить. Направление «Go-разработчик» в RecallDeck даёт 333+ отобранных вопросов и возвращает каждый по расписанию в стиле Anki (SM-2) ровно перед тем, как вы забудете — чтобы на собеседовании ответы были под рукой.
Какие темы охватывает направление «Go-разработчик»?
Направление «Go-разработчик» разбито на ключевые области, которые реально проверяют на таких собеседованиях, — по темам и уровню сложности (Concept, Junior, Middle, Senior). Полный план и примеры вопросов можно посмотреть выше до входа.
Помогает ли интервальное повторение в подготовке к «Go-разработчик»?
Да. Активно вспоминать ответ и честно себя оценивать — куда прочнее для памяти, чем перечитывать заметки. RecallDeck планирует каждую карту «Go-разработчик» так, чтобы она вернулась перед моментом забывания: ежедневных повторений становится меньше, а знания держатся.
Можно попробовать направление «Go-разработчик» до оплаты?
Да. Месячный и годовой варианты включают семь пробных дней со всем направлением «Go-разработчик», полным планировщиком SM-2, статистикой, гибким темпом и блиц-режимом. Отменить можно онлайн до первого списания.