Skip to content

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

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

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

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

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

Вопросы

Предопределённые базовые типы Go делятся на логические, числовые и строковые.

  • bool представляет значения true и false.
  • К числовым типам относятся знаковые и беззнаковые целые числа, числа с плавающей точкой и комплексные числа.
  • string представляет неизменяемую последовательность байтов.

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

Обе формы объявляют переменные, но различаются правилами области видимости и указания типа.

  • var работает на уровне пакета или функции и может включать явный тип, начальное значение либо и то и другое.
  • Объявление var без явного типа выводит его из начального значения, а без начального значения использует zero value этого типа.
  • := работает только внутри функций и всегда выводит типы объявляемых переменных.
  • Короткое объявление нескольких переменных может повторно использовать переменные из того же блока, только если хотя бы одна переменная, кроме _, является новой.

Зачем это спрашивают: Короткая форма удобна для локального вывода типов, а var поддерживает любую область объявления и явное указание типа.

Нулевое значение присваивается переменной по умолчанию, если она создана без явного начального значения.

  • Логические значения по умолчанию равны false, числовые равны 0, а строки являются пустыми.
  • Указатели, срезы, карты, функции, каналы и интерфейсы по умолчанию равны nil.
  • Каждый элемент массива изначально содержит нулевое значение типа элемента.
  • Каждое поле структуры изначально содержит нулевое значение типа поля.

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

Константы Go могут быть нетипизированными или явно типизированными, а iota помогает создавать связанные наборы констант.

  • Нетипизированная константа сохраняет абстрактный вид константы, пока контекст не потребует конкретный тип.
  • Типизированная константа имеет явный тип из объявления, и её значение должно быть представимо этим типом.
  • Значения констант фиксируются во время компиляции и не могут изменяться после объявления.
  • Внутри блока const значение iota начинается с 0 и увеличивается на 1 для каждого следующего объявления константы.

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

Явное преобразование в Go применяет целевой тип в форме T(value).

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

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

immutability

Строка Go хранит доступную только для чтения последовательность байтов, содержимое которой нельзя изменить на месте.

  • Строка может содержать произвольные байты и не обязана быть корректной последовательностью UTF-8.
  • Индексация строки возвращает байт в указанной позиции.
  • Срез строки выбирает диапазон байтов и создаёт другое строковое значение.
  • Для изменения текста нужно создать новую строку, часто через преобразование в []byte или []rune.

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

byte и rune являются предопределёнными псевдонимами для двух разных представлений текстовых или бинарных данных.

  • byte является псевдонимом uint8 и представляет один необработанный байт.
  • rune является псевдонимом int32 и по соглашению представляет одну кодовую точку Unicode.
  • Символьный литерал в одинарных кавычках создаёт значение rune.
  • Индексация строки работает с байтами, а range по строке декодирует кодовые точки UTF-8 в значения rune.

Зачем это спрашивают: Выбор byte или rune показывает, работает ли код с необработанными закодированными данными или с кодовыми точками Unicode.

Для строки len сообщает количество байтов, а не декодированных кодовых точек Unicode.

  • Символы ASCII занимают один байт, поэтому для строк только из ASCII количество байтов и rune совпадает.
  • utf8.RuneCountInString считает значения rune, полученные при декодировании UTF-8.
  • Преобразование в []rune с последующим вызовом len тоже считает декодированные rune, но создаёт срез rune.
  • Количество rune не всегда равно числу символов, воспринимаемых пользователем, потому что один видимый знак может состоять из нескольких кодовых точек.

Зачем это спрашивают: Разделение длины в байтах и количества rune помогает избежать неверных предположений о тексте UTF-8 переменной ширины.

Функция Go может объявлять и возвращать несколько значений в фиксированном порядке.

  • Сигнатура функции перечисляет все типы результатов, а каждый return передаёт соответствующие значения.
  • Результаты вызова с несколькими значениями можно присвоить такому же количеству переменных.
  • Blank identifier _ отбрасывает значение, не создавая доступную для использования привязку.
  • Любая другая локальная переменная должна использоваться, поэтому _ позволяет явно игнорировать ненужный результат.

Зачем это спрашивают: Несколько результатов напрямую выражают связанные выходные значения, а blank identifier позволяет намеренно пропустить одно из них.

Объявление именованного типа создаёт отдельный тип, а псевдоним даёт другое имя существующему типу.

  • type UserID int определяет UserID как новый именованный тип с базовым типом int.
  • При передаче значений между UserID и int обычно требуется явное преобразование.
  • type UserID = int делает UserID идентичным int с точки зрения идентичности типов и присваивания.
  • Псевдоним не создаёт отдельный тип или отдельное базовое представление.

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

slices-maps

Массив имеет фиксированную длину, которая входит в его тип, а срез является гибким представлением нижележащего массива.

  • Длина входит в тип массива, поэтому [3]int и [4]int являются разными типами.
  • При присваивании массива копируются все его элементы.
  • Срез может менять длину в пределах своей емкости и расти с помощью append.
  • При присваивании среза копируется его дескриптор, поэтому оба среза могут ссылаться на одни элементы.

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

slices-maps

Срез описывается указателем на нижележащий массив, длиной и емкостью.

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

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

slices-maps

Nil-срез не ссылается на нижележащее хранилище, а пустой срез, не равный nil, инициализирован, но не содержит элементов.

  • У обоих длина и емкость могут быть равны нулю.
  • Только nil-срез равен nil при сравнении.
  • append, len, cap и range работают с обоими видами срезов.
  • make([]T, 0) и []T{} создают пустые срезы, не равные nil.

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

slices-maps

Функция make создает инициализированный срез с указанной длиной и необязательной емкостью.

  • make([]T, length) создает срез, емкость которого не меньше его длины.
  • make([]T, length, capacity) задает оба значения, причем емкость не может быть меньше длины.
  • Все элементы в пределах начальной длины содержат нулевое значение типа T.
  • make возвращает значение среза, а не указатель на срез.

Зачем это спрашивают: Использование make подготавливает нижележащее хранилище и дескриптор среза за одну операцию.

capacityslices-maps

append возвращает срез, содержащий исходные элементы, за которыми следуют новые.

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

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

slices-maps

Подсрез обычно использует тот же нижележащий массив, что и исходный срез.

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

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

slices-maps

copy переносит элементы из исходного среза в существующий целевой срез.

  • Она копирует min(len(dst), len(src)) элементов.
  • Она возвращает количество скопированных элементов.
  • Она не увеличивает целевой срез и не меняет его длину.
  • Она дает правильный результат, даже если исходный и целевой диапазоны перекрываются.

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

Тип ключа map должен поддерживать сравнение на равенство с помощью == и !=.

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

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

forms

Чтение отсутствующего ключа map возвращает нулевое значение типа значений этой map.

  • Прямое чтение имеет форму value := m[key].
  • Форма comma-ok value, ok := m[key] также сообщает, существует ли ключ.
  • ok равен false для отсутствующего ключа и true для существующего.
  • Логическое значение отличает отсутствующий ключ от существующего ключа с нулевым значением.

Зачем это спрашивают: Форма comma-ok устраняет неоднозначность, когда допустимое сохраненное значение может совпадать с нулевым значением типа.

Nil-map можно читать, но перед сохранением элементов ее нужно инициализировать.

  • Чтение nil-map возвращает нулевое значение типа значений.
  • Присваивание элемента nil-map вызывает панику во время выполнения.
  • make(map[K]V) создает инициализированную map, в которую можно записывать элементы.
  • delete(m, key) удаляет ключ и безопасен для отсутствующих ключей и nil-map.

Зачем это спрашивают: Для записи map необходимо инициализировать, а чтение и удаление безопасно определены и для nil-map.

Закрытые вопросы

  • 21

    Что такое структура в Go?

    structs
  • 22

    Как работают литералы структур и что является нулевым значением структуры?

    structs
  • 23

    Чем метод отличается от функции в Go?

  • 24

    Что означает получатель-значение у метода Go?

  • 25

    Что означает получатель-указатель у метода Go?

  • 26

    Как Go автоматически берёт адрес и разыменовывает значения при вызове методов?

  • 27

    Что такое указатели, & и * в Go?

  • 28

    Как в Go передаются аргументы, включая аргументы-указатели?

  • 29

    Что такое встраивание структур и продвигаемые члены в Go?

    structsnlp
  • 30

    Как регистр идентификаторов управляет экспортом в Go?

  • 31

    Как работают интерфейсы в Go и что означает неявная реализация интерфейса?

    typesinterfaces
  • 32

    Почему в Go предпочитают небольшие интерфейсы?

    typesinterfaces
  • 33

    Чем nil-интерфейс отличается от интерфейса с типизированным nil-значением?

    typesinterfaces
  • 34

    Как в Go работает проверка типа в форме comma-ok?

    forms
  • 35

    Что такое переключатель типов в Go?

  • 36

    Что представляет собой встроенный интерфейс error в Go?

    typesinterfaces
  • 37

    Как errors.New и fmt.Errorf создают ошибки в Go?

  • 38

    Как работают оборачивание ошибок через %w и функция errors.Is?

    error-handling
  • 39

    Как errors.As работает вместе с пользовательскими типами ошибок?

    error-handling
  • 40

    Как идиоматично проверять и возвращать ошибки в Go?

  • 41

    В каком порядке выполняются отложенные вызовы и когда вычисляются их аргументы?

    decision-makingerror-handling
  • 42

    Для чего в Go используется panic?

    error-handling
  • 43

    При каких условиях recover останавливает panic?

    error-handling
  • 44

    Как работают пакеты и импорты и чем отличается package main?

  • 45

    Что определяет go.mod и как семантические версии влияют на пути импорта модулей?

  • 46

    Для чего нужна функция init и в каком базовом порядке инициализируются пакеты?

  • 47

    Что такое горутина и что делает инструкция go?

    concurrency
  • 48

    Как небуферизованный канал синхронизирует горутины?

    concurrency
  • 49

    Как для буферизованных каналов работают емкость, блокировка и ответственность за закрытие?

    ownershipcapacityconcurrency
  • 50

    Как работают получение с comma-ok, range по закрытому каналу и select?

    concurrency
  • 51

    Функция добавляет элементы в слайс через append, но вызывающий код их не видит. Как это исправить?

    slices-maps
  • 52

    Код записывает значение в nil map и падает с паникой. Что вы измените?

    error-handling
  • 53

    Вспомогательная функция открывает файл и иногда оставляет файловый дескриптор незакрытым. Как это исправить?

    descriptors
  • 54

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

  • 55

    Функция принимает интерфейс с типизированным nil-указателем, но проверка if value == nil не срабатывает. Как поступить?

    typesinterfaces
  • 56

    Метод должен менять поле структуры, но после вызова значение остаётся прежним. Что вы проверите и исправите?

    structs
  • 57

    Нужно скопировать слайс перед изменением, чтобы оригинал остался прежним. Какой код вы используете?

    slices-maps
  • 58

    Горутина обновляет map, пока HTTP-обработчик читает её. Как исправить гонку?

    concurrencyhttp
  • 59

    Несколько горутин увеличивают общий счётчик запросов, но итоговое значение оказывается меньше ожидаемого. Что вы измените?

    concurrency
  • 60

    Программа запускает рабочие горутины, но завершается раньше них. Как дождаться их окончания?

    concurrency
  • 61

    Горутина навсегда ждёт отправки результата после тайм-аута вызывающего кода. Как предотвратить утечку?

    concurrency
  • 62

    Получатель бесконечно выполняет range по каналу после обработки всех задач. Чего не хватает?

    concurrency
  • 63

    Две горутины могут закрыть один канал, и программа иногда падает с паникой. Как изменить схему?

    concurrencyerror-handling
  • 64

    Нужно получить результат, но прекратить ожидание через 500 миллисекунд. Как вы это напишете?

  • 65

    В цикле с select есть ветка default, и теперь в простое он полностью загружает одно ядро CPU. Как это исправить?

  • 66

    Вы реализуете параллельную обработку задач с фиксированным пределом. Как не запускать отдельную горутину для каждой задачи?

    concurrency
  • 67

    Этап конвейера досрочно останавливается из-за ошибки, а предыдущий этап продолжает отправку. Как избежать заблокированных горутин?

    concurrencyci-cd
  • 68

    Для исправления дедлока добавили буфер канала, но на больших входных данных код всё равно зависает. Что вы сделаете?

    lockingconcurrency
  • 69

    Несколько горутин добавляют ошибки в один слайс. Как безопасно их собрать?

    concurrencyslices-maps
  • 70

    Кэш использует RWMutex, но метод захватывает RLock, а затем пытается изменить map. Как это исправить?

    caching
  • 71

    Функция возвращает fmt.Errorf("load user: %v", err), после чего errors.Is перестаёт распознавать причину. Что вы измените?

    error-handling
  • 72

    Репозиторий возвращает sql.ErrNoRows в HTTP-обработчик. Как обработать это аккуратно?

    sqlhttp
  • 73

    Очистка завершилась ошибкой после того, как основная операция уже упала. Как вернуть полезную информацию?

  • 74

    Функция логирует ошибку и одновременно возвращает её, поэтому каждый слой создаёт одинаковую запись. Какое правило вы введёте?

  • 75

    HTTP-запрос продолжает выполняться после отключения клиента. Как связать отмену с работой?

    resiliencehttp
  • 76

    Вы добавили тайм-аут через context.WithTimeout внутри функции. Какая очистка нужна?

    resilienceconcurrency
  • 77

    Фоновая горутина сохраняет контекст запроса и использует его намного позже завершения обработчика. Как изменить решение?

    concurrency
  • 78

    JSON-эндпоинт молча принимает неизвестные поля и скрывает опечатки клиентов. Как сделать декодирование строже?

    endpoints
  • 79

    JSON-ответ раскрыл хэш пароля, потому что код сериализовал всю структуру пользователя. Что вы измените?

    passwordsstructs
  • 80

    В PATCH-запросе нужно различать отсутствующее поле, false и true. Как вы его смоделируете?

  • 81

    HTTP-обработчик пишет тело ошибки после того, как уже отправил ответ 200. Как его структурировать?

    httpstructs
  • 82

    Обработчик читает тело запроса без ограничения и может исчерпать память. Какую защиту вы добавите?

    memory
  • 83

    Вызов через HTTP-клиент иногда зависает навсегда. Как сделать его безопасным?

    http
  • 84

    Сервис вызывает сторонний API и получает ответ не из диапазона 2xx. Что нужно сделать до декодирования успешных данных?

    fan-outapi
  • 85

    Обработчику нужен ID пользователя из пути, но сейчас он доверяет любой строке. Как вы его проверите?

    validation
  • 86

    Нужно middleware для записи длительности запроса без нарушения работы обработчика. Как вы его реализуете?

    middleware
  • 87

    Табличный модульный тест останавливается на первом неудачном случае. Как улучшить диагностику?

    unit
  • 88

    Тесты проходят по отдельности, но падают вместе из-за общей map на уровне пакета. Как их исправить?

    testing
  • 89

    Как протестировать HTTP-обработчик без запуска настоящего сервера?

    http
  • 90

    Функция напрямую зависит от конкретного клиента базы данных, поэтому её трудно модульно тестировать. Какой небольшой рефакторинг вы сделаете?

    refactoringdatabaseunit
  • 91

    Тест сравнивает полный текст ошибки и ломается после добавления контекста. Как сделать его устойчивым?

    concurrency
  • 92

    Параллельный тест нестабилен, потому что спит 50 миллисекунд и предполагает, что работа завершилась. Чем заменить sleep?

    flakyconcurrency
  • 93

    Как проверить, что функция останавливается при отмене контекста?

    concurrency
  • 94

    Функция разбора вызывает панику на некорректных пользовательских данных. Как изменить реализацию и тест?

    error-handling
  • 95

    Тесту нужен временный выходной файл. Как не оставить файлы после выполнения?

  • 96

    Функция запускает горутины в цикле, и каждая должна обработать свой вход. Как сделать это намерение явным и тестируемым?

    concurrency
  • 97

    Производитель может не отправить ни одного значения, но потребитель всё равно должен завершиться. Как реализовать и проверить этот крайний случай?

  • 98

    Эндпоинт возвращает JSON, но при ошибке кодирования иногда отправляет частичный ответ. Как снизить этот риск?

    endpoints
  • 99

    Сервис повторяет любой неудачный HTTP-запрос, включая POST и ответы 400. Как сделать повторы безопаснее?

    resiliencehttp
  • 100

    Вам достался небольшой Go API с возможными гонками и слабыми тестами. В какой последовательности вы проверите его перед изменением поведения?

    ownershipapitesting