ENIGMA AI
ENIGMA AI
Golang

Собеседование Golang

E... Enigma Team 13 мин чтения 82 просмотров
ПОДГОТОВКА · BACKEND · GOLANG
Go проверяют не по синтаксису, а по горутинам и подводным камням.

Полный гайд: какие темы решают исход, разбор частых вопросов и шесть задач с решением — для junior, middle и senior.

горутинытема №1
6задач с решением
2026с дженериками и errors

// обновлено 07.2026 · темы, вопросы и практика

Собеседование Golang

Собеседование по Go отличается от собеседований по другим языкам одним акцентом: синтаксис здесь маленький, а спрашивают глубоко. Проверяют не «знаете ли вы язык», а понимаете ли конкурентную модель, устройство слайсов и интерфейсов и десяток идиоматичных подводных камней, на которых спотыкаются даже опытные.

Этот гайд — карта того, что действительно спрашивают, разбор ключевых тем и шесть задач с решением, которые реально дают на лайвкодинге. Подойдёт на позицию Go-разработчика любого грейда. А готовиться к самому интервью удобно с нейросетью для собеседований ENIGMA — тренажёр и разбор ответов.

01 · ФОРМАТ

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

Скелет процесса стандартный: скрининг, техническая секция, лайвкодинг, а для middle+ и senior — system design. Но акценты у Go свои.

  • Конкурентность в центре. Горутины, каналы, синхронизация и context — то, вокруг чего строится большинство вопросов и задач.
  • Идиоматичность важнее «умных» решений. Ценят «Go way»: простой явный код, обработку ошибок значениями, отсутствие лишних абстракций.
  • Чтение кода и ловушки. Часто дают сниппет с вопросом «что выведет и почему» — на слайсах, интерфейсах, defer.
  • Инструменты рядом. Ждут знания go test, флага -race, профилирования и табличных тестов.

Интервьюер проверяет три уровня: знаете базу, понимаете, как оно работает внутри (планировщик, слайс-хедер, устройство интерфейса), и умеете применять идиоматично. Третий уровень и отличает middle от junior.

02 · КАРТА ТЕМ

Что спрашивают и на каких грейдах

Темы распределены неравномерно: конкурентность забирает львиную долю, и её пропуск чаще всего стоит оффера. Ниже — два среза плюс распределение по грейдам.

Доля тем на Go-собеседованиях

Иллюстративно, по разборам собеседований — сверьте с вашими данными

Конкурентность (горутины, каналы, sync)32%
Слайсы и мапы17%
Интерфейсы15%
Ошибки и defer13%
Рантайм и GC12%
Дженерики и прочее11%

Как часто тема всплывает

Доля собеседований, где тема встречается (иллюстративно)

Горутины и каналы94%
Слайсы и мапы82%
Интерфейсы76%
Ошибки и defer71%
Память / GC / escape58%
Дженерики47%

Из чего состоит секция по грейдам

Условное распределение: теория · задачи · дизайн (иллюстративно)

JUNIORтеория 60% · задачи 35% · дизайн 5%
MIDDLEтеория 40% · задачи 40% · дизайн 20%
SENIORтеория 25% · задачи 32% · дизайн 43%
Теория Задачи Дизайн
// что проверяют на каждом грейде
ГрейдЧто проверяютТипичные вопросы
JuniorСинтаксис, базовые горутины и каналы, слайсы, ошибкиЧем горутина отличается от потока; буферизованный vs небуферизованный канал
MiddleВнутренности, гонки, context, интерфейсы, defer-ловушкиКак работает планировщик; почему «typed nil» не равен nil; slice aliasing
SeniorАрхитектура, производительность, escape-анализ, дизайн под нагрузкуКак спроектировать пайплайн; когда значение уходит в кучу; тюнинг GC
// ENIGMA

Готовьтесь по реальным вопросам

ENIGMA — нейросеть для собеседований: подсказки в реальном времени и разбор ответов, чтобы выходить на секцию уверенно.

Нейросеть для собеседований →
03 · КОНКУРЕНТНОСТЬ

Горутины и планировщик

Главная тема. Горутина — это лёгкий поток исполнения, которым управляет не ОС, а рантайм Go. Стартовый стек — порядка пары килобайт, он растёт по мере надобности, поэтому горутин можно держать сотни тысяч. Запуск — go f().

Модель G-M-P

Планировщик мультиплексирует множество горутин (G) на ограниченное число потоков ОС (M) через логические процессоры (P), которых GOMAXPROCS штук. Каждый P держит локальную очередь горутин; при простое P «крадёт» работу у других (work stealing). Отсюда сильный ответ на «чем горутина отличается от потока»: тысячи горутин живут на горстке потоков, переключение дешёвое и происходит в пользовательском пространстве.

Утечки горутин

Любимый вопрос на middle. Горутина, заблокированная на канале, который никто не закроет и в который никто не пошлёт, живёт вечно и течёт по памяти. Правильный ответ включает context для отмены и аккуратное закрытие каналов — кто пишет, тот и закрывает.

// ИДИОМА «Don't communicate by sharing memory; share memory by communicating». Базовый принцип Go: предпочитать передачу данных через каналы разделяемому состоянию под мьютексом. Но это не догма — счётчик под sync.Mutex или atomic часто проще и быстрее канала.
04 · КАНАЛЫ

Каналы и select

Небуферизованный канал (make(chan int)) синхронный: отправка блокируется, пока кто-то не прочитает. Буферизованный (make(chan int, 8)) принимает значения до заполнения буфера. Это первый вопрос по теме.

// поведение каналов — что нужно знать наизусть
ОперацияРезультат
Отправка в закрытый каналПаника
Чтение из закрытого каналаНулевое значение и ok == false
Чтение/отправка в nil-каналБлокировка навсегда
Закрытие nil или уже закрытогоПаника
range по каналуЧитает, пока канал не закроют

select ждёт первый готовый case; если готовых несколько — выбирает случайный; с default не блокируется вовсе. На этом строятся таймауты (case <-time.After(d)), неблокирующие отправки и отмена через <-ctx.Done().

05 · SYNC / CONTEXT

Синхронизация и context

Когда каналы избыточны, в ход идёт пакет sync:

  • sync.Mutex / sync.RWMutex — взаимное исключение; RWMutex разводит много читателей и одного писателя.
  • sync.WaitGroup — дождаться завершения группы горутин (Add, Done, Wait).
  • sync.Once — гарантированно однократная инициализация.
  • sync/atomic — безблокировочные счётчики и флаги; быстрее мьютекса на простых операциях.

context

context.Context — стандартный способ передать отмену, дедлайн и request-scoped значения через цепочку вызовов. Идиома: ctx — первым аргументом функции, отмена распространяется вниз по дереву вызовов, а по <-ctx.Done() горутины корректно завершаются. Это ключ к тому, чтобы горутины не текли.

// ЛОВУШКА Мапа не потокобезопасна. Одновременные чтение и запись в обычную map роняют программу с фатальной ошибкой (не паникой, которую можно поймать). Нужно — sync.RWMutex поверх мапы или sync.Map под специфичные сценарии.
06 · СЛАЙСЫ / МАПЫ

Слайсы и мапы

Устройство слайса

Слайс — это заголовок из трёх полей: указатель на массив, длина (len) и ёмкость (cap). Отсюда всё поведение. append дописывает в пределах cap, а при нехватке — выделяет новый массив с запасом и копирует данные. Именно поэтому переслайсинг «делит» массив, и запись через один слайс может неожиданно изменить другой (подробный разбор — в задаче 3).

copy(dst, src) копирует поэлементно; трёхиндексный слайс s[low:high:max] ограничивает cap и заставляет следующий append аллоцировать заново — частый способ обезопасить себя от алиасинга.

Мапы

  • Порядок итерации намеренно случайный — полагаться на него нельзя.
  • Чтение из nil-мапы возвращает нулевое значение, а запись — паникует.
  • Ключи должны быть сравнимыми (comparable); слайс ключом быть не может.
  • Для конкурентного доступа — синхронизация (см. выше).
07 · ИНТЕРФЕЙСЫ

Интерфейсы

Интерфейсы в Go удовлетворяются неявно: тип реализует интерфейс, если у него есть нужные методы — без ключевого слова «implements». Пустой интерфейс any (он же interface{}) принимает любое значение.

Ключ к каверзным вопросам — устройство: значение интерфейса это пара (тип, значение). Интерфейс равен nil, только когда обе части пусты. Если положить в интерфейс nil-указатель конкретного типа, интерфейс станет не-nil — это знаменитая ловушка «typed nil» (разбор — в задаче 4).

Проверка и извлечение типа — через утверждение v, ok := i.(T) и switch по типу:

switch v := x.(type) {
case int:
    fmt.Println("int:", v)
case string:
    fmt.Println("string:", v)
default:
    fmt.Println("другой тип")
}
08 · ОШИБКИ

Ошибки и defer

Ошибка в Go — обычное значение: интерфейс error с методом Error() string. Их возвращают и проверяют явно, а не бросают. Что спрашивают:

  • Оборачивание. fmt.Errorf("...: %w", err) сохраняет исходную ошибку в цепочке; развернуть — через errors.Is (сравнение с sentinel) и errors.As (приведение к типу).
  • Sentinel-ошибки. var ErrNotFound = errors.New("not found") и сравнение через errors.Is.
  • panic/recover. Для действительно исключительных ситуаций, не для управления потоком. recover работает только внутри defer.

defer

Отложенные вызовы выполняются в порядке LIFO при выходе из функции. Два нюанса, на которых ловят: аргументы defer вычисляются в момент объявления (а не выполнения), и defer может менять именованные возвращаемые значения:

func f() (result int) {
    defer func() { result++ }() // изменит возврат
    return 10                   // вернётся 11
}
09 · РАНТАЙМ

Память, GC и ресиверы

Стек, куча и escape-анализ

Где разместить значение — на стеке или в куче — решает компилятор через escape-анализ. Если ссылка «убегает» за пределы функции (возвращается указатель, значение кладётся в интерфейс или захватывается замыканием горутины), оно уходит в кучу. Посмотреть решения компилятора можно так: go build -gcflags="-m". Это частый senior-вопрос: не «что такое куча», а «когда и почему значение туда попадёт».

Сборщик мусора

GC в Go — конкурентный, трёхцветный mark-and-sweep, без поколений и без компактизации, заточенный под короткие паузы (обычно доли миллисекунды stop-the-world). Частоту регулирует GOGC. Хороший ответ — про компромисс между частотой сборок и потреблением памяти, а не пересказ терминов.

Ресиверы значения и указателя

Метод с указателем-ресивером (func (c *Counter)) может менять получателя и не копирует его; со значением (func (c Counter)) работает с копией. Отсюда правило про множества методов: интерфейс, требующий метод с указателем-ресивером, удовлетворяет только *T, но не значение T.

// ресивер: значение или указатель
АспектЗначение (T)Указатель (*T)
Меняет получателяНет (копия)Да
Копирование при вызовеДаНет
Когда выбиратьМелкие неизменяемые типыИзменение состояния, большие структуры
10 · ДЖЕНЕРИКИ

Дженерики

С версии 1.18 в Go есть обобщённые типы: параметры типа в квадратных скобках и ограничения (constraints). В 2026-м базовое владение ими ждут почти везде.

func Map[T, U any](in []T, f func(T) U) []U {
    out := make([]U, len(in))
    for i, v := range in {
        out[i] = f(v)
    }
    return out
}

Ограничение any разрешает любой тип, comparable — типы, которые можно сравнивать через ==, а собственные ограничения-интерфейсы задают допустимый набор типов. Сильный ответ на «когда применять дженерики»: когда иначе пришлось бы дублировать код под разные типы или городить any с приведениями — но не ради самих дженериков там, где хватает интерфейса.

11 · ЗАДАЧИ

Практические задачи с решением

Шесть задач, которые реально дают на лайвкодинге Go-разработчику. Сначала попробуйте сами, потом сверьтесь.

Задача 1. Пул воркеров (junior/middle)

Обработать поток заданий фиксированным числом горутин и собрать результаты.

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for j := range jobs {
        results <- j * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    var wg sync.WaitGroup

    for w := 1; w <= 3; w++ { // 3 воркера
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }

    for j := 1; j <= 9; j++ {
        jobs <- j
    }
    close(jobs)   // сигнал воркерам: заданий больше нет

    wg.Wait()
    close(results) // безопасно закрыть после того, как все воркеры вышли

    for r := range results {
        fmt.Println(r)
    }
}

Разбор. Ключевое — порядок закрытия: close(jobs) останавливает range в воркерах, wg.Wait() ждёт их завершения, и только потом закрывается results. Закрыть results раньше — паника при отправке; не закрыть — range по нему повиснет.

Задача 2. Гонка данных на счётчике (middle)

Сто горутин по 1000 инкрементов общего счётчика. Ожидаем 100000, стабильно получаем меньше. Почему и как починить?

type Counter struct {
    value int
}

func (c *Counter) Inc() { c.value++ } // гонка: чтение-инкремент-запись не атомарны

Разбор. c.value++ — три операции без синхронизации, горутины перетирают результаты. Чинится мьютексом:

type Counter struct {
    mu    sync.Mutex
    value int
}

func (c *Counter) Inc() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.value++
}

Для простого счётчика быстрее sync/atomic (atomic.AddInt64) — без блокировки. А найти гонку помогает встроенный детектор: go test -race или go run -race.

Задача 3. Слайсы: что выведет код? (middle)

Классическая ловушка на понимание слайс-хедера.

s := []int{1, 2, 3, 4}
low := s[:2]           // len 2, cap 4 — общий массив с s
low = append(low, 99)  // места хватает → пишем в s[2]
fmt.Println(s)         // что здесь?

Разбор. Выведет [1 2 99 4]. У low ёмкость 4, поэтому append не выделяет новый массив, а пишет прямо в общий с s — и «портит» исходный слайс. Фикс — ограничить ёмкость трёхиндексным слайсом:

low := s[:2:2]         // len 2, cap 2
low = append(low, 99)  // теперь append выделит новый массив
fmt.Println(s)         // [1 2 3 4] — не тронут

Задача 4. Ловушка «typed nil» (middle/senior)

Почему проверка на nil не срабатывает?

type MyErr struct{}

func (e *MyErr) Error() string { return "boom" }

func do() error {
    var e *MyErr // nil-указатель
    return e     // возвращаем его как error
}

func main() {
    err := do()
    fmt.Println(err == nil) // что напечатает?
}

Разбор. Напечатает false. Интерфейс error хранит пару (тип, значение); здесь тип = *MyErr, значение = nil, а значит сам интерфейс не nil. Фикс — возвращать нетипизированный nil явно:

func do() error {
    var e *MyErr
    if e == nil {
        return nil // чистый nil-интерфейс
    }
    return e
}

Задача 5. Слияние каналов (fan-in) (middle/senior)

Собрать несколько входных каналов в один. Заодно — на дженериках.

func merge[T any](chans ...<-chan T) <-chan T {
    out := make(chan T)
    var wg sync.WaitGroup
    wg.Add(len(chans))

    for _, c := range chans {
        go func(c <-chan T) { // c параметром — без захвата переменной цикла
            defer wg.Done()
            for v := range c {
                out <- v
            }
        }(c)
    }

    go func() {
        wg.Wait()
        close(out) // закрываем, когда все входы вычитаны
    }()

    return out
}

Разбор. На каждый вход — своя горутина, все пишут в общий out. Отдельная горутина ждёт wg.Wait() и закрывает out, чтобы получатель мог спокойно делать range. Передача c параметром важна для версий до Go 1.22, где переменная цикла была общей.

Задача 6. Отмена группы задач: errgroup + context (senior)

Запустить N задач параллельно, отменить остальные при первой ошибке и дождаться всех.

func fetchAll(ctx context.Context, urls []string) ([]string, error) {
    g, ctx := errgroup.WithContext(ctx)
    results := make([]string, len(urls))

    for i, url := range urls {
        i, url := i, url // до Go 1.22: копия переменных цикла
        g.Go(func() error {
            data, err := fetch(ctx, url)
            if err != nil {
                return err // первая ошибка отменит ctx для остальных
            }
            results[i] = data // индексы не пересекаются — гонки нет
            return nil
        })
    }

    if err := g.Wait(); err != nil {
        return nil, err
    }
    return results, nil
}

Разбор. errgroup.WithContext даёт группу и производный ctx: как только одна горутина вернёт ошибку, ctx отменяется, и остальные, если слушают ctx.Done(), завершаются. Запись в разные индексы results безопасна — горутины не пересекаются по памяти.

// ЧЕСТНОСТЬ На собеседовании спросят по коду. Написали errgroup и «без гонок» — будьте готовы объяснить, почему именно без гонок, и запустить с -race. Усиливать формулировки можно, выдумывать — нет.
12 · ЧЕК-ЛИСТ

Чек-лист подготовки

  1. Закройте конкурентность. Горутины и планировщик, каналы и select, sync и context, утечки горутин — это ядро.
  2. Выучите ловушки. Slice aliasing, «typed nil», defer с именованными возвратами, случайный порядок мапы, паника на закрытом канале.
  3. Поймите внутренности. Слайс-хедер, устройство интерфейса, escape-анализ, ресиверы значения и указателя.
  4. Подтяните ошибки. Оборачивание %w, errors.Is/As, sentinel-ошибки, panic/recover.
  5. Освойте дженерики на базовом уровне и понимайте, когда они уместнее интерфейса.
  6. Решайте вслух и запускайте с -race: проговаривайте план, разбор случаев и сложность — именно это оценивают.
13 · FAQ

Частые вопросы

Что чаще всего спрашивают на собеседовании по Go?

Конкурентность (горутины, каналы, sync, context), устройство слайсов и мап, интерфейсы, обработка ошибок и defer, а также идиоматичные подводные камни. На конкурентность приходится большинство вопросов и задач.

Чем горутина отличается от потока ОС?

Горутина легче: стартовый стек — пара килобайт против мегабайтов у потока, а управляет ими рантайм Go, мультиплексируя тысячи горутин на горстку потоков через модель G-M-P. Переключение дешёвое и происходит в пользовательском пространстве.

Нужны ли дженерики на Go-собеседовании в 2026?

Да, на базовом уровне: параметры типа, ограничения any и comparable, понимание, когда дженерики уместнее интерфейса или дублирования кода. Глубокой теории обычно не требуют, но владение ждут.

Как подготовиться к собеседованию по Go?

Двигайтесь по частоте тем: сначала конкурентность и ловушки, затем внутренности (слайсы, интерфейсы, escape-анализ) и ошибки. Решайте задачи вслух и запускайте с -race, а не «в уме».

Дают ли задачи на Go-собеседовании?

Да, почти всегда лайвкодинг: пулы воркеров и пайплайны на каналах, синхронизация, а также сниппеты «что выведет код» на слайсах, интерфейсах и defer. Оценивают не только результат, но и ход мысли.

Коротко

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

// ENIGMA

Прогоните собеседование перед реальным

Тренажёр с реальными вопросами по Go, разбор ответов и слабых мест — чтобы прийти на секцию готовым.

Нейросеть для собеседования →
ENIGMA

Нейросеть для собеседований и подготовки к интервью — подсказки в реальном времени и тренажёр: enigmai.ru

// материал носит справочный характер · цифры в графиках иллюстративные

Похожие статьи

Все статьи