Вопросы на собеседовании: Go-разработчик
100 реальных вопросов с образцовыми ответами и пояснениями для уровня Go-разработчик.
Смотреть пример резюме: Go-разработчик →Тренировка флешкарточками
Интервальное повторение · Hunter Pass
Вопросы
Bounded worker pool запускает фиксированное число воркеров, которые получают задачи из общего канала.
- Фиксированное число воркеров ограничивает параллелизм и потребление ресурсов независимо от количества задач.
- Производитель отправляет задачи и закрывает канал задач после последней из них, чтобы воркеры, читающие его через range, могли завершиться.
- Каждый воркер вызывает Done у WaitGroup при выходе, а координатор ожидает завершения всех воркеров.
- Если воркеры выдают результаты, координатор закрывает канал результатов только после того, как все воркеры закончили отправку.
Зачем это спрашивают: Интервьюер проверяет понимание ограниченного параллелизма, жизненного цикла каналов и согласованного завершения работы.
Fan-out распределяет работу из одного входного потока между несколькими конкурентными потребителями.
- Несколько горутин могут получать данные из одного канала без дополнительной синхронизации вокруг каждого чтения.
- Каждое отправленное значение получает ровно один успешно читающий получатель, поэтому воркеры делят поток, а не дублируют его.
- Число потребителей задает максимальный параллелизм этого этапа.
- Порядок завершения может отличаться от входного порядка, если его не восстановить явно.
Зачем это спрашивают: Интервьюер проверяет, отличает ли кандидат параллельное распределение работы от широковещательной передачи и понимает ли влияние на порядок.
Fan-in объединяет значения из нескольких входных каналов в один выходной канал.
- Распространенная реализация запускает по одной пересылающей горутине для каждого входного канала.
- Каждая пересылающая горутина читает свой вход через range и отправляет все значения в общий выход.
- WaitGroup отслеживает пересылающие горутины, чтобы согласовать их независимое завершение.
- Один координатор закрывает выход после завершения всех пересылающих горутин; ни одна отдельная горутина его не закрывает.
Зачем это спрашивают: Интервьюер проверяет понимание объединения каналов по схеме многие-к-одному и безопасного закрытия выхода.
Pipeline соединяет конкурентные этапы, каждый из которых получает значения, преобразует их и передает результаты дальше.
- Этапы предоставляют каналы с совместимыми типами элементов и направлениями, поэтому один этап может передавать данные следующему.
- С небуферизованными каналами медленный последующий этап сразу блокирует отправки предыдущего.
- Конечный буфер компенсирует ограниченное расхождение скоростей, но предыдущий этап все равно блокируется после заполнения буфера.
- Эта блокировка распространяется к источнику и создает backpressure без неограниченной очереди.
Зачем это спрашивают: Интервьюер проверяет понимание композиции pipeline и управления потоком через каналы.
Каждый этап pipeline должен учитывать общий сигнал отмены во время потенциально блокирующих операций.
- Нижестоящий потребитель отменяет context или закрывает общий канал done, если прекращает работу до полного чтения pipeline.
- Каждый этап через select ожидает либо отмену, либо завершение блокирующей операции с каналом, особенно отправки следующему этапу.
- При отмене этапы завершаются, закрывают принадлежащие им выходные каналы и освобождают другие ресурсы.
- Простого выхода из финального цикла чтения недостаточно, потому что вышестоящие горутины могут навсегда заблокироваться на отправке.
Зачем это спрашивают: Интервьюер проверяет, умеет ли кандидат завершить все горутины pipeline, когда обычное полное чтение данных не происходит.
Владение каналом означает соглашение, по которому один компонент управляет отправкой и жизненным циклом канала.
- Владелец обычно создает канал, выполняет или координирует все отправки и предоставляет потребителям канал только для чтения.
- Канал закрывает отправляющая сторона только тогда, когда новых значений больше никогда не будет.
- Получатели не должны закрывать канал, а сам канал нельзя закрывать больше одного раза.
- При нескольких отправителях отдельный координатор закрывает канал только после завершения всех отправителей.
Зачем это спрашивают: Интервьюер проверяет, умеет ли кандидат назначить ответственность за жизненный цикл канала и избежать паник при закрытии.
Направленные типы каналов ограничивают операции, которые код может выполнять с каналом.
- chan<- T разрешает отправку и закрытие, но запрещает чтение на этапе компиляции.
- <-chan T разрешает чтение, но запрещает отправку и закрытие на этапе компиляции.
- Двунаправленный chan T можно присвоить или передать как любой направленный тип, но неявно расширить ограниченное представление обратно нельзя.
- Эти типы в сигнатурах функций документируют намерение о владении и обеспечивают границы API.
Зачем это спрашивают: Интервьюер проверяет понимание проверяемых компилятором возможностей каналов и их роли в проектировании API.
Nil-канал никогда не готов к передаче данных ни в одном направлении.
- Прямая отправка в nil-канал или чтение из него блокируется навсегда.
- В select ветка с nil-каналом отключена и не может быть выбрана.
- Присваивание nil переменной канала позволяет динамически отключить ее ветку без изменения структуры select.
- Select, у которого отключены все коммуникационные ветки, блокируется навсегда, если в нем нет default.
Зачем это спрашивают: Интервьюер проверяет, может ли кандидат точно рассуждать о nil-каналах и динамическом поведении select.
Select выбирает одну операцию, которая может выполниться в момент вычисления оператора.
- Если готова ровно одна ветка, выбирается она.
- Если готовы несколько веток, одна выбирается равновероятно псевдослучайным образом без приоритета по порядку в исходном коде.
- Ветка default выполняется только тогда, когда ни одна коммуникационная ветка не готова немедленно, поэтому select становится неблокирующим.
- Повторение select с default в плотном цикле может создать активное ожидание и расходовать процессорное время.
Зачем это спрашивают: Интервьюер проверяет понимание готовности операций, допустимых предположений о справедливости и неблокирующего поведения select.
Емкость канала определяет, насколько тесно синхронизируются отправители и получатели.
- Небуферизованная отправка завершается только при участии получателя, создавая прямую передачу между горутинами.
- Буферизованная отправка может завершиться без немедленного получателя, пока в буфере есть свободное место.
- После заполнения буфера следующие отправки блокируются, поэтому емкость задает запас до появления backpressure.
- Буферизация меняет временные характеристики и пропускную способность, но не устраняет необходимость координировать жизненный цикл и отмену.
Зачем это спрашивают: Интервьюер проверяет понимание емкости канала как границы синхронизации, а не просто параметра производительности.
sync.Mutex предоставляет исключительный доступ к критической секции.
- Его нулевое значение является разблокированным мьютексом, Lock ждёт доступности мьютекса, а Unlock может вызвать другая горутина.
- Успешный Unlock синхронизируется с последующим успешным Lock, поэтому записи в критической секции видимы после этого Lock.
- Мьютекс не является реентерабельным: повторный Lock в той же горутине без промежуточного Unlock приводит к взаимной блокировке.
- Mutex нельзя копировать после первого использования, поскольку у копии отдельное состояние блокировки, которое больше не координирует весь доступ к защищённым данным.
Зачем это спрашивают: Интервьюер проверяет понимание идентичности мьютекса, парных блокировок, гарантий видимости и запрета копирования после начала использования.
sync.RWMutex допускает либо несколько читателей, либо одного эксклюзивного писателя.
- RLock может сосуществовать с другими блокировками чтения, а Lock ждёт всех читателей и исключает как читателей, так и писателей.
- Когда писатель уже ожидает, новые вызовы RLock блокируются, чтобы непрерывный поток читателей не вызвал голодание писателя.
- Переход от RLock к Lock и от Lock к RLock не поддерживается, и RWMutex нельзя использовать для рекурсивной блокировки чтения.
- Дополнительный учёт оправдан в основном при преобладании чтения и достаточно длинных критических секциях; иначе Mutex может быть проще и быстрее.
Зачем это спрашивают: RWMutex повышает конкурентность только тогда, когда параллельное чтение перевешивает дополнительные расходы и более строгие правила блокировки.
sync.WaitGroup ожидает завершения отслеживаемого набора задач.
- Вызывайте Add перед запуском каждой задачи, а в самой задаче вызывайте Done ровно один раз, обычно через defer.
- Wait блокируется, пока счётчик не станет равен нулю, а Add вызывает панику, если обновление делает счётчик отрицательным.
- Положительный Add при нулевом счётчике должен произойти до Wait; конкурентный вызов Add и Wait небезопасен, поскольку Wait может вернуться слишком рано.
- WaitGroup можно использовать повторно только после возврата предыдущего Wait, причём все Add нового цикла должны происходить после этого.
Зачем это спрашивают: Счётчик должен описывать всю незавершённую работу до того, как ожидающий сможет увидеть завершение.
Эти средства выполняют ленивую инициализацию не более одного раза и безопасно публикуют её завершённый результат.
- Once.Do вызывает функцию при первом обращении, блокирует конкурентных вызывающих до её завершения и больше её не вызывает.
- Если функция Once.Do паникует, этот вызов всё равно считается завершённым, поэтому последующие Do возвращаются без повторного запуска.
- OnceValue кэширует одно возвращаемое значение, а OnceValues два, и каждая обёртка при каждом вызове возвращает тот же кэшированный результат.
- Если инициализатор OnceValue или OnceValues паникует, каждый вызов обёртки паникует с тем же значением паники, а не молча возвращается.
Зачем это спрашивают: Обёртки значений сохраняют неудачную инициализацию для каждого вызывающего, тогда как Once.Do после первой паники подавляет последующий запуск.
Атомарные операции делают одну поддерживаемую операцию с памятью неделимой без объявления критической секции.
- Загрузки, сохранения, обмены, сложения и успешные операции compare-and-swap атомарны только для адресуемого значения и конкретной операции.
- Последовательность чтения, изменения и записи из отдельных атомарных вызовов не является одной атомарной транзакцией, если она не реализована как корректный цикл CAS.
- В Go атомарные операции ведут себя так, словно все они выполняются в едином последовательно согласованном порядке, что обеспечивает синхронизацию между горутинами.
- Используйте мьютекс, если инвариант охватывает несколько полей или шагов, а атомики для простых счётчиков, флагов или тщательно спроектированного неблокирующего состояния.
Зачем это спрашивают: Применение атомиков требует доказать корректность на точной границе состояния, защищаемой каждой атомарной операцией.
Производные контексты образуют дерево от переданного родительского контекста.
- Background и TODO являются ненулевыми корневыми контекстами, а WithCancel, WithDeadline и WithTimeout создают дочерние.
- Отмена родителя отменяет всех производных от него потомков.
- Отмена дочернего контекста не отменяет его родителя, соседние контексты или другие ветви дерева.
- Context безопасен для конкурентного использования и обычно явно передаётся первым параметром через границы вызовов.
Зачем это спрашивают: Древовидная модель позволяет одной операции остановить всю подчинённую работу, не затрагивая несвязанную работу.
Отмена контекста кооперативна: код должен сам наблюдать сигнал и прекращать свою работу.
- Done возвращает канал только для чтения, который закрывается при отмене контекста или истечении срока, и может быть nil у контекстов без возможности отмены.
- До закрытия Done метод Err возвращает nil, а после него возвращает context.Canceled или context.DeadlineExceeded.
- Блокирующийся код обычно выбирает через select между обычной операцией с каналом и ctx.Done(), чтобы не ждать бесконечно.
- После сигнала Done код должен быстро вернуться и освободить свои ресурсы, а не ожидать, что контекст сам завершит горутину.
Зачем это спрашивают: Done передаёт сигнал, Err указывает категорию, а остановка остаётся обязанностью принимающего кода.
WithDeadline и WithTimeout создают производный контекст, который будет отменён не позже заданного времени.
- WithDeadline использует абсолютное время, а WithTimeout вычисляет срок относительно текущего времени.
- Эффективный срок не может быть позже более раннего срока родителя.
- Возвращённую функцию cancel обычно следует сразу отложить через defer, даже если ожидается автоматическое истечение срока.
- Ранний вызов cancel освобождает таймер и связь производного контекста с родителем и не удерживает ресурсы до истечения срока.
Зачем это спрашивают: Срок ограничивает работу, а явная функция cancel обеспечивает детерминированное освобождение ресурсов.
Значения контекста предназначены для данных запроса, которые должны пересекать границы API и процессов.
- Ключи должны иметь закрытый сравнимый тип вместо встроенных строк, чтобы исключить коллизии между пакетами.
- Сохранённые значения должны быть безопасны для конкурентного доступа и обычно предоставляться через типизированные функции доступа.
- Value ищет в текущем контексте, затем в его предках и возвращает ближайшее значение для подходящего ключа или nil.
- Конфигурация и необязательные параметры функции должны передаваться явными аргументами или типами опций, поскольку значения контекста скрывают зависимости и ослабляют проверку типов.
Зачем это спрашивают: Ограничение значений сквозными метаданными запроса сохраняет API явными, типизированными и удобными для сопровождения.
WithCancelCause позволяет передать при отмене предметную ошибку в дополнение к стандартному состоянию контекста.
- Он возвращает CancelCauseFunc, которая записывает переданную ненулевую ошибку, если контекст ещё не отменён; передача nil записывает context.Canceled.
- Если первой срабатывает CancelCauseFunc, Cause возвращает записанную ошибку, а Err возвращает context.Canceled; более ранняя отмена родителя сохраняет его Err и причину.
- Причина распространяется на потомков, если потомок раньше не установил собственную причину отмены.
- До отмены Cause возвращает nil, а после отмены без явной причины возвращает ту же стандартную ошибку, что и Err.
Зачем это спрашивают: Причины отмены сохраняют диагностический смысл, не меняя стабильный контракт Context.Err.
Закрытые вопросы
- 21
Чем отличаются наборы методов T и *T и как это влияет на реализацию интерфейса?
typesinterfaces - 22
Что такое interface{} и any в Go?
typesinterfaces - 23
Как работают утверждения конкретного и интерфейсного типа, включая форму comma-ok?
typesinterfacesforms - 24
Какова семантика переключателя типов в Go?
- 25
Как встраивание интерфейсов обеспечивает их композицию в Go?
typesoopinterfaces - 26
Что происходит, если встроенные интерфейсы содержат методы с одинаковыми именами, и как объединяются их требования к множествам типов?
typesinterfaces - 27
Почему интерфейсы в Go обычно определяют потребители и делают их небольшими?
typesinterfaces - 28
Чем nil-интерфейс отличается от интерфейса, который хранит типизированное nil-значение?
typesinterfaces - 29
Как обобщенные функции и типы используют параметры типов и инстанцирование в Go?
generics - 30
Как интерфейс выступает ограничением обобщения и описывает допустимое множество типов?
genericstypesinterfaces - 31
Чем ограничения any и comparable отличаются в дженериках Go?
generics - 32
Что означает элемент вида ~T в ограничении Go?
- 33
Как термы объединения работают в ограничениях Go и какие правила на них наложены?
uniontypes - 34
Как в Go выводятся аргументы типов обобщенной функции и когда их все еще нужно указывать явно?
generics - 35
Когда в Go следует предпочесть дженерики интерфейсам, а когда интерфейсы являются лучшей абстракцией?
genericstypesoop - 36
Что означают happens-before и гонка данных в модели памяти Go?
memory - 37
Какие операции с каналами создают связи синхронизации в модели памяти Go?
memoryconcurrency - 38
Какие гарантии синхронизации дают Mutex, RWMutex и Once в модели памяти Go?
concurrencymemory - 39
Как трассирующий сборщик мусора Go определяет, что можно освободить, включая циклические данные?
gc - 40
Какую роль играют GOGC и мягкий лимит памяти GOMEMLIMIT в сборщике мусора Go?
gcmemory - 41
Как связаны заголовок слайса, его базовый массив, совместное использование данных и перераспределение при append?
slices-maps - 42
Что Go-код может предполагать о росте ёмкости слайса и памяти, которую удерживает маленький подслайс?
capacitymemoryslices-maps - 43
Какие концептуальные свойства map в Go следуют из устройства хеш-таблицы?
design - 44
Какие конкурентные обращения к обычной map в Go допустимы и когда нужна синхронизация?
concurrency - 45
Как следует определять и проверять ошибки-сентинелы с помощью errors.Is?
error-handling - 46
Как errors.As работает с собственными типами ошибок, особенно когда различаются формы значения и указателя?
error-handling - 47
Какую семантику цепочке или дереву ошибок задают fmt.Errorf с %w и errors.Join?
joins - 48
Почему в Go табличные тесты часто сочетают с подтестами?
testing - 49
Какие правила жизненного цикла важны для t.Helper, t.Cleanup, t.Parallel и переменных цикла в современных тестах Go?
testing - 50
Как в бенчмарке Go использовать b.N или b.Loop, управление таймером и отчёт об аллокациях?
- 51
Go-сервис перестал отвечать, но загрузка CPU остаётся низкой. Как проверить возможный deadlock?
locking - 52
Race detector сообщил о двух конфликтующих обращениях в интеграционном тесте. Как превратить отчёт в исправление?
integration - 53
Количество горутин растёт после каждого неудачного запроса к зависимому сервису. Как найти и исправить утечку?
concurrency - 54
Как построить конкурентный pipeline, в котором первая ошибка worker отменяет оставшуюся работу?
ci-cdconcurrency - 55
Handler удерживает mutex во время медленного запроса к базе данных, вызывая скачки задержки. Как изменить дизайн?
databasequeriesconcurrency - 56
Два метода захватывают lock счёта и журнала в разном порядке и иногда создают deadlock. Как исправить дизайн?
lockingdesign - 57
Несколько producers пишут в один канал, а shutdown иногда вызывает panic send on closed channel. Как организовать владение?
concurrencyerror-handlingownership - 58
Пакетная задача запускает горутину для каждой записи и исчерпывает память на больших данных. Как ограничить конкурентность?
concurrencybatchmemory - 59
WaitGroup иногда паникует из-за misuse при динамическом обходе задач. Как безопасно организовать обход?
concurrencystructserror-handling - 60
Цикл select обрабатывает постоянно готовый канал данных и медленно реагирует на отмену. Как ускорить shutdown?
reactresponsiveconcurrency - 61
Конфигурационная map часто читается handlers и периодически перезагружается. Как сделать доступ безопасным и быстрым?
config - 62
Функция инициализации использует sync.Once, но после временного сбоя больше не может повториться. Как её изменить?
resilienceconcurrency - 63
Кэш использует sync.Map для всех данных, но работает медленнее ожидаемого. Как решить, стоит ли его заменить?
cachingconcurrency - 64
Go-сервис внезапно стал использовать вдвое больше CPU. Как исследовать проблему через pprof?
profiling - 65
Heap продолжает расти в долгоживущем сервисе. Как отличить удерживаемую память от временного потока аллокаций?
memorydata-structures - 66
JSON-эндпоинт создаёт много аллокаций. Как измерить и сократить их без догадок?
endpoints - 67
Как использовать escape analysis, если горячая функция неожиданно выделяет память?
memory - 68
Парсер постоянно преобразует string в []byte и обратно. Как решить, нужно ли это оптимизировать?
optimization - 69
Когда стоит заранее выделять slice или map в production-коде и как проверить пользу?
slices-maps - 70
pprof показывает высокий вклад fmt.Sprintf в горячем пути логирования. Как его оптимизировать?
loggingprofilingoptimization - 71
Задержка растёт вместе с трафиком, а mutex-профиль указывает на один общий lock. Что делать дальше?
concurrencylatency - 72
Запросы проводят время в блокировке, хотя CPU и конкуренция за mutex низкие. Чем поможет block profile?
concurrency - 73
CPU- и heap-профили выглядят нормально, но у запросов бывают периодические скачки задержки. Как применить go tool trace?
latencydata-structures - 74
Микробенчмарк показывает ускорение на 30 процентов, но production-задержка не меняется. Как переоценить оптимизацию?
optimizationlatency - 75
Стоит ли добавлять sync.Pool для сокращения аллокаций буферов в нагруженном сервисе?
memoryconcurrency - 76
Как реализовать graceful shutdown HTTP-сервиса в Kubernetes?
lifecycleserviceshttp - 77
Как выбрать порядок HTTP middleware для recovery, tracing, аутентификации и логирования?
authhttpmiddleware - 78
Как передать context через HTTP handler, который обращается к базе и другому сервису?
databasehttpconcurrency - 79
Что хранить в context, а что оставлять явными параметрами функций?
concurrency - 80
Как настроить таймауты публичного net/http сервера?
resiliencehttpconfig - 81
Как реализовать общую аутентификацию и observability для всех gRPC-методов?
authgrpcobservability - 82
Как спроектировать liveness и readiness checks для Go-микросервиса?
microservicesdesignhealth-checks - 83
Как добавить structured logging и tracing, не потеряв связь событий одного запроса?
correlationloggingstructs - 84
Как добавить retry в исходящий Go client и не вызвать retry storm?
resilience - 85
Как добавить circuit breaker для нестабильного зависимого сервиса?
resilience - 86
Как гарантировать остановку database transaction при отмене запроса?
databasetransactions - 87
Как остановить Kafka consumer, не теряя владение и не обрабатывая сообщения повторно без необходимости?
ownershipkafkaconcurrency - 88
Как добавить контекст в ошибку и сохранить работу проверок errors.Is?
concurrencyerror-handling - 89
Когда использовать собственный тип ошибки и errors.As в сервисе?
error-handling - 90
Как переводить domain errors в HTTP- или gRPC-ответы без связи domain с transport-кодом?
httpgrpcmicroservices - 91
Функция может завершиться ошибкой основной операции и второй ошибкой при закрытии ресурса. Как сохранить обе?
- 92
Пакетная операция успешна для части элементов и неуспешна для других. Как спроектировать её результат в Go?
designbatch - 93
Где восстанавливаться после panic в Go-сервисе и что делать после recover?
error-handling - 94
Как обрабатывать ошибки отмены context иначе, чем обычные сбои сервиса?
resilienceconcurrency - 95
Как организовать table-driven tests для парсера с валидными и невалидными входами?
structs - 96
Как замокать платёжную зависимость в Go без создания огромного interface?
typesinterfacesmocking - 97
Как протестировать HTTP handler вместе с middleware и mapping ошибок?
middlewarehttp - 98
Как написать детерминированный тест конкурентного бага вместо добавления time.Sleep?
concurrency - 99
Тест поведения context timeout нестабилен в CI. Как сделать его надёжным?
resilienceflakyconcurrency - 100
Как спроектировать стратегию тестирования Go-сервиса с PostgreSQL и внешним gRPC API?
designmicroservicesapi