В статье я рассмотрю наиболее часто встречаемые вопросы на собеседованиях Golang разработчика. Эта выборка основана только на личном опыте по результатам моих интервью в компании Авито, Тинькофф, SberCloud, МТС Cloud, NVIDIA, Acronis, Mail.ru Cloud Solutions, VK Pay. Всё действие происходило летом 2021 года.
В зависимости от зрелости процессов отбора кандидатов все собеседования можно разделить на две группы:
На многих интервью требовалось писать код, решать алгоритмические задачи или обсуждать представленный фрагмент кода, объясняя что в нем не так и как можно исправить. Поэтому стоит заранее уточнить у HR процесс собеседования, чтобы быть готовым и иметь под рукой ноутбук.
В одном случае меня попросили решить тестовое задание, поскольку я не слишком хорошо прошел первую секцию на алгоритмы. Но в целом это исключение, поскольку немногие кандидаты готовы тратить на это время.
Вопросы по предыдущим местам работы. Причины ухода с текущего места, ожидания от будущего работодателя.
Рекомендую вспомнить не только свои основные достижения, но и проблемы, с которыми приходилось сталкиваться (например, нарушение сроков, некорректная реализация поставленной задачи, конфликты в коллективе и т.п.), как их удавалось решить и какие выводы были сделаны, чтобы не допустить повторения этих ситуаций в будущем.
Вопросы по soft skills. Например, опишите вашего идеального руководителя или кем вы видите себя через 5 лет.
Эта группа вопросов помогает понять насколько вы разделяете ценности компании, будет ли команде комфортно работать с вами и вашу мотивацию. Я встречал мало людей, которые целенаправленно задавали похожие вопросы и могли чётко объяснить, что они ожидают услышать и какие делают выводы из ответов кандидата.
Расскажите чем вам нравится язык Golang, какие есть недостатки?
Достаточно рассказать о своём опыте работы с языком. Можно сравнить с другими языками - везде есть свои плюсы и минусы.
Расскажите, что происходит, когда пользователь набирает в адресной строке браузера, например, google.ru.
Довольно заезженный вопрос, спросили только на одном собеседовании. Можно найти множество подробных ответов на этот вопрос (например). Я бы поверхностно описал весь процесс, уточнив какие темы наиболее интересно было бы услышать собеседнику.
Что такое slice? Расскажите внутреннее устройство.
Как работает функция append? Как происходит аллокация нового массива при использовании функции append?
Хорошее описание есть в документации: раз, два.
Сколько байт занимает в памяти структура slice?
type slice struct {
len int // 4/8 bytes
cap int // 4/8 bytes
ptr *type // 4/8 bytes
}
Размер целочисленного типа и указателя зависит от платформы компиляции (32-bit, 64-bit).
Что такое интерфейс? Расскажите внутреннее устройство.
Нужно понимать что из себя представляет структура интерфейса, как выполняется её инициализация и что такое таблица методов. Здесь подробное описание.
Пример кода на понимание внутреннего представления интерфейсов.
Что будет выведено в консоль после выполнения кода?
package main
import "fmt"
func main() {
err := do()
if err != nil {
fmt.Println("ERROR")
} else {
fmt.Println("NO ERROR")
}
}
func do() error {
var p *MyError = nil
if false {
p = &MyError{"error"}
}
return p
}
type MyError struct{
msg string
}
func (e MyError) Error() string {
return e.msg
}
Правильный ответ: ERROR, т.к. структура интерфейса будет инициализирована на строке 15.
Пример кода с интерфейсом и value/pointer receiver.
Какой результат выполнения этого кода?
package main
import "fmt"
type Bar interface {
Get() string
}
func print(i Bar) {
fmt.Println(i.Get())
}
type Foo struct {
f string
}
func (foo *Foo) Get() string {
return foo.f
}
func main() {
foo := Foo{"hello"}
print(foo)
}
Результатом будет ошибка компиляции:
cannot use foo (type Foo) as type Bar in argument to print: Foo does not implement Bar (Get method has pointer receiver)
Если кратко: брать ссылку на временный скопированный объект (в Go всё передается по значению) не имеет смысла, поскольку это может приводить к изменению только временного объекта, а не исходного. Поэтому при преобразовании foo
в тип интерфейса Bar
возникает ошибка, т.к. компилятор находит только метод с pointer receiver. Более подробно можно прочитать здесь.
Что такое горутина? В чем отличие горутин от потоков операционной системы?
Этот вопрос тесно связан с планировщиком горутин (смотри ниже), поэтому рекомендую ознакомиться с его устройством. Также хорошо освежить в памяти, что такое context switching потоков ОС и то что в Go используется динамически расширяемый stack памяти под каждую горутину.
Как работает планировщик горутин?
Советую прочитать отличный цикл статей о планировщике Go.
Кооперативная многозадачность vs вытесняющая многозадачность.
В Go версии 1.14 было внесено важное изменение в работу планировщика, с которым можно ознакомиться в этой статье.
Какие вы знаете способы синхронизации между горутинами в Go?
Мьютексы (sync.Mutex
и sync.RWMutex
), каналы, атомики, sync.WaitGroup
, sync.Once
. Нужно понимать в каких случаях следует использовать sync.RWMutex
.
Про особенность использования sync.Map
стоит прочитать эту статью. А про устройство CPU caches посмотрите это видео.
Задача на многопоточность.
Обычно просили реализовать свой worker pool, который сможет принимать на вход задачи и обрабатывать их параллельно.
Что такое каналы в Go и как они устроены? Какие виды каналов вы знаете?
Читаем отличный доклад с одной из конференций.
Что происходит при записи или чтении в/из nil канала, закрытого канала, буферизированного/небуферизированного канала?
Можно ознакомиться в статье выше или здесь
Что такое select и как он работает?
Можно ознакомиться в статье выше. Порядок чтения каналов в select выбирается произвольно, но есть небольшой хак, с помощью которого можно сделать поведение детерминированным:
select {
case <-ch1:
fmt.Println("read from ch1")
default:
select {
case <-ch2:
fmt.Println("read from ch2")
default:
}
}
Но такой хак стоит использовать осторожно, т.к. можно уйти в полку по CPU.
Что это за структура данных, какие ее преимущества?
Классическая хэш-таблица. Стоит рассказать о методе разрешения коллизий, переаллокациях при превышении load factor и алгоритмической сложности в нотации O-большое.
Что происходит при конкурентной записи в map?
Структура данных не является потокобезопасной, поэтому конкурентная запись может приводить к панике. Стоит рассказать о способах синхронизации доступа к map.
Какие типы данных могут быть использованы в качестве ключа map?
Любой сравниваемый тип:
As mentioned earlier, map keys may be of any type that is comparable. The language spec defines this precisely, but in short, comparable types are boolean, numeric, string, pointer, channel, and interface types, and structs or arrays that contain only those types. Notably absent from the list are slices, maps, and functions; these types cannot be compared using
==
, and may not be used as map keys.
Назовите два способа работы с ошибками в Go?
Возврат ошибки в виде интерфейса error и выкидывание panic. Паника - это не тоже самое, что и классические исключения в других языках, поскольку паника гарантированно завершает выполнение текущей функции.
Как можно обработать панику?
С помощью recover. Обратите внимание, что обработчик паники должен быть объявлен в той же горутине, где возникает паника.
Как можно определить место возникновения ошибки?
В случае небольших микросервисов и приложений достаточно подробного описания ошибки, чтобы понять место её возникновения. В остальных случаях можно добавлять стек вызова функций в описание ошибки.
Что такое контекст и в каких случаях его стоит использовать?
В документации хорошо об этом рассказано.
Как работает garbage collector?
Еще один отличный цикл статей по работе сборщика мусора.
Что такое escape analysis и как он работает?
Здесь важно понимать какие правила используются для определения необходимости перемещения объекта в heap. Читайте здесь.
Что такое defer?
Читаем документацию. Можно посмотреть статью о подводных камнях при использовании defer
.
Эта тема довольно обширная, но вопросы обычно повторяются. Из основного:
SELECT a, b FROM table WHERE a = 0 AND b > 1 ORDER BY c
Достаточно знать особенности и отличия транспортных протоколов TCP и UDP. Один раз спросили каким образом происходит преобразование IP адреса в MAC адрес узла: рассказываем про протокол ARP и arping.
Здесь уже вариативность вопросов большая. Например, приходилось проектировать пользовательскую ленту Facebook, сервис сокращения ссылок с нагрузкой на чтение и запись в 300000 RPS. Обычно в разговоре затрагиваются темы проектирования отказоустойчивых, масштабируемых решений, способы хранения данных (репликация и шардирование), кэширование данных, мониторинги и логирование, API.
В интернете можно найти много видео и материалов на тему подготвки и прохождения архитектурных секций. Рекомендую их изучить, чтобы быть готовыми.