Перейти к содержимому

Roadmap: Go-разработчик уровня Junior (0 → Junior), 2025-2026

Подготовка к собеседованиям в РФ/СНГ. Версия Go: 1.22+ (актуально для 1.24/1.25). Зарплатная вилка Junior в РФ: 100-160k₽ (Москва/СПб до 160k₽, регионы 80-130k₽).


Примитивы:

  • Целые: int, int8/16/32/64, uint, uint8/16/32/64, uintptr, byte (= uint8), rune (= int32)
  • Вещественные: float32, float64
  • Комплексные: complex64, complex128
  • Логический: bool (true/false)
  • Строки: string (immutable, набор байт UTF-8)
  • int зависит от платформы — 32 или 64 бита

Составные:

  • Массивы ([5]int) — фиксированной длины, размер часть типа
  • Слайсы ([]int) — динамические, header (ptr, len, cap)
  • Карты (map[K]V) — хеш-таблицы
  • Структуры (struct)
  • Каналы (chan T)
  • Интерфейсы (interface{} / any начиная с 1.18)
  • Функции как тип
  • Указатели (*T)

Zero values (значения по умолчанию):

int0
float640.0
boolfalse
string"" (пустая строка)
pointernil
slicenil (len=0, cap=0)
mapnil
channelnil
interfacenil
structвсе поля в zero values
// Объявление переменных
var x int = 10
var y = 10 // тип выводится
z := 10 // short declaration, только внутри функций
var a, b int = 1, 2
// Константы
const Pi = 3.14
const (
StatusOK = 200
StatusBadRequest = 400
)
// iota — генератор последовательных значений
const (
Sunday = iota // 0
Monday // 1
Tuesday // 2
Wednesday // 3
)
// iota с битовыми флагами
const (
Read = 1 << iota // 1
Write // 2
Execute // 4
)

Важно: := создаёт новую переменную, = присваивает. Минимум одна новая переменная должна быть слева от :=.

  • Арифметические: +, -, *, /, %
  • Сравнения: ==, !=, <, >, <=, >=
  • Логические: &&, ||, !
  • Битовые: &, |, ^, <<, >>, &^ (AND NOT)

Циклы (только for!):

// Классический
for i := 0; i < 10; i++ { }
// while
for x < 10 { x++ }
// бесконечный
for { break }
// range
for i, v := range slice { }
for k, v := range myMap { }
for i, c := range "hello" { } // c — rune, i — байтовый индекс
// Go 1.22+: range по числу
for i := range 10 { } // 0..9

Условия:

if err := doSomething(); err != nil {
return err
}
switch x {
case 1, 2, 3:
fmt.Println("small")
case 4, 5:
fmt.Println("medium")
default:
fmt.Println("other")
}
// switch без переменной
switch {
case x < 0: ...
case x == 0: ...
}
// Type switch
switch v := i.(type) {
case int: ...
case string: ...
}

В Go нет while, do-while, тернарного оператора (?:).

// Обычная функция
func Add(a, b int) int { return a + b }
// Множественный возврат
func Divide(a, b float64) (float64, error) {
if b == 0 { return 0, errors.New("div by zero") }
return a / b, nil
}
// Named return values
func Split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return // "naked return"
}
// Variadic — переменное число аргументов
func Sum(nums ...int) int {
total := 0
for _, n := range nums { total += n }
return total
}
Sum(1, 2, 3)
slice := []int{1, 2, 3}
Sum(slice...) // распаковка
// defer — выполнится при выходе из функции (LIFO)
func main() {
defer fmt.Println("3")
defer fmt.Println("2")
defer fmt.Println("1") // выведет: 1, 2, 3 → но в обратном порядке!
}
// Функция как значение
add := func(a, b int) int { return a + b }

Подвох: аргументы defer вычисляются сразу, а функция выполняется при возврате:

i := 1
defer fmt.Println(i) // выведет 1, не 2
i = 2
  • Пакет — каталог с .go файлами с одинаковой директивой package
  • package main + func main() — исполняемая программа
  • Видимость: идентификаторы с заглавной буквы (PascalCase) экспортируются, со строчной (camelCase) — приватные для пакета
  • Импорт: import "fmt", групповой импорт через скобки
  • Алиасы: import f "fmt", blank import: import _ "package" (для side effects)
  • init() — выполняется при загрузке пакета
package main
import (
"fmt"
"net/http"
json "encoding/json" // алиас
_ "github.com/lib/pq" // только side effects
)
type User struct {
ID int
Name string
Email string
}
// Создание
u1 := User{1, "Alice", "a@e.com"} // позиционно (не рекомендуется)
u2 := User{ID: 1, Name: "Alice"} // именованно (рекомендуется)
u3 := &User{Name: "Bob"} // указатель
u4 := new(User) // указатель с zero values
// Методы
func (u User) Greet() string { // value receiver
return "Hello, " + u.Name
}
func (u *User) Rename(name string) { // pointer receiver — может изменить
u.Name = name
}
// Встраивание (composition)
type Admin struct {
User // встроенный тип
Permissions []string
}
a := Admin{User: User{Name: "Eve"}}
a.Greet() // методы User промотированы
  • &x — взять адрес переменной
  • *p — разыменовать указатель (получить значение)
  • *T — тип “указатель на T”
  • nil — нулевое значение указателя
x := 10
p := &x
fmt.Println(*p) // 10
*p = 20
fmt.Println(x) // 20

Когда использовать указатели:

  1. Нужно мутировать значение (func (u *User) SetName(...))
  2. Большая структура (избежать копирования)
  3. Чтобы отличить “значение не задано” (nil) от zero value
  4. Реализовать nil-safe методы

Когда НЕ использовать:

  • Маленькие immutable значения (int, bool, string)
  • Когда мутация нежелательна
  • Map, slice, chan уже содержат внутренние указатели

Массив — фиксированный размер, размер часть типа, передаётся по значению (копируется):

var a [3]int = [3]int{1, 2, 3}
b := a // полная копия!

Слайс — структура из 3 слов: (ptr, len, cap). Передаётся по значению (копируется header), но указывает на тот же базовый массив.

s := []int{1, 2, 3} // len=3, cap=3
s = append(s, 4) // len=4, cap=6 (рост в 2 раза)
s2 := s[1:3] // len=2, cap=5 (share underlying array!)
s3 := make([]int, 5, 10) // len=5, cap=10

Рост capacity при append:

  • До 256 элементов — рост ×2
  • После 256 — рост постепенно снижается до ×1.25
  • Если cap превышен — создаётся новый базовый массив

Подвохи слайсов (ОБЯЗАТЕЛЬНО ЗНАТЬ ДЛЯ СОБЕСА):

// 1. Share underlying array
a := []int{1, 2, 3, 4, 5}
b := a[1:3]
b[0] = 99
// a теперь [1, 99, 3, 4, 5]
// 2. append может или не может изменить оригинал
func add(s []int) { s = append(s, 999) }
x := []int{1, 2, 3}
add(x)
// x остался [1, 2, 3]! header скопирован, append вернул новый slice
// 3. nil slice vs empty slice
var s1 []int // nil, len=0, cap=0
s2 := []int{} // не nil, len=0, cap=0
s3 := make([]int,0) // не nil, len=0, cap=0
// append работает с nil!
// 4. Memory leak: маленький slice держит большой массив
big := make([]int, 1_000_000)
small := big[:3] // small держит весь массив!
// решение: small = append([]int(nil), big[:3]...)

copy():

src := []int{1, 2, 3}
dst := make([]int, 3)
n := copy(dst, src) // вернёт min(len(dst), len(src))
m := make(map[string]int)
m["a"] = 1
v, ok := m["b"] // v=0, ok=false (idiomatic check)
delete(m, "a")
len(m)
// Литерал
m2 := map[string]int{"a": 1, "b": 2}
// Итерация (порядок СЛУЧАЙНЫЙ!)
for k, v := range m2 { }

Внутреннее устройство (ОБЯЗАТЕЛЬНО ЗНАТЬ):

  • Хеш-таблица с separate chaining через overflow buckets
  • Каждый bucket хранит 8 пар key-value
  • При load factor > 6.5 — растёт ×2
  • Использует AES-хеш на современных CPU
  • Порядок итерации намеренно рандомизирован (предотвращает зависимости от порядка)
  • В Go 1.24+ — реализация на Swiss Tables (быстрее, меньше памяти)

Нюансы:

  • nil map: чтение возвращает zero value, запись → panic
  • Запись/чтение в карту НЕ потокобезопасны — нужен sync.Mutex или sync.Map
  • Нельзя взять адрес значения в карте: &m["key"] — ошибка компиляции
  • Ключом может быть любой comparable тип (нельзя slice, map, function)
  • string — readonly слайс байт в UTF-8
  • byte = uint8 (один байт)
  • rune = int32 (один Unicode code point)
  • len(s) возвращает кол-во байт, не символов!
s := "Привет"
fmt.Println(len(s)) // 12 (в кириллице 2 байта на букву)
fmt.Println(utf8.RuneCountInString(s)) // 6
// Итерация по байтам
for i := 0; i < len(s); i++ { fmt.Printf("%x ", s[i]) }
// Итерация по руническим символам
for i, r := range s {
fmt.Printf("%d:%c\n", i, r)
}
// Конвертации
b := []byte(s) // в байты
r := []rune(s) // в руны
s2 := string(r) // обратно
// Конкатенация
result := strings.Builder{}
result.WriteString("hello")
result.WriteString(" world")
final := result.String()

Подвох: s[0] — это байт, не символ. Для кириллицы s[0] будет половиной буквы.

Error — это интерфейс:

type error interface {
Error() string
}

Идиоматический подход:

func ReadFile(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read %s: %w", path, err) // wrap!
}
return data, nil
}

Создание ошибок:

errors.New("not found")
fmt.Errorf("user %d not found", id)
fmt.Errorf("read failed: %w", err) // wrapping (Go 1.13+)

Sentinel errors:

var ErrNotFound = errors.New("not found")
// в вызывающем коде:
if errors.Is(err, ErrNotFound) { ... }

Custom error types:

type ValidationError struct {
Field string
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}
// Проверка:
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Println(ve.Field)
}

errors.Is vs errors.As:

  • errors.Is(err, target) — проверка на конкретное значение (sentinel)
  • errors.As(err, &target) — проверка типа + извлечение

panic/recover:

defer func() {
if r := recover(); r != nil {
log.Printf("recovered: %v", r)
}
}()
panic("something bad")

Использовать panic только в неисправимых ситуациях — обычные ошибки возвращать через error.


  • Лёгкие потоки (~2-4 KB стек, растёт динамически)
  • Управляются рантаймом Go, не ОС
  • Запуск: go funcCall()
  • Модель: G (goroutine), M (machine = OS thread), P (processor = scheduler context)
  • При блокирующем системном вызове M отвязывается от P, рантайм создаёт/берёт нового M
go func() {
fmt.Println("hello from goroutine")
}()

Отличие от потоков:

GoroutineThread (OS)
~2 KB старт~1-8 MB
Стек растётФикс. размер
Управляется GoУправляется ОС
Контекст ~nsКонтекст ~µs
Можно запустить млнТысячи max
ch := make(chan int) // небуферизованный (синхронный)
ch := make(chan int, 10) // буферизованный (асинхронный до cap)
ch <- 42 // отправка
v := <-ch // приём
v, ok := <-ch // ok=false если канал закрыт и пуст
close(ch) // закрытие

Поведение каналов (ВАЖНО):

Операцияnil chanopen chan (empty)open chan (full)closed chan
sendблокируется навсегдапишетблокируетсяpanic!
receiveблокируется навсегдаблокируетсячитаетвозвращает zero, ok=false
closepanic!OKOKpanic!

Принципы:

  • Закрывать должен отправитель, не получатель
  • Не закрывать канал из нескольких горутин (паника)
  • Чтение из закрытого канала всегда возвращает zero value
select {
case v := <-ch1:
fmt.Println("from ch1:", v)
case ch2 <- 42:
fmt.Println("sent to ch2")
case <-time.After(time.Second):
fmt.Println("timeout")
default:
fmt.Println("non-blocking")
}
  • Выбирает готовую операцию случайно (если несколько готовы)
  • default — делает select неблокирующим
  • Без default и без готовых веток — блокируется
  • Без веток (select {}) — deadlock навсегда
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
// работа
}(i)
}
wg.Wait()

Подвох: wg.Add(1) должен быть до go, не внутри горутины!

var mu sync.Mutex
mu.Lock()
defer mu.Unlock()
// критическая секция
var rw sync.RWMutex
rw.RLock() // много читателей одновременно
v := data[key]
rw.RUnlock()
rw.Lock() // один писатель
data[key] = v
rw.Unlock()
  • Не копировать! Mutex нельзя копировать после использования (go vet ругается).
  • Не лочить дважды (deadlock).
  • Используется по значению как поле структуры — но передавать структуру через указатель.
var once sync.Once
once.Do(func() {
// выполнится ровно один раз во всех горутинах
})

Fan-out — один писатель → N воркеров:

jobs := make(chan int, 100)
for w := 1; w <= 3; w++ {
go worker(w, jobs)
}
for j := 1; j <= 5; j++ { jobs <- j }
close(jobs)

Fan-in — N писателей → один консьюмер (через merge):

func merge(cs ...<-chan int) <-chan int {
var wg sync.WaitGroup
out := make(chan int)
output := func(c <-chan int) {
defer wg.Done()
for v := range c { out <- v }
}
wg.Add(len(cs))
for _, c := range cs { go output(c) }
go func() { wg.Wait(); close(out) }()
return out
}

Worker pool:

type Job struct { ID int }
type Result struct { ID, Output int }
func worker(jobs <-chan Job, results chan<- Result) {
for j := range jobs {
results <- Result{ID: j.ID, Output: j.ID * 2}
}
}

Pipeline:

func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums { out <- n }
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for n := range in { out <- n * n }
}()
return out
}
// Использование: square(gen(1,2,3,4))

Race возникает, когда минимум 2 горутины обращаются к одной переменной, и хотя бы одна пишет.

Детектор:

Окно терминала
go run -race main.go
go test -race ./...
go build -race

Решения: sync.Mutex, каналы, sync/atomic.


fmt.Println("hello", x)
fmt.Printf("name=%s age=%d\n", name, age)
fmt.Sprintf("%v", x) // format в string
fmt.Errorf("wrap: %w", err)
// Глаголы:
%v // значение по умолчанию
%+v // структуры с именами полей
%#v // Go-синтаксис
%T // тип
%d // int
%s // string
%q // строка в кавычках
%x // hex
%f // float
%t // bool
%p // указатель
%w // wrap error (только в fmt.Errorf)
strings.Contains("hello", "ell")
strings.Split("a,b,c", ",")
strings.Join([]string{"a","b"}, "-")
strings.ReplaceAll(s, "old", "new")
strings.ToLower(s), strings.ToUpper(s)
strings.TrimSpace(s)
strings.HasPrefix(s, "go")
strconv.Itoa(42) // "42"
strconv.Atoi("42") // 42, nil
strconv.ParseFloat("3.14", 64)
strconv.FormatFloat(3.14, 'f', 2, 64)
now := time.Now()
later := now.Add(5 * time.Minute)
diff := later.Sub(now) // Duration
time.Sleep(100 * time.Millisecond)
now.Format("2006-01-02 15:04:05") // эталонная дата!
time.Parse("2006-01-02", "2025-05-21")
// Таймеры
t := time.NewTimer(time.Second)
<-t.C
// Тикер
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for range ticker.C { ... }
data, err := os.ReadFile("file.txt")
err = os.WriteFile("out.txt", data, 0644)
f, err := os.Open("file.txt")
defer f.Close()
// bufio для построчного чтения
sc := bufio.NewScanner(f)
for sc.Scan() {
line := sc.Text()
}
// os.Args
fmt.Println(os.Args) // [program, arg1, arg2]
os.Getenv("HOME")
os.Setenv("KEY", "val")
os.Exit(1)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"` // не сериализуется если пустой
pw string `json:"-"` // лоукерс + неэкспорт = ignore
Pass string `json:"-"` // явный ignore
}
// Marshal: struct → JSON
data, err := json.Marshal(user)
data, err := json.MarshalIndent(user, "", " ") // с отступами
// Unmarshal: JSON → struct
var u User
err := json.Unmarshal(data, &u)
// В map для неизвестной структуры
var m map[string]interface{}
json.Unmarshal(data, &m)
// Encoder/Decoder для потоков
enc := json.NewEncoder(w)
enc.Encode(user)
dec := json.NewDecoder(r)
dec.Decode(&user)

Подвох: только экспортируемые (PascalCase) поля попадают в JSON.

Сервер:

func main() {
http.HandleFunc("/", helloHandler)
http.HandleFunc("/api/users", usersHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, world!")
}
// Go 1.22+ — паттерны с методами и wildcards
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", getUserHandler)
mux.HandleFunc("POST /users", createUserHandler)
func getUserHandler(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
// ...
}

Middleware:

func logMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}

Клиент:

resp, err := http.Get("https://api.example.com/users")
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
// С таймаутом
client := &http.Client{Timeout: 5 * time.Second}
req, _ := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(jsonBody))
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)

Используется для cancellation, timeout, и передачи request-scoped значений.

// Базовые
ctx := context.Background() // корневой
ctx = context.TODO() // если не уверен
// Cancellation
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go worker(ctx)
cancel() // отменит ctx и всех потомков
// Timeout
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
// Deadline
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(5*time.Second))
// Values (использовать только для request-scoped данных, не для параметров!)
ctx = context.WithValue(ctx, userIDKey, 42)
id := ctx.Value(userIDKey).(int)
// Использование
func work(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err() // context.Canceled или context.DeadlineExceeded
case <-time.After(time.Second):
return nil
}
}

Правила:

  • ctx — всегда первый параметр функции
  • НЕ хранить в структуре (передавать явно)
  • ВСЕГДА вызывать cancel() (через defer)
  • Передавать в БД-запросы, HTTP-запросы, etc.
// файл math_test.go
package math
import "testing"
func TestAdd(t *testing.T) {
got := Add(2, 3)
want := 5
if got != want {
t.Errorf("Add(2,3) = %d; want %d", got, want)
}
}
// Table-driven test (стандарт идиоматический)
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
want int
}{
{"positive", 2, 3, 5},
{"zero", 0, 0, 0},
{"negative", -1, 1, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Add(tt.a, tt.b); got != tt.want {
t.Errorf("got %d, want %d", got, tt.want)
}
})
}
}
// Benchmark
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ { Add(2, 3) }
}

Запуск:

Окно терминала
go test ./... # все тесты
go test -v ./... # verbose
go test -run TestAdd ./... # конкретный
go test -race ./... # race detector
go test -cover ./... # coverage
go test -bench=. ./... # бенчмарки

testify (популярная сторонняя библиотека):

import "github.com/stretchr/testify/assert"
assert.Equal(t, expected, actual)
assert.NoError(t, err)
assert.Nil(t, obj)

Окно терминала
go mod init github.com/user/project # создать модуль
go mod tidy # подчистить зависимости
go mod download # скачать
go get github.com/foo/bar@v1.2.3 # добавить/обновить
go get -u ./... # обновить всё
go run main.go # запуск
go build # сборка
go build -o myapp ./cmd/server # с именем
go install # build + поместить в $GOPATH/bin
go test ./... # тесты
go test -v -race -cover ./...
go vet ./... # статанализ
gofmt -w . # форматирование
go fmt ./...
goimports -w . # + сортировка импортов
go doc fmt.Println # документация
go env # переменные окружения

go.mod пример:

module github.com/user/myapp
go 1.22
require (
github.com/gin-gonic/gin v1.10.0
github.com/stretchr/testify v1.9.0
)

go.sum — хеши версий, не редактировать руками.

GoLand (JetBrains, платный):

  • Лучшая поддержка Go
  • Встроенный отладчик, рефакторинг
  • Студенческая лицензия бесплатно

VS Code + Go extension (бесплатно):

  • Расширение golang.go
  • Использует gopls, delve
  • Меньше “из коробки”, но настраивается

golangci-lint v2 (актуальный стандарт):

Окно терминала
brew install golangci-lint
golangci-lint run ./...
golangci-lint fmt # форматер v2

Конфиг .golangci.yml:

version: "2"
linters:
default: none
enable:
- errcheck
- staticcheck
- govet
- ineffassign
- gosimple
- revive
- gosec
- misspell
- unused

Delve (dlv):

Окно терминала
go install github.com/go-delve/delve/cmd/dlv@latest
dlv debug ./cmd/server # запуск
(dlv) break main.main # точка останова
(dlv) continue
(dlv) next # next line
(dlv) step # step in
(dlv) print x # значение переменной
(dlv) goroutines
(dlv) bt # стек

Удобнее всего отлаживать в GoLand или VS Code (через дебаггер).

  • pprof — профайлинг CPU/память
  • air — hot reload для разработки
  • godoc — документация
  • sqlc — генерация Go из SQL
  • mockery / mockgen — генерация моков

Must-know команды:

Окно терминала
git init / git clone <url>
git status
git add . / git add file.go
git commit -m "feat: add user endpoint"
git push origin main
git pull --rebase
# Ветки
git branch
git checkout -b feature/auth # или git switch -c
git switch main
# Merge / rebase
git merge feature/auth
git rebase main
# Конфликты — разрешать в редакторе, потом
git add <file>
git rebase --continue
# История
git log --oneline --graph
git diff
git diff --staged
git stash
git stash pop
# Откатить
git reset HEAD~1 # отменить коммит, сохранить файлы
git reset --hard HEAD~1 # ОПАСНО: уничтожить изменения
git revert <commit> # создать обратный коммит

Знать концептуально:

  • HEAD, branches, tags
  • Fast-forward vs merge commit
  • Rebase vs merge (когда что)
  • Что такое pull request / merge request
  • Conventional Commits (feat:, fix:, docs:, chore:)
-- SELECT
SELECT id, name FROM users WHERE age > 18 ORDER BY name LIMIT 10;
-- JOIN
SELECT u.name, o.amount
FROM users u
INNER JOIN orders o ON o.user_id = u.id
WHERE o.created_at > '2025-01-01';
-- LEFT JOIN, RIGHT JOIN, INNER JOIN, FULL OUTER JOIN — понимать разницу
-- Aggregation
SELECT user_id, COUNT(*), SUM(amount), AVG(amount)
FROM orders
GROUP BY user_id
HAVING SUM(amount) > 1000;
-- DML
INSERT INTO users (name, email) VALUES ('Alice', 'a@e.com');
UPDATE users SET name = 'Bob' WHERE id = 1;
DELETE FROM users WHERE id = 1;
-- DDL
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE
);
-- Индексы
CREATE INDEX idx_users_email ON users(email);

Должен знать:

  • INNER vs LEFT/RIGHT/OUTER JOIN
  • Что такое индекс, когда помогает (поиск, JOIN), когда тормозит (вставки)
  • Транзакции (BEGIN, COMMIT, ROLLBACK)
  • Уровни изоляции (READ COMMITTED, REPEATABLE READ, SERIALIZABLE) — базово
  • Primary key, foreign key, unique constraint
  • N+1 проблема
  • EXPLAIN / EXPLAIN ANALYZE — базовое понимание
  • Методы: GET (получить), POST (создать), PUT (заменить), PATCH (обновить), DELETE (удалить)
  • Статус-коды:
    • 2xx: 200 OK, 201 Created, 204 No Content
    • 3xx: 301, 302, 304
    • 4xx: 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 422 Unprocessable Entity, 429 Too Many Requests
    • 5xx: 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable
  • Headers: Content-Type, Authorization, Accept, User-Agent
  • Идемпотентность (GET/PUT/DELETE — да, POST — нет)
  • REST принципы: ресурсы, stateless, единообразный интерфейс
  • JSON, query params (?name=alice&age=20), path params (/users/{id}), request body
  • Cookies, JWT (что это, как используется)
{
"id": 1,
"name": "Alice",
"tags": ["admin", "user"],
"address": {"city": "Moscow"},
"active": true,
"score": null
}

Знать: типы (string, number, boolean, null, object, array), синтаксис, отличия от YAML/XML.

Окно терминала
# Навигация
pwd, cd, ls, ls -la, cd ..
# Файлы
cat file, less file, head, tail, tail -f log.txt
cp, mv, rm, rm -rf
touch, mkdir, mkdir -p
# Поиск
find . -name "*.go"
grep "pattern" file.txt
grep -r "TODO" .
# Права
chmod +x script.sh
chown user:group file
# Процессы
ps aux
top, htop
kill -9 <pid>
nohup ./app &
# Сеть
curl, wget
ping, netstat -tulpn, ss -tulpn
# Pipes & redirects
cmd1 | cmd2
cmd > file
cmd >> file
cmd 2>&1
cmd < file
# vim/nano (базовый редактор)
# ssh, scp
ssh user@host
scp file.txt user@host:/path

6. Типичные вопросы на собеседовании Junior Go (50+ вопросов с ответами)

Заголовок раздела «6. Типичные вопросы на собеседовании Junior Go (50+ вопросов с ответами)»

1. Что такое zero value и какие zero values у разных типов? Значение по умолчанию. int→0, string→"", bool`→false, pointer/slice/map/chan/interface/func→nil.

2. Разница между var x int = 0, x := 0, var x int? Первое и второе создают переменную с значением 0. Третье — то же, но без явной инициализации (zero value). := работает только внутри функций.

3. Что такое iota? Идентификатор-счётчик в const блоке, начинается с 0 и увеличивается на 1 на каждой строке.

4. В чём разница между массивом и слайсом? Массив имеет фиксированную длину, длина — часть типа, передаётся по значению (полная копия). Слайс — это структура (ptr, len, cap) поверх массива, динамический, при передаче копируется header, но базовый массив общий.

5. Что внутри слайса (slice header)? Три поля: указатель на underlying array, длина (len), вместимость (cap). На 64-битной системе размер 24 байта.

6. Как растёт capacity слайса при append? До 256 — ×2. После — постепенно снижается до ×1.25. Если cap превышен — создаётся новый массив, данные копируются.

7. Чем отличается nil slice от empty slice? var s []int — nil slice, s == nil → true. s := []int{} или make([]int, 0) — не nil. Оба имеют len=0, cap=0. append работает с nil.

8. Подвох с append:

func modify(s []int) { s = append(s, 999) }
x := []int{1,2,3}
modify(x)
// x НЕ изменился, потому что header скопирован, и если cap был превышен, append создал новый массив

9. Что такое map в Go? Как устроен? Хеш-таблица с separate chaining через overflow buckets. Каждый bucket хранит 8 пар. При load factor > 6.5 удваивается. С Go 1.24 — Swiss Tables.

10. Почему порядок итерации map случайный? Намеренно — чтобы программисты не зависели от порядка (порядок не часть спеки). Реализовано через случайный стартовый индекс.

11. Как проверить, есть ли ключ в map?

v, ok := m["key"]
if ok { /* есть */ }

12. Что такое rune и byte? byte = alias для uint8 (1 байт). rune = alias для int32, представляет Unicode code point. Строка — слайс байт, но в range — итерация по рунам.

13. Почему len("Привет") возвращает 12, а не 6? Потому что len возвращает кол-во байт. Кириллица в UTF-8 — 2 байта на букву. Для подсчёта символов: utf8.RuneCountInString(s).

14. Что такое pointer в Go? Когда использовать? Адрес в памяти. &x — взять адрес, *p — разыменовать. Используем для (1) мутации значения, (2) избежать копирования больших структур, (3) реализации nil-safe методов.

15. Разница между value receiver и pointer receiver у методов? Value — копия структуры, метод не может изменить оригинал. Pointer — указатель, может изменить. Если структура большая или мутирующая — pointer receiver. Внутри одного типа лучше быть консистентным.

16. Что такое interface в Go? Тип, описывающий набор методов. Реализация неявная (duck typing): если тип реализует все методы интерфейса — он его удовлетворяет. Никакого implements.

17. Чем интерфейс отличается от структуры? Структура — данные с полями. Интерфейс — контракт поведения (методы). Структура реализует интерфейс, имея его методы.

18. Что такое empty interface interface{} / any? Интерфейс без методов — все типы его реализуют. Используется когда тип неизвестен заранее. С Go 1.18 есть алиас any.

19. Что такое type assertion и type switch?

v, ok := i.(string) // assertion
switch v := i.(type) { // switch
case int: ...
case string: ...
}

20. Объясните typed nil interface gotcha:

var p *MyError = nil
var err error = p
fmt.Println(err == nil) // false!

Интерфейс = (тип, значение). Тип установлен (*MyError), значит интерфейс не nil, даже если значение nil. Поэтому возвращать стоит nil напрямую, а не nil-pointer типизированный.

21. Что такое embedding (встраивание) и зачем? Включение одного типа в другой без явного поля → composition. Методы и поля встроенного типа промотируются. Заменяет наследование.

22. Как реализовано ООП в Go? Нет классов, наследования, перегрузки. Есть: структуры (данные), методы (привязаны к типу), интерфейсы (полиморфизм), embedding (композиция). Используется композиция вместо наследования.

23. Что такое defer и как он работает? Откладывает вызов функции до выхода из текущей функции. Несколько defer — LIFO. Аргументы вычисляются при объявлении, функция — при выходе. Используется для cleanup: defer f.Close(), defer mu.Unlock().

24. Что такое panic и recover? panic — прерывает выполнение, раскручивает стек, выполняя defer. recover (только в defer) — перехватывает panic. Использовать только в исключительных случаях, не как замену error.

25. Что такое named return values?

func div(a, b int) (result int, err error) {
result = a / b
return // вернёт result, err
}

Может улучшать читаемость, но не злоупотреблять.

26. Что такое goroutine? Лёгкий поток (~2 KB), управляемый рантаймом Go (модель G-M-P). Запуск: go funcCall(). Можно запустить миллионы.

27. Чем goroutine отличается от потока ОС? Goroutine — пользовательский уровень, маленький стек (динамически растёт), переключение в Go-рантайме. Потоки ОС — большие стеки, переключение через ядро.

28. Что такое канал? Зачем нужен? Типизированная труба для передачи данных между горутинами. “Не общайтесь через память — общайтесь через каналы” (motto). Синхронизирует горутины.

29. Разница между буферизованным и небуферизованным каналом? Небуферизованный (make(chan int)) — отправитель блокируется до приёма. Буферизованный (make(chan int, N)) — блокируется только когда буфер полон.

30. Что произойдёт при чтении из закрытого канала? При записи? Чтение — вернёт zero value, ok=false. Запись — panic! Закрытие закрытого — panic!

31. Что произойдёт при отправке/приёме на nil-канал? Блокируется навсегда (deadlock).

32. Зачем нужен select? Ожидать несколько канальных операций одновременно. Выбирает случайно среди готовых. С default — неблокирующий.

33. Как sync.WaitGroup работает? Add(n) — увеличить счётчик, Done() — уменьшить (в горутине), Wait() — ждать, пока счётчик не станет 0. Add должен быть до go, иначе race.

34. Mutex vs RWMutex? Mutex — один владелец (читает или пишет). RWMutex — много читателей ИЛИ один писатель. Используем RWMutex если читателей сильно больше.

35. Что такое race condition? Как обнаружить? Когда 2+ горутины обращаются к общей переменной, и хотя бы одна пишет, без синхронизации. Обнаружение: go run -race, go test -race.

36. Что такое deadlock? Приведите пример. Все горутины ждут друг друга, никто не двигается. Пример: 2 горутины ждут друг от друга чтение из небуферизованного канала.

37. Подвох с loop variable и closures (до Go 1.22):

for i := 0; i < 5; i++ {
go func() { fmt.Println(i) }() // печатает 5 пять раз!
}

Решение (до 1.22): i := i внутри цикла. В Go 1.22+ — каждая итерация имеет свою переменную, проблема исчезла.

38. Что такое context и зачем? Передача дедлайнов, сигналов отмены и request-scoped значений через цепочку вызовов. Обязательно использовать в HTTP/DB-запросах.

39. Что такое утечка горутины и как её избежать? Горутина никогда не завершается (например, ждёт чтения из канала, в который никто не пишет). Решение: всегда иметь способ её остановить (context, close канала).

40. Как сериализовать struct в JSON?

data, err := json.Marshal(user)

Только экспортируемые поля. Контроль через теги: json:"name,omitempty".

41. Что делает json:"-"? Игнорирует поле при сериализации/десериализации.

42. Что делает omitempty? Не включает поле в JSON, если оно zero value (пустая строка, 0, false, nil).

43. Как создать HTTP-сервер?

http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)

44. Что такое middleware и как реализовать в Go? Функция, оборачивающая http.Handler, добавляющая cross-cutting concerns (логи, auth, recover).

45. Как написать тест в Go? Файл _test.go, функция func TestXxx(t *testing.T). Запуск: go test ./....

46. Что такое table-driven test? Множество кейсов в виде структуры, перебор циклом. Идиоматичный паттерн Go.

47. Что такое go.mod и go.sum? go.mod — описание модуля и зависимостей. go.sum — хеши версий для проверки целостности.

48. Как обновить зависимость? go get -u github.com/foo/bar или go get github.com/foo/bar@v1.2.3. Затем go mod tidy.

49. В чём разница между go build и go run? go build создаёт исполняемый файл. go run компилирует и сразу запускает (без сохранения бинарника).

50. Что делает go vet? Статический анализ — ищет подозрительные конструкции (Printf-формат, копирование Mutex, и т.п.). Запускать в CI.

51. В чём разница между PUT и PATCH? PUT — полная замена ресурса. PATCH — частичное обновление.

52. Что такое индекс в SQL? Когда нужен? Структура данных (обычно B-tree) для быстрого поиска. Ускоряет SELECT/JOIN по индексированному полю, замедляет INSERT/UPDATE.

53. Что такое JOIN? Какие бывают? Соединение таблиц по условию. INNER (пересечение), LEFT (всё из левой + совпадения), RIGHT, FULL OUTER.

54. Что такое транзакция? Атомарный набор операций. ACID: Atomicity, Consistency, Isolation, Durability. BEGIN ... COMMIT/ROLLBACK.

55. В чём разница между git merge и git rebase? Merge — создаёт merge-коммит, сохраняя историю обеих веток. Rebase — переписывает коммиты текущей ветки поверх целевой, история линейная. Не делать rebase публичной ветки.

Задача 1. Реализовать функцию, возвращающую пересечение двух слайсов:

func Intersect(a, b []int) []int {
set := make(map[int]bool, len(a))
for _, v := range a { set[v] = true }
var result []int
for _, v := range b {
if set[v] { result = append(result, v); delete(set, v) }
}
return result
}

Задача 2. Worker pool: распараллелить обработку N задач K воркерами.

Задача 3. Реализовать rate limiter (например, token bucket).

Задача 4. Развернуть строку с учётом UTF-8 (через []rune).

Задача 5. Реализовать “потокобезопасный счётчик” (counter с инкрементом из множества горутин).

Задача 6. FizzBuzz, но с использованием каналов.

Задача 7. Дано: канал чисел. Напишите функцию, которая считает сумму всех чисел из канала, пока он не закроется.

Задача 8. Реализовать LRU cache (простую версию).

Задача 9. “Бэкенды ненадёжны”: реализовать Balancer (round-robin) с retry и поддержкой ctx.Done().

Задача 10. Распарсить JSON в структуру и сделать HTTP-сервер с endpoints для CRUD.


Цель — показать понимание Go, REST API, БД, тестов, Docker и Git.

  • CRUD через HTTP (GET/POST/PUT/DELETE)
  • Хранение в PostgreSQL (через database/sql + pgx)
  • Структура: cmd/, internal/, pkg/
  • Unit + integration тесты
  • Dockerfile + docker-compose
  • README с описанием API

Стек: Go + Gin/Chi + PostgreSQL + Docker

  • Принимает длинный URL, возвращает короткий
  • Редирект по коду
  • Кэш в Redis
  • Статистика переходов
  • Аутентификация (опционально)

Стек: Go + chi/Gin + PostgreSQL + Redis

  • Что-то полезное: погода, переводчик, заметки, валюта
  • Использование github.com/go-telegram-bot-api/telegram-bot-api
  • Команды, inline-клавиатуры
  • Хранение состояния пользователя в БД
  • Например: парсер логов, генератор паролей, ASCII-art, конвертер валют
  • Использование cobra или flag
  • Цветной вывод
  • Тесты

Что-то, где Go блистает: web crawler (с rate limit), пинговалка хостов, генератор отчётов из API нескольких источников. Демонстрирует goroutines, channels, context.

  • Real-time чат через WebSocket (gorilla/websocket)
  • Несколько комнат
  • Простая аутентификация
  • Демонстрирует concurrency

Что обязательно должно быть в каждом проекте:

Заголовок раздела «Что обязательно должно быть в каждом проекте:»
  • README на английском (структура, как запустить, скриншоты/curl-примеры)
  • Makefile с командами make build, make test, make run
  • Dockerfile (multi-stage build)
  • .gitignore, .golangci.yml
  • Тесты (хотя бы unit для бизнес-логики)
  • CI через GitHub Actions (хотя бы go test + golangci-lint)
  • Структура папок: cmd/, internal/, pkg/, migrations/, configs/
  • Commit history — осмысленная, не “fix” × 50

Обязательные:

  1. Алан Донован, Брайан Керниган — «Язык программирования Go» (есть русский перевод, ISBN 978-5-8459-2051-5). Классика, must-read. Местами устарела (до generics), но фундамент.
  2. Jon Bodner — Learning Go (2-е изд. 2024). Самая актуальная книга для входа в Go, охватывает generics, modules, error handling 1.13+.
  3. William Kennedy — Ultimate Go Notebook / Go in Action. Идиомы Go, конкурентность.

Дополнительно: 4. Mat Ryer — Writing An Interpreter In Go — для глубокого понимания. 5. Teiva Harsanyi — 100 Go Mistakes and How to Avoid Them ⭐ — очень рекомендуется к собесу. 6. Katherine Cox-Buday — Concurrency in Go — для углубления в каналы/горутины.

Платные/полуплатные (RU):

  • Stepik — Golang. Уровень 1-3 (бесплатно).
  • Stepik — Горутины и каналы в Go: задачи с собеседований и паттерны (Тузов).
  • Yandex Practicum — Go-разработчик (платный, с трудоустройством).
  • OTUS — Golang Developer / Backend.
  • Slurm — Golang для инженеров.
  • Balun Courses — Golang курсы.

Бесплатные стажировки/курсы (RU):

  • Route 256 от Ozon (route256.ozon.ru) — бесплатный курс для студентов 3+ курса.
  • VK Стажировка Go-разработчик.
  • Школа разработки Тинькофф (T-Образование).

Английский:

  • boot.dev — Learn Go, Learn Backend (платно, но качественно).
  • Frontend Masters — Go Fundamentals (Brian Holt).
  • Udemy — Go: The Complete Developer’s Guide (Stephen Grider).
  • Николай Тузов — Golang (https://www.youtube.com/@nikolay_tuzov) — лучший русский ютубер по Go. От основ до продвинутого.
  • Юра Семёнов / Учитель Java — есть треды по Go.
  • Технотрек / Технострим Mail.ru — лекции от VK.
  • Образовательные плейлисты от Ozon, Avito, Тинькофф.
  • Anthony GG
  • TutorialEdge (Elliot Forbes)
  • Tech with Tim
  • Jon Calhoun (calhoun.io)
  • GopherCon talks (официальный канал)
  • Just for Func (Francesc Campoy) — продвинутое
  • Ardan Labs (Bill Kennedy)
  • @golang_google — крупный канал по Go
  • @goproglib — Библиотека Go-разработчика (23k+)
  • @gophernews — 4gophers
  • @gogolang — новости и материалы
  • @go_perf — Go performance
  • @ntuzov — Николай Тузов (анонсы)
  • Чаты: Gopher Club, SPb Go, Golang_RU
  • Habr (RU) — теги “golang”, “go” — много годных статей и переводов
  • Medium (тег “go”) — много инди-блогов
  • dev.to/golang
  • gobyexample.com
  • eli.thegreenplace.net (Эли Бендерский, глубокие посты)
  • dave.cheney.net (Dave Cheney, идиомы и нюансы)
  • github.com/avelino/awesome-go (большой список библиотек)
  • github.com/goavengers/go-interview — вопросы для собеса
  • pkg.go.dev — официальная документация всех пакетов
  • go.dev/doc/ — официальные туториалы
  • go.dev/blog/ — официальный блог Go (релизы и важные темы)

9. Что НЕ требуется от Junior (но требуется от Middle/Senior)

Заголовок раздела «9. Что НЕ требуется от Junior (но требуется от Middle/Senior)»

Чтобы понять “потолок” уровня и не учить лишнее, вот что не требуется от Junior, но потребуется выше:

  • Глубокое понимание планировщика Go (G-M-P, work stealing, syscall handling) — на собесе Middle/Senior
  • Внутренности GC (tricolor mark-and-sweep, write barrier, фазы GC, тюнинг GOGC/GOMEMLIMIT)
  • Escape analysis в деталях, профайлинг через pprof, оптимизация аллокаций
  • Тонкие нюансы memory model Go, happens-before
  • Внутренняя реализация каналов (hchan, runtime/chan.go)
  • sync.Pool, atomic операции, lock-free структуры данных
  • Микросервисная архитектура: service mesh, sagas, distributed tracing
  • gRPC / protobuf на продакшн-уровне
  • Kubernetes глубоко (только базовое понимание контейнеров и k8s от Middle)
  • Kafka / RabbitMQ / NATS — паттерны producer/consumer, partition assignment, exactly-once
  • Базы данных: репликация (master-slave, multi-master), шардинг, выбор изоляции для конкретной нагрузки
  • Тонкости PostgreSQL: MVCC, vacuum, query plans, partitioning
  • Redis: лок-механизмы (Redlock), Lua-скрипты, persistence-стратегии
  • Метрики/observability: Prometheus + Grafana, OpenTelemetry, structured logging
  • DDD, гексагональная архитектура, clean architecture (от Middle уже могут спросить)
  • CI/CD pipelines (хотя базовое от Junior уже желательно)
  • Авторская архитектура с нуля, мнения о технических решениях
  • Глубокие знания сетевых протоколов: TCP handshake, congestion control, QUIC
  • Внутренности HTTP/2, HTTP/3, gRPC streaming
  • Performance tuning, бенчмарки, профайлинг
  • Security: OWASP Top 10 глубоко, SQL-инъекции, JWT-нюансы, TLS-настройки
  • Уверенный синтаксис, типы данных
  • Умение читать чужой код
  • Базовая concurrency (горутины, каналы, мьютексы)
  • Базовый HTTP-сервер и БД-запросы
  • Тесты пишет
  • Git: branch/merge/conflict
  • Docker базово
  • Понимает, чего не знает, и умеет искать в документации

Главное у Junior — не объём знаний, а обучаемость, аккуратность кода, понимание основ и здравый смысл.


10. Реалистичный план изучения с нуля до Junior

Заголовок раздела «10. Реалистичный план изучения с нуля до Junior»

Условие: опыт программирования есть (например, базовый Python/JS), 2-3 часа в день. Если ноль опыта — добавить 2-3 недели на основы программирования.

  • Установить Go 1.24+, VS Code + Go extension
  • Пройти A Tour of Go полностью (https://go.dev/tour/)
  • Прочитать главы 1-2 Donovan & Kernighan
  • Базовый синтаксис: переменные, типы, циклы, условия, функции
  • Написать 5-10 микро-программ (FizzBuzz, факториал, Фибоначчи, реверс строки, проверка простого числа)
  • Массивы, слайсы (углублённо: header, capacity, append-подвохи)
  • Maps (внутреннее устройство, итерация)
  • Strings vs runes vs bytes
  • Структуры, методы, указатели
  • Пакеты, видимость
  • Donovan & Kernighan главы 3-4
  • Решить 15-20 задач на Exercism Go track
  • Интерфейсы, duck typing, embedding
  • Empty interface / any, type assertion, type switch
  • Error handling, errors.Is/As, wrapping, custom errors
  • defer, panic, recover
  • Donovan & Kernighan главы 6-7
  • Написать собственный простой пакет с тестами
  • Goroutines, каналы (буферизованные/нет), select
  • sync.Mutex, sync.WaitGroup, sync.Once
  • Контекст (context)
  • Базовые паттерны: worker pool, fan-in/fan-out, pipeline
  • Race condition, -race
  • Donovan & Kernighan глава 8
  • Решить 10 задач на горутины (есть курс Тузова на Stepik)
  • Прочитать “Concurrency in Go” Кэтрин Кокс-Бьюди (хотя бы первые 4 главы)
  • fmt, strings, strconv, time, os, io, bufio
  • encoding/json (теги, omitempty, custom Marshaler)
  • net/http: сервер, клиент, middleware
  • testing: unit, table-driven, testify
  • Pet-проект 1: CLI-утилита (например, парсер логов или конвертер)
  • SQL базово: SELECT, INSERT, UPDATE, DELETE, JOIN, индексы
  • database/sql + pgx для PostgreSQL
  • Миграции (goose или migrate)
  • Pet-проект 2: To-Do REST API с PostgreSQL
  • Использовать Gin или chi
  • Тесты с testify
  • Docker базово: Dockerfile, docker-compose
  • Multi-stage build для Go
  • Git углублённо: branches, rebase, merge, conflicts, PR-workflow
  • Linux: bash, ssh, основные команды
  • Завернуть pet-проект в Docker, поднять через compose
  • golangci-lint, gofmt, go vet, делв (dlv)
  • Makefile, CI через GitHub Actions
  • Pet-проект 3: что-то с concurrency (web crawler / пинговалка / Telegram-бот с обработкой апдейтов)
  • Прочитать “100 Go Mistakes” Harsanyi (хотя бы 30-40 главных)
  • Прочитать оба тома вопросов на собес с Habr
  • Изучить typed nil, loop variable capture, slice gotchas
  • Generics базово (когда использовать, когда нет)
  • Решить 100 вопросов с собеса (Habr, GitHub goavengers/go-interview)
  • Решить 20-30 задач LeetCode на Go (easy/medium)
  • Повторить SQL, HTTP, REST, JSON
  • Прорешать “5 заданий с собеса” с tproger
  • Записать собственное mock-интервью (записать и пересмотреть)
  • Доделать README в каждом pet-проекте (английский, скриншоты, curl-примеры)
  • Добавить CI, тесты, golangci-lint
  • Сделать pinned-репозитории на GitHub
  • Резюме на hh.ru, habr career, LinkedIn
  • Откликаться на 5-10 вакансий в день (Junior Go / Стажёр Go)
  • Стажировки: Yandex, VK, Ozon (Route 256), Тинькофф, Сбер, Avito
  • Готовиться к каждому собесу: смотреть стек компании заранее
  • Записывать вопросы с собесов, разбирать ошибки
  • Не сдаваться — Junior Go это узкий рынок, нужно 30-50+ откликов

Можешь объяснить за 1-2 минуты, без подглядывания:

  • Что такое слайс, его header, почему append может вернуть новый массив
  • Чем отличается nil slice от empty slice
  • Как устроен map внутри, почему случайный порядок
  • Что такое rune, почему len("Привет") = 12
  • Когда использовать pointer receiver, когда value
  • Что такое interface, как работает duck typing
  • Typed nil gotcha (interface != nil но значение nil)
  • Что такое горутина, отличие от потока
  • Что произойдёт при send/receive/close на nil/closed канале
  • Что делает select, когда блокируется
  • Когда WaitGroup, когда Mutex, когда канал
  • Что такое context и зачем
  • defer order (LIFO), вычисление аргументов
  • errors.Is vs errors.As, wrapping через %w
  • Race condition и как обнаружить (-race)
  • Loop variable capture (и фикс в Go 1.22)
  • Что в go.mod / go.sum
  • Как написать table-driven test
  • Базовый CRUD REST API с PostgreSQL и Docker
  • Git: rebase vs merge, fix conflict
  • SQL: JOIN типы, что такое индекс
  • HTTP: коды, методы, идемпотентность