Как пройти собеседование по Go: топ вопросов и ответов

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как пройти собеседование по 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 использует параллельный сборщи

Блоги

Рекомендаций по книгам