Вопросы по интерфейсам и основам Go на собеседовании
Основы Go спрашивают через ловушки: интерфейс, который не равен nil, слайс, неожиданно поделивший массив с другим, и мапа, которая паникует при параллельной записи. Именно эти места отделяют читавших книгу от писавших код.
Из чего состоит тема
Так тема разложена в тренажёре: движок ведёт прогресс по каждой подтеме отдельно и возвращает те, где вы ошибаетесь.
- Мапы и строки13
- Слайсы и массивы12
- Структуры и методы10
- Указатели и передача9
- Интерфейсы: неявные8
- Дженерики и constraints7
- Типы и zero values7
- any и type assertion6
- Ловушка nil-интерфейса6
- Type switch4
Разборы подтем
Конспект по каждой: что это, как отвечать вслух, на чём валятся, плюс вопросы для самопроверки.
- Типы и нулевые значения в Go7 вопросов
- Слайсы в Go: len, cap и append12 вопросов
- Мапы и строки в Go13 вопросов
- Структуры и методы в Go10 вопросов
- Указатели в Go9 вопросов
- Интерфейсы в Go8 вопросов
- Ловушка nil-интерфейса в Go6 вопросов
- Type switch в Go4 вопросов
- any и type assertion в Go6 вопросов
- Дженерики в Go7 вопросов
Примеры вопросов с разбором
- var m map[string]int (без make). Что делают чтение m["x"] и запись m["x"]=1?A)Чтение вернёт 0; запись паникует: assignment to entry in nil mapB)Оба паникуют: nil-мапа непригодна ни к чемуC)Чтение вызовет панику, а запись молча создаст мапу автоматическиD)Оба работают: nil-мапа ведёт себя как пустая
показать ответ и разбор
+A)Чтение вернёт 0; запись паникует: assignment to entry in nil map// разбор: nil-мапа читается как пустая — любой ключ даёт нулевое значение (и comma-ok даёт ok=false), но запись в неё паникует «assignment to entry in nil map», потому что вставке некуда идти. Поэтому мапу перед записью создают через make или литерал. Частый баг — забыть make у мапы-поля структуры. Спот-чек: чтение дало 0.
- Что верно про указатели в Go?A)Есть арифметика указателей как в C: p+1 сдвигает на элементB)&x берёт адрес, *p разыменовывает; арифметики указателей нетC)Указатели запрещены — Go передаёт всё по ссылке автоматическиD)new(T) возвращает T по значению, а не *T
показать ответ и разбор
+B)&x берёт адрес, *p разыменовывает; арифметики указателей нет// разбор: &x даёт адрес, *p разыменовывает, new(T) выделяет обнулённый T и возвращает *T. Но адресной арифметики (p+1) в Go нет — это часть безопасности памяти (обход только через пакет unsafe). Указатели существуют и нужны: мутация, крупные структуры, опциональность через nil — просто без арифметики адресов.
- b := a[:2] от слайса a с запасом cap, затем b = append(b, x). Что со слайсом a?A)a не изменится: append тут неизменно создаёт отдельный новый массивB)Компилятор запретит append к под-слайсу другого слайсаC)При запасе ёмкости append пишет в общий массив и затирает a[2]D)a обрежется до длины b автоматически
показать ответ и разбор
+C)При запасе ёмкости append пишет в общий массив и затирает a[2]// разбор: Под-слайс делит с исходным один backing-массив. Если у него есть запас (cap > len), append пишет в тот же массив по месту — и портит элементы исходного за его длиной (a[2]). Если запаса нет, append выделяет новый массив и связь рвётся. Отсюда коварные баги: поведение append зависит от cap. Спот-чек: a стал [1 2 99].
- Структуры с полями-числами. Что даёт T{1,2} == T{1,2}?A)true — структуры сравнимы поэлементно, если поля сравнимыB)false — оператор сравнивает адреса, а они разныеC)Ошибка компиляции: структуры оператором == не сравниваютD)true, но только для структур, помеченных как comparable
показать ответ и разбор
+A)true — структуры сравнимы поэлементно, если поля сравнимы// разбор: == для структур сравнивает их поэлементно и даёт true, если равны все соответствующие поля — при условии, что все поля СРАВНИМЫ. Структура со слайсом/мапой/функцией внутри уже несравнима: == не скомпилируется. Сравниваются значения полей, а не адреса. Присваивание структуры тоже копирует её целиком по значению. Спот-чек: true.
- Чему равны var n int; var s string; var p *int сразу после объявления?A)0, "" и nil соответственно — это нулевые значения типовB)Неинициализированному мусору из памяти, как в CC)Это ошибка компиляции: переменную надо инициализировать явноD)0, "" и указателю на нулевой int с валидным адресом
показать ответ и разбор
+A)0, "" и nil соответственно — это нулевые значения типов// разбор: В Go нет неинициализированных переменных: объявленная без значения получает нулевое значение типа — 0 у чисел, "" у строк, false у bool, nil у указателей/слайсов/мап/каналов/интерфейсов/функций. Это детерминированно (не мусор из памяти) и часто делает тип полезным из коробки: var b bytes.Buffer уже готов к работе.
- Что такое any в современном Go и как достать из него конкретный тип?A)any — это указатель void*, тип достают приведением (T)xB)any — специальный generic-тип, распаковывается вызовом unwrap()C)any хранит только строки, другой тип надо маршалить через JSOND)any — псевдоним interface{}; тип достают ассерцией x.(T)
показать ответ и разбор
+D)any — псевдоним interface{}; тип достают ассерцией x.(T)// разбор: any — введённый в 1.18 псевдоним для interface{}: значение любого типа. Чтобы получить конкретный тип, используют type assertion x.(T) (с формой comma-ok против паники) или type switch. Это не void* и не generic-контейнер — обычный пустой интерфейс, просто с более читаемым именем.
- Как в Go (1.18+) объявить обобщённую функцию, работающую с типами-числами?A)func F(T type, x T) — тип передаётся первым обычным аргументомB)Только через interface{} и приведение внутри — дженериков в Go нетC)func F<T>(x T) — угловые скобки, как в C++ и JavaD)func F[T Number](x T) — параметр типа в скобках [] с constraint
показать ответ и разбор
+D)func F[T Number](x T) — параметр типа в скобках [] с constraint// разбор: Дженерики появились в Go 1.18. Параметры типа объявляют в КВАДРАТНЫХ скобках после имени: func F[T Number](x T) T, где Number — constraint (интерфейс-ограничение или объединение типов). Не угловые скобки (как C++/Java) и не «тип аргументом». До 1.18 обобщённость эмулировали через interface{}, теряя типобезопасность.
- Как тип в Go объявляет, что реализует интерфейс?A)Ключевым словом implements в объявлении типаB)Регистрацией типа в интерфейсе через reflect при старте программыC)Никак явно — хватает иметь все методы интерфейса (неявно)D)Обязательным встраиванием интерфейса полем в структуру
показать ответ и разбор
+C)Никак явно — хватает иметь все методы интерфейса (неявно)// разбор: В Go реализация интерфейсов НЕЯВНАЯ (структурная типизация): тип реализует интерфейс автоматически, если у него есть все методы с нужными сигнатурами. Ключевого слова implements нет, регистрации нет. Плюс — интерфейс можно объявить постфактум для чужого типа; минус — связь не задекларирована в коде (компилятор всё равно проверит при присваивании).
- Что представляет собой nil-интерфейс (var e error = nil)?A)Пару без типа и без значения — оба компонента пустыB)Указатель на нулевой объект интерфейса, лежащий в кучеC)Значение с уже заданным типом error, но пустыми даннымиD)Специальный объект-одиночку, общий для всех интерфейсов сразу
показать ответ и разбор
+A)Пару без типа и без значения — оба компонента пусты// разбор: Интерфейсное значение равно nil тогда и только тогда, когда пусты ОБА компонента — и динамический тип, и данные. var error = nil — именно такое. Как только в интерфейс кладут значение с конкретным типом (даже типизированный nil-указатель), компонент типа заполняется, и интерфейс перестаёт быть равен nil. Здесь корень ловушки typed nil.
это 9 из 82
Ещё 73 вопросов по теме — в тренажёре, с движком повторения
Прочитать разбор и ответить самому — разные навыки. В Сеньорчике вопросы идут сессиями, а движок возвращает подтемы, где вы ошибаетесь, пока они не начнут отскакивать. Бесплатно, лимит по энергии.
Частые вопросы
Почему интерфейс с nil внутри не равен nil?
Значение интерфейса это пара из типа и данных. Если тип известен, а указатель пуст, интерфейс не пустой. Это классический источник ошибок при возврате ошибки через интерфейс error.
Что спрашивают про слайсы?
Устройство из указателя, длины и ёмкости, поведение при append и перевыделении, почему подслайс может изменить исходный массив и как правильно копировать данные.