Skip to content

Вопросы на собеседовании: Go-разработчик

100 реальных вопросов с образцовыми ответами и пояснениями для уровня Go-разработчик.

Смотреть пример резюме: Go-разработчик

Тренировка флешкарточками

Интервальное повторение · Hunter Pass

Вопросы

structs

Bounded worker pool запускает фиксированное число воркеров, которые получают задачи из общего канала.

  • Фиксированное число воркеров ограничивает параллелизм и потребление ресурсов независимо от количества задач.
  • Производитель отправляет задачи и закрывает канал задач после последней из них, чтобы воркеры, читающие его через range, могли завершиться.
  • Каждый воркер вызывает Done у WaitGroup при выходе, а координатор ожидает завершения всех воркеров.
  • Если воркеры выдают результаты, координатор закрывает канал результатов только после того, как все воркеры закончили отправку.

Зачем это спрашивают: Интервьюер проверяет понимание ограниченного параллелизма, жизненного цикла каналов и согласованного завершения работы.

fan-outconcurrency

Fan-out распределяет работу из одного входного потока между несколькими конкурентными потребителями.

  • Несколько горутин могут получать данные из одного канала без дополнительной синхронизации вокруг каждого чтения.
  • Каждое отправленное значение получает ровно один успешно читающий получатель, поэтому воркеры делят поток, а не дублируют его.
  • Число потребителей задает максимальный параллелизм этого этапа.
  • Порядок завершения может отличаться от входного порядка, если его не восстановить явно.

Зачем это спрашивают: Интервьюер проверяет, отличает ли кандидат параллельное распределение работы от широковещательной передачи и понимает ли влияние на порядок.

concurrency

Fan-in объединяет значения из нескольких входных каналов в один выходной канал.

  • Распространенная реализация запускает по одной пересылающей горутине для каждого входного канала.
  • Каждая пересылающая горутина читает свой вход через range и отправляет все значения в общий выход.
  • WaitGroup отслеживает пересылающие горутины, чтобы согласовать их независимое завершение.
  • Один координатор закрывает выход после завершения всех пересылающих горутин; ни одна отдельная горутина его не закрывает.

Зачем это спрашивают: Интервьюер проверяет понимание объединения каналов по схеме многие-к-одному и безопасного закрытия выхода.

concurrencybackpressureci-cd

Pipeline соединяет конкурентные этапы, каждый из которых получает значения, преобразует их и передает результаты дальше.

  • Этапы предоставляют каналы с совместимыми типами элементов и направлениями, поэтому один этап может передавать данные следующему.
  • С небуферизованными каналами медленный последующий этап сразу блокирует отправки предыдущего.
  • Конечный буфер компенсирует ограниченное расхождение скоростей, но предыдущий этап все равно блокируется после заполнения буфера.
  • Эта блокировка распространяется к источнику и создает backpressure без неограниченной очереди.

Зачем это спрашивают: Интервьюер проверяет понимание композиции pipeline и управления потоком через каналы.

concurrencyresilienceci-cd

Каждый этап pipeline должен учитывать общий сигнал отмены во время потенциально блокирующих операций.

  • Нижестоящий потребитель отменяет context или закрывает общий канал done, если прекращает работу до полного чтения pipeline.
  • Каждый этап через select ожидает либо отмену, либо завершение блокирующей операции с каналом, особенно отправки следующему этапу.
  • При отмене этапы завершаются, закрывают принадлежащие им выходные каналы и освобождают другие ресурсы.
  • Простого выхода из финального цикла чтения недостаточно, потому что вышестоящие горутины могут навсегда заблокироваться на отправке.

Зачем это спрашивают: Интервьюер проверяет, умеет ли кандидат завершить все горутины pipeline, когда обычное полное чтение данных не происходит.

ownershipconcurrency

Владение каналом означает соглашение, по которому один компонент управляет отправкой и жизненным циклом канала.

  • Владелец обычно создает канал, выполняет или координирует все отправки и предоставляет потребителям канал только для чтения.
  • Канал закрывает отправляющая сторона только тогда, когда новых значений больше никогда не будет.
  • Получатели не должны закрывать канал, а сам канал нельзя закрывать больше одного раза.
  • При нескольких отправителях отдельный координатор закрывает канал только после завершения всех отправителей.

Зачем это спрашивают: Интервьюер проверяет, умеет ли кандидат назначить ответственность за жизненный цикл канала и избежать паник при закрытии.

concurrency

Направленные типы каналов ограничивают операции, которые код может выполнять с каналом.

  • chan<- T разрешает отправку и закрытие, но запрещает чтение на этапе компиляции.
  • <-chan T разрешает чтение, но запрещает отправку и закрытие на этапе компиляции.
  • Двунаправленный chan T можно присвоить или передать как любой направленный тип, но неявно расширить ограниченное представление обратно нельзя.
  • Эти типы в сигнатурах функций документируют намерение о владении и обеспечивают границы API.

Зачем это спрашивают: Интервьюер проверяет понимание проверяемых компилятором возможностей каналов и их роли в проектировании API.

concurrency

Nil-канал никогда не готов к передаче данных ни в одном направлении.

  • Прямая отправка в nil-канал или чтение из него блокируется навсегда.
  • В select ветка с nil-каналом отключена и не может быть выбрана.
  • Присваивание nil переменной канала позволяет динамически отключить ее ветку без изменения структуры select.
  • Select, у которого отключены все коммуникационные ветки, блокируется навсегда, если в нем нет default.

Зачем это спрашивают: Интервьюер проверяет, может ли кандидат точно рассуждать о nil-каналах и динамическом поведении select.

Select выбирает одну операцию, которая может выполниться в момент вычисления оператора.

  • Если готова ровно одна ветка, выбирается она.
  • Если готовы несколько веток, одна выбирается равновероятно псевдослучайным образом без приоритета по порядку в исходном коде.
  • Ветка default выполняется только тогда, когда ни одна коммуникационная ветка не готова немедленно, поэтому select становится неблокирующим.
  • Повторение select с default в плотном цикле может создать активное ожидание и расходовать процессорное время.

Зачем это спрашивают: Интервьюер проверяет понимание готовности операций, допустимых предположений о справедливости и неблокирующего поведения select.

concurrencybackpressure

Емкость канала определяет, насколько тесно синхронизируются отправители и получатели.

  • Небуферизованная отправка завершается только при участии получателя, создавая прямую передачу между горутинами.
  • Буферизованная отправка может завершиться без немедленного получателя, пока в буфере есть свободное место.
  • После заполнения буфера следующие отправки блокируются, поэтому емкость задает запас до появления backpressure.
  • Буферизация меняет временные характеристики и пропускную способность, но не устраняет необходимость координировать жизненный цикл и отмену.

Зачем это спрашивают: Интервьюер проверяет понимание емкости канала как границы синхронизации, а не просто параметра производительности.

concurrency

sync.Mutex предоставляет исключительный доступ к критической секции.

  • Его нулевое значение является разблокированным мьютексом, Lock ждёт доступности мьютекса, а Unlock может вызвать другая горутина.
  • Успешный Unlock синхронизируется с последующим успешным Lock, поэтому записи в критической секции видимы после этого Lock.
  • Мьютекс не является реентерабельным: повторный Lock в той же горутине без промежуточного Unlock приводит к взаимной блокировке.
  • Mutex нельзя копировать после первого использования, поскольку у копии отдельное состояние блокировки, которое больше не координирует весь доступ к защищённым данным.

Зачем это спрашивают: Интервьюер проверяет понимание идентичности мьютекса, парных блокировок, гарантий видимости и запрета копирования после начала использования.

concurrency

sync.RWMutex допускает либо несколько читателей, либо одного эксклюзивного писателя.

  • RLock может сосуществовать с другими блокировками чтения, а Lock ждёт всех читателей и исключает как читателей, так и писателей.
  • Когда писатель уже ожидает, новые вызовы RLock блокируются, чтобы непрерывный поток читателей не вызвал голодание писателя.
  • Переход от RLock к Lock и от Lock к RLock не поддерживается, и RWMutex нельзя использовать для рекурсивной блокировки чтения.
  • Дополнительный учёт оправдан в основном при преобладании чтения и достаточно длинных критических секциях; иначе Mutex может быть проще и быстрее.

Зачем это спрашивают: RWMutex повышает конкурентность только тогда, когда параллельное чтение перевешивает дополнительные расходы и более строгие правила блокировки.

concurrency

sync.WaitGroup ожидает завершения отслеживаемого набора задач.

  • Вызывайте Add перед запуском каждой задачи, а в самой задаче вызывайте Done ровно один раз, обычно через defer.
  • Wait блокируется, пока счётчик не станет равен нулю, а Add вызывает панику, если обновление делает счётчик отрицательным.
  • Положительный Add при нулевом счётчике должен произойти до Wait; конкурентный вызов Add и Wait небезопасен, поскольку Wait может вернуться слишком рано.
  • WaitGroup можно использовать повторно только после возврата предыдущего Wait, причём все Add нового цикла должны происходить после этого.

Зачем это спрашивают: Счётчик должен описывать всю незавершённую работу до того, как ожидающий сможет увидеть завершение.

concurrencyerror-handling

Эти средства выполняют ленивую инициализацию не более одного раза и безопасно публикуют её завершённый результат.

  • Once.Do вызывает функцию при первом обращении, блокирует конкурентных вызывающих до её завершения и больше её не вызывает.
  • Если функция Once.Do паникует, этот вызов всё равно считается завершённым, поэтому последующие Do возвращаются без повторного запуска.
  • OnceValue кэширует одно возвращаемое значение, а OnceValues два, и каждая обёртка при каждом вызове возвращает тот же кэшированный результат.
  • Если инициализатор OnceValue или OnceValues паникует, каждый вызов обёртки паникует с тем же значением паники, а не молча возвращается.

Зачем это спрашивают: Обёртки значений сохраняют неудачную инициализацию для каждого вызывающего, тогда как Once.Do после первой паники подавляет последующий запуск.

concurrency

Атомарные операции делают одну поддерживаемую операцию с памятью неделимой без объявления критической секции.

  • Загрузки, сохранения, обмены, сложения и успешные операции compare-and-swap атомарны только для адресуемого значения и конкретной операции.
  • Последовательность чтения, изменения и записи из отдельных атомарных вызовов не является одной атомарной транзакцией, если она не реализована как корректный цикл CAS.
  • В Go атомарные операции ведут себя так, словно все они выполняются в едином последовательно согласованном порядке, что обеспечивает синхронизацию между горутинами.
  • Используйте мьютекс, если инвариант охватывает несколько полей или шагов, а атомики для простых счётчиков, флагов или тщательно спроектированного неблокирующего состояния.

Зачем это спрашивают: Применение атомиков требует доказать корректность на точной границе состояния, защищаемой каждой атомарной операцией.

resilienceconcurrency

Производные контексты образуют дерево от переданного родительского контекста.

  • Background и TODO являются ненулевыми корневыми контекстами, а WithCancel, WithDeadline и WithTimeout создают дочерние.
  • Отмена родителя отменяет всех производных от него потомков.
  • Отмена дочернего контекста не отменяет его родителя, соседние контексты или другие ветви дерева.
  • Context безопасен для конкурентного использования и обычно явно передаётся первым параметром через границы вызовов.

Зачем это спрашивают: Древовидная модель позволяет одной операции остановить всю подчинённую работу, не затрагивая несвязанную работу.

concurrencyresilience

Отмена контекста кооперативна: код должен сам наблюдать сигнал и прекращать свою работу.

  • Done возвращает канал только для чтения, который закрывается при отмене контекста или истечении срока, и может быть nil у контекстов без возможности отмены.
  • До закрытия Done метод Err возвращает nil, а после него возвращает context.Canceled или context.DeadlineExceeded.
  • Блокирующийся код обычно выбирает через select между обычной операцией с каналом и ctx.Done(), чтобы не ждать бесконечно.
  • После сигнала Done код должен быстро вернуться и освободить свои ресурсы, а не ожидать, что контекст сам завершит горутину.

Зачем это спрашивают: Done передаёт сигнал, Err указывает категорию, а остановка остаётся обязанностью принимающего кода.

concurrency

WithDeadline и WithTimeout создают производный контекст, который будет отменён не позже заданного времени.

  • WithDeadline использует абсолютное время, а WithTimeout вычисляет срок относительно текущего времени.
  • Эффективный срок не может быть позже более раннего срока родителя.
  • Возвращённую функцию cancel обычно следует сразу отложить через defer, даже если ожидается автоматическое истечение срока.
  • Ранний вызов cancel освобождает таймер и связь производного контекста с родителем и не удерживает ресурсы до истечения срока.

Зачем это спрашивают: Срок ограничивает работу, а явная функция cancel обеспечивает детерминированное освобождение ресурсов.

concurrency

Значения контекста предназначены для данных запроса, которые должны пересекать границы API и процессов.

  • Ключи должны иметь закрытый сравнимый тип вместо встроенных строк, чтобы исключить коллизии между пакетами.
  • Сохранённые значения должны быть безопасны для конкурентного доступа и обычно предоставляться через типизированные функции доступа.
  • Value ищет в текущем контексте, затем в его предках и возвращает ближайшее значение для подходящего ключа или nil.
  • Конфигурация и необязательные параметры функции должны передаваться явными аргументами или типами опций, поскольку значения контекста скрывают зависимости и ослабляют проверку типов.

Зачем это спрашивают: Ограничение значений сквозными метаданными запроса сохраняет API явными, типизированными и удобными для сопровождения.

resilienceconcurrency

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