---
title: "Собеседование Junior Golang: базовые вопросы с ответами"
description: "Базовые вопросы на собеседовании Junior Golang: var и :=, срезы и массивы, nil-карты, defer, ошибки, структуры и методы, интерфейсы — с примерами кода."
url: "https://golangmaster.ru/interview/junior-voprosy/"
published: "2026-09-19"
updated: "2026-09-21"
level: "junior"
questions: 10
---

# Собеседование Junior Golang: базовые вопросы с ответами

*Уровень: junior. ~20 минут, 10 вопросов. Источник: https://golangmaster.ru/interview/junior-voprosy/*

Junior-собеседование по Go проверяет базу: объявление переменных, срезы и карты, defer, обработку ошибок, структуры и интерфейсы. Ниже — типовые вопросы с ответами, которые ждут от джуна. Если по ходу понимаете, что какая-то тема плавает, — пройдите её в [базовом курсе Go](/courses/go-basics/): каждая тема — отдельный урок с запуском кода в браузере.

## Чем var отличается от := ?

Оба объявляют переменную, но := — короткое объявление: работает только внутри функций, требует хотя бы одной новой переменной слева и выводит тип из правой части. var работает везде, включая уровень пакета, позволяет указать тип явно и объявить без инициализации (получится нулевое значение). Внутри функций обычно пишут :=, var оставляют для пакетного уровня и случаев, когда тип важнее значения: var r io.Reader.

```go
var port int          // 0, уровень пакета — только var
name := "ГоМастер"    // короткое объявление внутри функции
var r io.Reader       // тип важнее значения
```

## Чем срез отличается от массива?

Массив — значение фиксированной длины: [3]int влезает в тип, копируется целиком при присваивании и передаётся по значению. Срез — «окно» над массивом: структура из указателя на данные, длины len и ёмкости cap. Срез растёт через append (при нехватке cap выделяется новый массив и данные копируются), передаётся дёшево, и именно срезы в Go используются повсеместно. Вопрос-ловушка рядом: после append внутри функции вызывающий может не увидеть новые элементы, если произошёл реаллокация и срез указывает на старый массив — поэтому append перезаписывают в ту же переменную: s = append(s, x).

```go
a := [3]int{1, 2, 3} // массив: длина — часть типа
s := []int{1, 2, 3}  // срез: len=3, cap=3
s = append(s, 4)     // cap вырос, данные скопированы в новый массив
```

## Что будет при записи в nil-карту? А чтение?

Запись в nil-карту паникует: «assignment to entry in nil map». Чтение из nil-карты безопасно и возвращает нулевое значение: карта обязана быть «читаемой» без инициализации. Правильное создание — make(map[string]int) или литерал с элементами. Идиома comma-ok: v, ok := m[key] — ok отличает «ключ со значением-нулём» от «ключа нет». Карта не потокобезопасна: для конкурентного доступа — sync.Mutex вокруг или sync.Map для узких сценариев read-heavy.

```go
m := map[string]int{"a": 1}
v := m["нет"]          // 0, паники нет
v, ok := m["a"]        // 1, true
// m2["x"] = 1 // паника: m2 == nil
```

## Что делает defer? В каком порядке выполняются отложенные вызовы?

defer откладывает вызов функции до выхода из объемлющей функции — независимо от того, вышли по return, панике или обычному пути. Отложенные вызовы выполняются в порядке LIFO — стек: последний defer сработает первым. Аргументы вычисляются в момент записи defer, а не в момент вызова. Типовое применение — освобождение ресурсов: defer f.Close(), defer mu.Unlock(), defer recover() в обёртках. Ловушка в цикле: defer внутри цикла накопится до конца функции; если файлы нужно закрывать сразу — выносите тело цикла в функцию.

```go
func demo() {
    defer fmt.Println("первый defer")
    defer fmt.Println("второй defer")
    fmt.Println("тело")
}
// тело → второй defer → первый defer
```

## Как в Go обрабатывают ошибки? Что такое оборачивание ошибок?

Ошибки — значения: функция возвращает (результат, error), и вызывающий обязан проверять if err != nil. Ошибку создают errors.New или fmt.Errorf, оборачивают через %w: fmt.Errorf("читаю конфиг: %w", err) — так сохраняется исходная ошибка, и её можно распаковать через errors.Is (сравнение с sentinel-ошибкой, например io.EOF) и errors.As (извлечение конкретного типа, например *fs.PathError). Исключений в Go нет: панику используют для программных ошибок программиста, а не для ожидаемых сбоев — «файл не найден» это ошибка, а не паника.

```go
data, err := os.ReadFile(path)
if err != nil {
    return fmt.Errorf("читаю конфиг %s: %w", path, err)
}
if errors.Is(err, fs.ErrNotExist) { /* своя политика */ }
```

## Нулевые значения — что это и зачем?

В Go нет «неинициализированных» переменных: каждая имеет нулевое значение своего типа — 0 для чисел, false для bool, пустая строка для string, nil для указателей, срезов, карт, каналов, интерфейсов и функций. Это убирает целый класс багов с чтением мусорной памяти и делает структуры пригодными к использованию сразу после объявления. Знать нулевые значения обязательно: срез nil — len 0 и append работает, карта nil — читать можно, писать нельзя, интерфейс nil — вызов метода на nil-интерфейсе паникует.

## Чем метод с приёмником-значением отличается от приёмника-указателя?

Метод с приёмником-значением работает с копией: изменения поля внутри метода не видны снаружи, вызов на больших структурах копирует данные. Приёмник-указатель получает адрес и может менять оригинал. Правило выбора: указатель — если метод мутирует состояние, структура большая или в типе уже есть хотя бы один указательный метод (смешивать нежелательно: набор методов типа определяется всеми его методами сразу). На значения-типы вроде time.Duration указательные методы не нужны — они неизменяемы.

```go
type Counter struct{ n int }
func (c *Counter) Inc() { c.n++ }      // указатель: меняет оригинал
func (c Counter) Value() int { return c.n } // значение: читает копию
```

## Что такое интерфейс и что такое неявная реализация?

Интерфейс — набор методов; тип реализует интерфейс просто потому, что у него есть все нужные методы, — без слова implements и без регистрации. Это позволяет объявлять интерфейс там, где он используется, а не там, где тип. Классика: io.Reader и io.Writer — любой тип с правильными сигнатурами встаёт в цепочку чтения. Специальный случай — пустой интерфейс any (interface{}): ему удовлетворяет любой тип; в новом коде для произвольных значений предпочитают any и дженерики, а не пустой интерфейс.

## Как работает строка: что вернёт len("привет")?

Строка в Go — неизменяемая последовательность байт в UTF-8. len возвращает число байт, а не символов: len("привет") — 12, потому что каждая кириллическая буква кодируется двумя байтами. Символ — это руна (кодовая точка Unicode, int32); перебор по range отдаёт именно руны с их индексами в байтах, а s[i] — отдельный байт. Обрезка s[:3] может разрезать букву посередине. Для «количества символов» — utf8.RuneCountInString(s).

```go
s := "привет"
fmt.Println(len(s))                    // 12 байт
fmt.Println(utf8.RuneCountInString(s)) // 6 рун
```

## Что выведет iota и где её применяют?

iota — счётчик const-блока: в первой строке он 0 и растёт на каждой следующей строке. Типовое применение — перечисления и битовые маски. Вопрос часто идёт с трюком: iota сбрасывается в каждом новом const-блоке и «замораживается», если строка без выражения повторяет предыдущее. Уметь прочитать такой блок — часть вопроса.

```go
type Level int
const (
    New Level = iota // 0
    Active           // 1
    Blocked          // 2
)
const (
    KB = 1 << (10 * (iota + 1)) // 1024
    MB                          // 1048576
)
```
