Как пройти собеседование по Go: топ вопросов
Go (Golang) — один из самых востребованных языков для создания высоконагруженных серверных приложений и микросервисов. Собеседование на позицию Go-разработчика часто проверяет не только знание синтаксиса, но и понимание внутреннего устройства: горутин, каналов, интерфейсов и управления памятью. В этой статье мы разберём топ-10 вопросов, которые чаще всего задают на собеседованиях, с примерами кода и пояснениями.
1. В чём разница между массивом и срезом (array vs slice)?
Это классический вопрос на понимание основ Go.
- Массив (array) — это фиксированная последовательность элементов одного типа. Размер массива является частью его типа.
- Срез (slice) — это динамическая структура, которая ссылается на базовый массив. Срез может расти и изменять размер.
// Массив — фиксированный размер
var arr [3]int = [3]int{1, 2, 3}
// Срез — динамический
var slc []int = []int{1, 2, 3}
// Важно: arr и [4]int — разные типы
// Срезы передаются по ссылке (указатель на underlying array)
Совет: Если нужно изменить длину — используйте срез. Если нужна гарантия фиксированного размера — массив.
2. Что такое горутины и как они работают?
Горутина — это лёгкий поток выполнения, управляемый рантаймом Go. В отличие от системных потоков (OS threads), горутины имеют меньший стек (начиная с 2 КБ) и быстро переключаются.
- Запуск горутины:
go func() - Горутины работают в одном адресном пространстве, поэтому доступ к разделяемым данным нужно синхронизировать (через каналы или мьютексы).
package main
import (
"fmt"
"time"
)
func printNumbers() {
for i := 1; i <= 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(i)
}
}
func main() {
go printNumbers() // запуск горутины
time.Sleep(1 * time.Second) // ждём завершения
fmt.Println("Main done")
}
Важно: Основная функция не ждёт завершения горутин. Для синхронизации используйте sync.WaitGroup или каналы.
3. Как работают каналы (channels)?
Каналы — это типизированные "трубы", через которые горутины обмениваются данными. Они обеспечивают безопасную передачу данных без явных блокировок.
- Небуферизированный канал: отправитель блокируется, пока получатель не прочитает данные.
- Буферизированный канал: отправитель блокируется только при заполнении буфера.
ch := make(chan int) // небуферизированный
chBuf := make(chan int, 3) // буферизированный (ёмкость 3)
// Отправка
go func() {
ch <- 42
}()
// Получение
value := <-ch
fmt.Println(value)
// Закрытие канала
close(ch)
// Чтение из закрытого канала возвращает zero value
Частый вопрос: Что будет, если читать из nil-канала? — Ответ: горутина заблокируется навсегда (deadlock).
4. Что такое интерфейсы в Go и чем они отличаются от других языков?
Интерфейс в Go — это набор методов. В отличие от Java или C#, реализация интерфейса в Go неявная (duck typing): тип автоматически удовлетворяет интерфейсу, если реализует все его методы.
type Speaker interface {
Speak() string
}
type Dog struct{}
func (d Dog) Speak() string { return "Woof!" }
type Cat struct{}
func (c Cat) Speak() string { return "Meow!" }
func makeSound(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
makeSound(Dog{}) // Woof!
makeSound(Cat{}) // Meow!
}
Почему это важно: Позволяет писать гибкий, слабо связанный код. Пустой интерфейс interface{} (или any в Go 1.18+) может хранить значение любого типа.
5. Как работает обработка ошибок в Go?
Go не использует исключения (try-catch). Ошибки возвращаются как последнее значение из функции. Это явный и предсказуемый подход.
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("деление на ноль")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Ошибка:", err)
return
}
fmt.Println("Результат:", result)
}
Типичные вопросы: Как создать кастомную ошибку? — Используйте fmt.Errorf или реализуйте интерфейс error.
6. Что такое nil и как он работает с разными типами?
nil — это нулевое значение для указателей, срезов, мап, каналов, функций и интерфейсов. Для разных типов nil ведёт себя по-разному.
- Указатель:
var p *int = nil— разыменование вызовет panic. - Срез:
var s []int = nil— длина и ёмкость равны 0,appendработает. - Мапа:
var m map[string]int = nil— чтение работает, запись вызывает panic. - Интерфейс: интерфейс равен
nilтолько если и его тип, и значение равныnil.
var s []int = nil
s = append(s, 1) // работает, s теперь [1]
var m map[string]int = nil
// m["key"] = 1 // panic: assignment to entry in nil map
_ = m["key"] // работает, возвращает 0
7. Как работает сборка мусора (GC) в Go?
Go использует параллельный сборщи