Go Middle 1 Roadmap (2025-2026) — подготовка к собеседованию в РФ/СНГ
Источники собраны из официальной документации Go, статей Хабра, блогов 2025-2026, материалов от Яндекс.Практикум, Tinkoff Career, Ozon Tech и реальных собеседований в Avito, VK, Сбер. Roadmap рассчитан на грейд Middle 1 (junior+ → начальный middle): уровень, где от тебя ждут самостоятельной работы, понимания “что под капотом” и опыта в production-задачах.
0. Что такое Middle 1 в Go в 2025-2026
Заголовок раздела «0. Что такое Middle 1 в Go в 2025-2026»По данным анализа 9 247 технических интервью в 2025-2026, Go стал самым быстрорастущим стеком — доля интервью выросла с 7% (Q1) до 12% (Q4), Go вышел на 4-е место после Python, Java и C#. Главные работодатели — Ozon, Avito, Яндекс, T-Bank, VK, Сбер.
Особенности Go-интервью:
- System Design встречается в 34% случаев (выше среднего) — Go берут под high-load.
- SOLID спрашивают реже (21%), фокус — на конкурентности, каналах, API-дизайне.
- Внутреннее устройство языка (slice, map, channel, scheduler, GC) — обязательный минимум.
- Реальные задачи на race conditions — must.
Что отличает Middle 1 от Junior:
- Junior: пишет код, знает синтаксис, делает CRUD-сервисы.
- Middle 1: понимает КАК работают горутины, GC, escape analysis; умеет писать тесты, профилировать, дебажить production; знает паттерны concurrency; работал с реальной БД и observability.
- Middle 2/Senior: проектирует системы, может оптимизировать под нагрузку, отвечает за архитектуру.
1. Углублённое знание языка
Заголовок раздела «1. Углублённое знание языка»1.1 Интерфейсы под капотом: iface, eface, itab
Заголовок раздела «1.1 Интерфейсы под капотом: iface, eface, itab»В Go две внутренние структуры (runtime/iface.go):
// Непустой интерфейсtype iface struct { tab *itab // указатель на таблицу методов data unsafe.Pointer // указатель на данные}
// Пустой интерфейс (interface{} / any)type eface struct { _type *_type // информация о типе data unsafe.Pointer // данные}
// itab — interface tabletype itab struct { inter *interfacetype // тип интерфейса _type *_type // конкретный тип hash uint32 // копия _type.hash (для type switch) _ [4]byte fun [1]uintptr // методы конкретного типа в порядке интерфейса}Почему две структуры: eface — оптимизация. При работе с any/interface{} не нужна таблица методов, поэтому экономим pointer load при type switch.
Важные следствия:
- Любое присваивание конкретного типа в интерфейс — это 2 указателя в памяти.
itabкэшируется в runtime по паре (interfacetype, _type).- Сравнение интерфейсов = сравнение пар (tab/_type, data) → может паниковать, если data не comparable (map, slice, func).
nil interface ≠ interface с nil-указателем внутри:
var p *MyError = nilvar err error = pfmt.Println(err == nil) // false! tab != nil, хотя data == nilЭто классический вопрос на собеседовании.
1.2 Type assertions и type switches
Заголовок раздела «1.2 Type assertions и type switches»// Одиночное assertion: панические при неудачеs := i.(string)
// Comma-ok: безопасноеs, ok := i.(string)if !ok { /* ... */ }
// Type switchswitch v := i.(type) {case nil: // ...case int: fmt.Println(v * 2)case string: fmt.Println(len(v))case Stringer: // assertion на интерфейс fmt.Println(v.String())default: fmt.Printf("unknown: %T\n", v)}В type switch для каждой пары (исходный тип, target тип) runtime ищет itab в кэше. Это быстрее, чем reflect.
1.3 Embedding (композиция вместо наследования)
Заголовок раздела «1.3 Embedding (композиция вместо наследования)»type Animal struct { Name string}func (a Animal) Greet() string { return "Hi, I'm " + a.Name }
type Dog struct { Animal // embedding Breed string}
d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Husky"}fmt.Println(d.Greet()) // "Hi, I'm Rex" — метод поднялсяfmt.Println(d.Name) // "Rex" — поле тожеОсобенности:
- Можно встраивать структуры, интерфейсы, указатели.
- Не наследование: нет полиморфизма по embedded типу, нет super.
- Конфликт имён: явное обращение
d.Animal.Name. - Интерфейс может встраивать другие интерфейсы (
io.ReadWriter = io.Reader + io.Writer).
1.4 Generics (Go 1.18+)
Заголовок раздела «1.4 Generics (Go 1.18+)»Базовый синтаксис:
type Number interface { ~int | ~int64 | ~float64}
func Sum[T Number](values []T) T { var sum T for _, v := range values { sum += v } return sum}Когда использовать (по официальному гайду “When to use generics”):
- Функции работают со стандартными контейнерами (slice, map, channel) —
Map,Filter,Reduce. - Общие структуры данных (
Stack[T],Cache[K, V],LinkedList[T]). - Когда метод вызывается на разных типах, но поведение идентично.
Когда НЕ использовать:
- Если нужно вызвать метод на значении — используй интерфейс, а не generic.
- Когда реализации для разных типов реально разные.
- Преждевременно: начинай с конкретных типов и рефактори в generic только когда повторение очевидно.
Go 1.21 → 1.24: улучшено type inference, type aliases с параметрами, range over functions/integers. Go 1.25 убрал концепцию “core types” — упростил спецификацию.
1.5 Reflect — когда стоит и не стоит
Заголовок раздела «1.5 Reflect — когда стоит и не стоит»Стоит, если:
- ORM/маппинг (sqlx, GORM, encoding/json).
- Валидация по тегам (go-playground/validator).
- DI-контейнеры (fx, dig).
- Generic сериализация.
НЕ стоит, если:
- Можно решить интерфейсом или generic.
- В hot path — рефлексия медленнее в 10-100 раз.
v := reflect.ValueOf(user)t := v.Type()for i := 0; i < t.NumField(); i++ { f := t.Field(i) tag := f.Tag.Get("json") fmt.Printf("%s -> %v (json: %s)\n", f.Name, v.Field(i), tag)}Правила Лоуса о рефлексии: 1) reflection обратима, 2) интерфейс ↔ reflect.Value, 3) изменения требуют Addr() или передачи указателя.
1.6 Unsafe — базовое понимание
Заголовок раздела «1.6 Unsafe — базовое понимание»unsafe.Pointer нарушает безопасность типов и должен использоваться очень осторожно. Применяется в:
- CGO: взаимодействие с C-кодом.
- Оптимизация без копирования: string ↔ []byte без аллокаций.
- Доступ к приватным полям (тестирование, ORM).
- Чтение бинарных форматов напрямую из памяти.
// Преобразование string в []byte без копирования (Go 1.20+ — есть unsafe.StringData/Slice)b := unsafe.Slice(unsafe.StringData(s), len(s))Правило: не используй unsafe, пока нет четкой необходимости. Всегда покрывай тестами и документируй.
1.7 Escape analysis (heap vs stack)
Заголовок раздела «1.7 Escape analysis (heap vs stack)»Компилятор Go сам решает, где разместить переменную. Если он может доказать, что значение не переживёт функцию — оно идёт на стек (дёшево, без GC). Если нет — escape to heap.
Что вызывает escape:
- Возврат указателя на локальную переменную:
func newInt() *int { x := 42; return &x } // x escape- Конвертация в interface{}:
fmt.Println(x) // x теряет тип → eface → heap- Замыкания, ловящие переменную по ссылке.
- Слайс/map, который растёт за пределы статически известного размера.
- Слишком большой объект для стека.
Проверка:
go build -gcflags="-m" main.gogo build -gcflags="-m -m" main.go # больше деталей# ./main.go:5:6: moved to heap: x1.8 Go Memory Model basics (happens-before)
Заголовок раздела «1.8 Go Memory Model basics (happens-before)»Базовые правила синхронизации:
- В одной горутине операции упорядочены (program order).
- Между горутинами порядок определяется только synchronization events: каналы, mutex, atomic, sync.Once, runtime.Gosched/Goexit.
- Рецепт: всякое чтение, которое не синхронизировано с записью — это data race, и поведение не определено.
Конкретные гарантии:
- Отправка в канал happens-before приёма из канала.
- Закрытие канала happens-before приёма zero-value.
- n-й Unlock happens-before (n+1)-й Lock.
- atomic.Store happens-before atomic.Load, читающего записанное значение.
2. Глубокая concurrency
Заголовок раздела «2. Глубокая concurrency»2.1 Channels изнутри (hchan)
Заголовок раздела «2.1 Channels изнутри (hchan)»type hchan struct { qcount uint // кол-во элементов в буфере dataqsiz uint // capacity buf unsafe.Pointer // кольцевой буфер elemsize uint16 closed uint32 elemtype *_type sendx uint // индекс для send в буфер recvx uint // индекс для recv recvq waitq // список goroutines, ждущих recv (sudog) sendq waitq // список goroutines, ждущих send lock mutex // защищает hchan}Как работает:
- Send в небуферизованный канал: если в recvq есть waiter → данные копируются напрямую goroutine → goroutine, отправитель продолжает. Если нет — отправитель блокируется (создаётся sudog, push в sendq, паркуется).
- Send в буферизованный: если есть место в
buf→ копия вbuf[sendx]. Иначе — блокируется. - Recv: симметрично.
Lock — hybrid spin-mutex для уменьшения overhead.
2.2 Select — внутри
Заголовок раздела «2.2 Select — внутри»Каждый case → scase{c *hchan, kind, elem}. Алгоритм select:
- Random shuffle порядка cases (через
fastrandn) — чтобы избежать starvation. - Сортировка по lock-order (адрес channel) для избежания deadlock при многоканальном lock.
- Заблокировать все каналы.
- Пройти cases, если есть ready — выполнить и unlock все.
- Если нет — для каждого канала создать sudog и записать в его recvq/sendq, потом goroutine паркуется.
- Когда одна из операций готова — goroutine просыпается, остальные sudog удаляются.
Если есть default — он используется, если ни один case не готов сразу (без блокировки).
2.3 sync package
Заголовок раздела «2.3 sync package»| Тип | Когда использовать |
|---|---|
sync.Mutex | Защита данных, < few μs критическая секция. |
sync.RWMutex | Много чтений, мало записей. Под высокой нагрузкой может быть хуже Mutex из-за внутренней сложности. |
sync.Once | Однократная инициализация (singleton, lazy init). Гарантирует happens-before. |
sync.WaitGroup | Дождаться завершения N горутин. Add → перед Go(), Done в defer, Wait после. |
sync.Cond | Координация через condition variables. Чаще лучше каналы. Использовать когда нужен Broadcast многим waiters. |
sync.Pool | Переиспользование короткоживущих объектов (буферы, JSON encoders). GC может очистить pool в любой момент. |
sync.Map | Concurrent map для случаев: 1) ключ пишется один раз, читается много; 2) горутины работают с непересекающимися ключами. В общем случае Mutex + map быстрее. |
Пример sync.Pool:
var bufPool = sync.Pool{ New: func() any { return new(bytes.Buffer) },}
func handler(w http.ResponseWriter, r *http.Request) { buf := bufPool.Get().(*bytes.Buffer) defer func() { buf.Reset(); bufPool.Put(buf) }() // используем buf}2.4 atomic
Заголовок раздела «2.4 atomic»Пакет sync/atomic — для примитивных типов (int32/64, uint32/64, uintptr, Pointer) и обёрток (atomic.Int64, atomic.Value, atomic.Bool — с Go 1.19).
var counter atomic.Int64counter.Add(1)v := counter.Load()Правило выбора atomic vs Mutex:
- Один примитив, простая операция (Add/Load/CAS) → atomic.
- Несколько связанных полей или сложная логика → Mutex.
2.5 context
Заголовок раздела «2.5 context»Правила использования:
- Context — первый параметр функции:
func F(ctx context.Context, ...). - Никогда не храни context в struct (исключения — для долгоживущих сервисов, явно документируй).
- Никогда не передавай nil context — используй
context.TODO()если не уверен. - Всегда defer cancel() для
WithTimeout/WithDeadline/WithCancel. context.Value— только для request-scoped данных (trace ID, user ID), не для optional аргументов.- Ключ для Value — свой неэкспортируемый тип:
type ctxKey intconst userIDKey ctxKey = 0
ctx = context.WithValue(ctx, userIDKey, 42)v, ok := ctx.Value(userIDKey).(int)Go 1.20+ ввёл context.WithCancelCause(ctx, err) — можно отменять с причиной, потом context.Cause(ctx) достаёт её.
2.6 Паттерны concurrency
Заголовок раздела «2.6 Паттерны concurrency»Worker pool — ограничить параллелизм:
func workerPool(ctx context.Context, jobs <-chan Job, n int) <-chan Result { results := make(chan Result) var wg sync.WaitGroup for i := 0; i < n; i++ { wg.Add(1) go func() { defer wg.Done() for { select { case <-ctx.Done(): return case j, ok := <-jobs: if !ok { return } results <- process(j) } } }() } go func() { wg.Wait(); close(results) }() return results}Pipeline — стадия → стадия через каналы:
nums := generate(ctx)sq := square(ctx, nums)out := filter(ctx, sq)for v := range out { fmt.Println(v) }Fan-out / fan-in:
// Fan-out: 1 канал → N горутин// Fan-in: N каналов → 1func fanIn(ctx context.Context, chans ...<-chan int) <-chan int { out := make(chan int) var wg sync.WaitGroup for _, c := range chans { wg.Add(1) go func(c <-chan int) { defer wg.Done() for v := range c { select { case out <- v: case <-ctx.Done(): return } } }(c) } go func() { wg.Wait(); close(out) }() return out}Semaphore через buffered channel:
sem := make(chan struct{}, 10) // max 10 concurrentfor _, task := range tasks { sem <- struct{}{} go func(t Task) { defer func() { <-sem }() run(t) }(task)}Generator:
func gen(ctx context.Context, n int) <-chan int { out := make(chan int) go func() { defer close(out) for i := 0; i < n; i++ { select { case out <- i: case <-ctx.Done(): return } } }() return out}2.7 Race detector
Заголовок раздела «2.7 Race detector»go test -race ./...go build -race ./...go run -race main.goЦена: ~5-10x CPU, ~2x memory, до 10x slower. В production не включаешь, в CI — обязательно.
2.8 Deadlock, livelock, starvation
Заголовок раздела «2.8 Deadlock, livelock, starvation»- Deadlock: 2 горутины ждут друг друга. Признаки: программа зависла,
runtime: all goroutines are asleep - deadlock!. - Livelock: горутины двигаются, но не прогрессируют.
- Starvation: одна горутина блокирует доступ другим (часто из-за приоритетов, RWMutex с постоянными читателями).
Как избегать:
- Всегда блокируй mutex’ы в одинаковом порядке.
- Используй
contextдля отмены. - Не держи lock на время сетевых вызовов.
- Buffered каналы — там, где это уместно, не везде.
2.9 Goroutine leaks
Заголовок раздела «2.9 Goroutine leaks»Типичные сценарии:
- Send в канал без получателя:
ch := make(chan int) // нет буфераgo func() { ch <- 42 }() // вечно блокирует, если никто не читает- Range по каналу, который никогда не закрывают.
- Горутина читает из канала, который потерял всех writer’ов.
- Тикеры/таймеры без Stop():
ticker := time.NewTicker(time.Second)// забыли defer ticker.Stop() → leakКак ловить:
runtime.NumGoroutine()в метриках.pprof goroutineprofile.uber-go/goleakв тестах:
func TestMain(m *testing.M) { goleak.VerifyTestMain(m) }- Go 1.26 (2026) добавил встроенный goroutine leak profile через GC reachability analysis.
3. Runtime и performance
Заголовок раздела «3. Runtime и performance»3.1 GMP-модель
Заголовок раздела «3.1 GMP-модель»- G (goroutine) — горутина с своим стеком (старт 2KB, растёт по необходимости).
- M (machine) — OS-поток.
- P (processor) — логический процессор, держит локальную очередь G. Число P =
GOMAXPROCS(по умолчанию = логические ядра).
Работа:
- Когда горутина создаётся (
go f()) — она кладётся в локальную run queue текущего P. - M выполняет горутины из run queue своего P.
- Если локальная очередь пуста — M ворует половину очереди у другого P (work stealing).
- Если G делает блокирующий syscall — M отделяется, P берёт другой M, новые G идут к нему.
- Если G блокируется на канале/mutex — она паркуется (не занимает M).
Preemption: с Go 1.14 — асинхронный preemption через сигналы. Длинные циклы не блокируют scheduler.
3.2 GC (Garbage Collector)
Заголовок раздела «3.2 GC (Garbage Collector)»Go использует non-moving, concurrent, tri-color, mark-and-sweep сборщик.
Цвета:
- White — кандидат на удаление.
- Gray — достижимый, ещё не просканирован.
- Black — достижимый и просканирован.
Алгоритм:
- STW (stop-the-world) — старт, включение write barrier (~микросекунды).
- Concurrent mark — параллельно с программой.
- Mark termination (STW) — быстро.
- Concurrent sweep — освобождение белых.
Write barrier — функция, которая запускается при каждой записи указателя во время mark phase. Гарантирует, что не пропустим объект.
Параметры:
GOGC=100(default) — GC запускается, когда heap вырос на 100% от размера live data в прошлом цикле.GOGC=200— реже GC, больше памяти.GOGC=off— выключить.
GOMEMLIMIT=4GiB(Go 1.19+) — soft limit на heap. GC становится агрессивнее при приближении. Полезно в k8s/контейнерах с лимитом памяти.
В Go 1.25 экспериментально внедрён “Green Tea” — переработка алгоритма для уменьшения cache miss.
3.3 Профилирование (pprof)
Заголовок раздела «3.3 Профилирование (pprof)»Подключение:
import _ "net/http/pprof"go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }()Сбор:
# CPU 30 секундgo tool pprof http://localhost:6060/debug/pprof/profile?seconds=30# Heapgo tool pprof http://localhost:6060/debug/pprof/heap# Горутиныgo tool pprof http://localhost:6060/debug/pprof/goroutine# Block (синхронизация)go tool pprof http://localhost:6060/debug/pprof/block# Mutex contentiongo tool pprof http://localhost:6060/debug/pprof/mutexВ pprof UI:
top— топ функций.list FuncName— построчно.web— flame graph (нужен Graphviz).traces— конкретные пути.
Для memory profile: inuse_space, inuse_objects, alloc_space, alloc_objects. Allocs дают понимание потерь.
3.4 Benchmarks
Заголовок раздела «3.4 Benchmarks»func BenchmarkSum(b *testing.B) { s := make([]int, 1000) for i := range s { s[i] = i } b.ResetTimer() for i := 0; i < b.N; i++ { _ = sum(s) }}Запуск:
go test -bench=. -benchmem -benchtime=10s -count=5 -cpu=1,2,4 > new.txtbenchstat old.txt new.txt-benchmem показывает allocations. benchstat (golang.org/x/perf/cmd/benchstat) сравнивает прогоны статистически (Wilcoxon test).
3.5 Trace tool
Заголовок раздела «3.5 Trace tool»import "runtime/trace"
f, _ := os.Create("trace.out")trace.Start(f)defer trace.Stop()// ...Или через pprof: curl 'http://localhost:6060/debug/pprof/trace?seconds=5' > trace.out.
Просмотр: go tool trace trace.out → браузер.
Видишь: горутины, syscalls, GC, network, scheduler latency.
3.6 Базовая оптимизация
Заголовок раздела «3.6 Базовая оптимизация»- Преаллокация slice/map:
make([]T, 0, n),make(map[K]V, n)— избежать ре-аллокаций. - strings.Builder для конкатенации в цикле.
- sync.Pool для часто создаваемых объектов.
- Указатели vs value receiver: для маленьких структур (< 64 байт) — value. Для больших — pointer.
- Избегать interface{} в hot path — escape to heap.
unsafe.String/unsafe.Sliceдля безаллокационного преобразования string ↔ []byte (с осторожностью).- PGO (Profile-Guided Optimization) в Go 1.21+ — даёт 2-7% прироста бесплатно:
go build -pgo=default.pgo.
4. Тестирование на Middle 1
Заголовок раздела «4. Тестирование на Middle 1»4.1 Table-driven tests
Заголовок раздела «4.1 Table-driven tests»Идиоматичный паттерн:
func TestParseInt(t *testing.T) { tests := []struct { name string input string want int wantErr bool }{ {"valid positive", "123", 123, false}, {"negative", "-42", -42, false}, {"invalid", "abc", 0, true}, {"empty", "", 0, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ParseInt(tt.input) if (err != nil) != tt.wantErr { t.Fatalf("err = %v, wantErr %v", err, tt.wantErr) } if got != tt.want { t.Errorf("got %d, want %d", got, tt.want) } }) }}t.Run создаёт subtest, можно запускать выборочно: go test -run TestParseInt/valid_positive. Поддерживается t.Parallel() для параллельного запуска.
4.2 Mocks
Заголовок раздела «4.2 Mocks»3 подхода:
- Ручные стабы (для маленьких интерфейсов) — лучший выбор по умолчанию.
stretchr/testify/mock— assertions + mock в одном пакете.uber-go/mock(бывший gomock) +mockery— генерация mocks из интерфейсов.
Пример testify/mock:
type UserRepoMock struct { mock.Mock }func (m *UserRepoMock) GetByID(ctx context.Context, id int) (*User, error) { args := m.Called(ctx, id) return args.Get(0).(*User), args.Error(1)}
// Тестrepo := new(UserRepoMock)repo.On("GetByID", mock.Anything, 1).Return(&User{ID: 1}, nil)svc := NewService(repo)u, err := svc.Fetch(ctx, 1)require.NoError(t, err)assert.Equal(t, 1, u.ID)repo.AssertExpectations(t)mockery генерирует mocks по интерфейсам — mockery --all --output mocks/.
4.3 httptest
Заголовок раздела «4.3 httptest»func TestHandler(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/users/1", nil) rec := httptest.NewRecorder() h := NewHandler(repo) h.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("status %d", rec.Code) }}
// Mock внешнего HTTP-сервисаsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`{"ok": true}`))}))defer srv.Close()client := NewClient(srv.URL)4.4 testcontainers
Заголовок раздела «4.4 testcontainers»Поднимать реальную БД для интеграционных тестов:
import "github.com/testcontainers/testcontainers-go/modules/postgres"
ctx := context.Background()pg, _ := postgres.Run(ctx, "postgres:16-alpine", postgres.WithDatabase("test"), postgres.WithUsername("test"), postgres.WithPassword("test"),)defer pg.Terminate(ctx)
dsn, _ := pg.ConnectionString(ctx, "sslmode=disable")db, _ := sql.Open("pgx", dsn)4.5 Coverage
Заголовок раздела «4.5 Coverage»go test -cover ./...go test -coverprofile=cover.out ./...go tool cover -html=cover.out # html-отчётgo tool cover -func=cover.out # сводкаMiddle 1: цель 70-80% coverage на бизнес-логику. Не гонись за 100% — это бессмысленно.
4.6 Fuzz testing (Go 1.18+)
Заголовок раздела «4.6 Fuzz testing (Go 1.18+)»func FuzzReverse(f *testing.F) { testcases := []string{"Hello", "", "12345"} for _, tc := range testcases { f.Add(tc) // seed corpus } f.Fuzz(func(t *testing.T, orig string) { rev := Reverse(orig) doubleRev := Reverse(rev) if orig != doubleRev { t.Errorf("Before: %q, after: %q", orig, doubleRev) } if !utf8.ValidString(rev) { t.Errorf("Reverse produced invalid UTF-8: %q", rev) } })}Запуск: go test -fuzz=FuzzReverse -fuzztime=30s. Найденные failing inputs сохраняются в testdata/fuzz/.
5. Базы данных
Заголовок раздела «5. Базы данных»5.1 database/sql правильно
Заголовок раздела «5.1 database/sql правильно»db, err := sql.Open("pgx", dsn) // Open lazy, не открывает connectionif err != nil { return err }defer db.Close()
// Обязательно: проверка живостиif err := db.PingContext(ctx); err != nil { return err }
// Настройки пулаdb.SetMaxOpenConns(25)db.SetMaxIdleConns(5)db.SetConnMaxLifetime(time.Hour)db.SetConnMaxIdleTime(10 * time.Minute)
// Запрос с контекстом — ОБЯЗАТЕЛЬНОrows, err := db.QueryContext(ctx, "SELECT id, name FROM users WHERE active = $1", true)if err != nil { return err }defer rows.Close() // обязательно!
for rows.Next() { var u User if err := rows.Scan(&u.ID, &u.Name); err != nil { return err } // ...}return rows.Err() // не забудь!Правила пула:
MaxOpenConns ≈ (CPU * 2) + 1как стартовая точка, тюнить по нагрузке.MaxIdleConns ≤ MaxOpenConns.ConnMaxLifetime— особенно с балансировщиками (HAProxy/pgbouncer) и в k8s, чтобы пересоздавать stale connections.- Сумма MaxOpenConns всех инстансов сервиса ≤ лимит БД.
Prepared statements: db.Prepare() готовит на одном соединении, в пуле это сложно. Чаще передаёшь db.QueryContext(ctx, sql, args...) — pgx сам кэширует prepared statements при prefer_simple_protocol=false.
5.2 pgx vs lib/pq
Заголовок раздела «5.2 pgx vs lib/pq»pgx — современный driver, рекомендуется для новых проектов:
- Extended protocol по умолчанию с binary форматом данных.
- Свой connection pool
pgxpool— быстрееdatabase/sql. - Поддержка PostgreSQL-специфичных типов (jsonb, hstore, arrays).
- Активная поддержка.
lib/pq — больше не разрабатывается активно, использует Simple protocol → лишний parse-bind-execute на каждый параметризованный запрос.
Использование pgx:
// Через database/sqlimport _ "github.com/jackc/pgx/v5/stdlib"db, _ := sql.Open("pgx", dsn)
// Или нативный pgxpool (быстрее)import "github.com/jackc/pgx/v5/pgxpool"pool, _ := pgxpool.New(ctx, dsn)rows, _ := pool.Query(ctx, "SELECT ...")5.3 ORM/Query builders: что и когда
Заголовок раздела «5.3 ORM/Query builders: что и когда»| Инструмент | Когда |
|---|---|
| database/sql + pgx | Простые проекты, полный контроль. |
| sqlx | Лёгкий wrapper над database/sql: Get, Select, StructScan. Удобно мапить результаты в структуры. |
| squirrel | Когда нужно строить SQL динамически (фильтры по условиям). Сам не выполняет. |
| sqlc | Компилируешь SQL в Go-код на этапе сборки. Type-safe, минимум reflection. Топ-выбор в 2025. |
| GORM | Полноценный ORM, миграции, hooks. Тяжёлый, reflection — медленнее. Хорош для прототипов/админок. |
| ent / bun | Альтернативные ORM с code-gen или хорошей структурой. |
В РФ-проектах часто: pgx + sqlc или pgx + sqlx + squirrel. GORM в production реже из-за производительности.
Пример sqlc (query.sql):
-- name: GetUser :oneSELECT * FROM users WHERE id = $1;
-- name: ListUsers :manySELECT * FROM users ORDER BY id LIMIT $1 OFFSET $2;После sqlc generate получаешь типизированные Go-функции.
5.4 Транзакции и изоляции
Заголовок раздела «5.4 Транзакции и изоляции»tx, err := db.BeginTx(ctx, &sql.TxOptions{ Isolation: sql.LevelSerializable,})if err != nil { return err }defer tx.Rollback() // безопасно вызывать после Commit (no-op)
if _, err := tx.ExecContext(ctx, "UPDATE accounts SET balance = balance - $1 WHERE id = $2", 100, fromID); err != nil { return err}if _, err := tx.ExecContext(ctx, "UPDATE accounts SET balance = balance + $1 WHERE id = $2", 100, toID); err != nil { return err}return tx.Commit()Изоляции (PostgreSQL):
READ UNCOMMITTED≡READ COMMITTED(PG не имеет dirty read).READ COMMITTED— default. Видишь только закоммиченное на момент запроса.REPEATABLE READ— снапшот на момент начала транзакции. Защита от non-repeatable read, фантомы возможны в стандарте, но в PG MVCC ловит и их.SERIALIZABLE— полная сериализация через SSI. Возможна ошибкаserialization_failure— нужно ретраить.
Аномалии:
- Dirty read — чтение незакоммиченного.
- Non-repeatable read — повторное чтение даёт другие значения.
- Phantom read — повторный range-запрос даёт новые строки.
- Lost update — два обновления, одно потерялось.
Deadlocks: PostgreSQL детектирует через deadlock_timeout (по умолчанию 1s). В Go обычно ловишь по коду ошибки (40P01) и ретраишь.
5.5 Миграции
Заголовок раздела «5.5 Миграции»goose (pressly/goose):
-- 20251001120000_create_users.sql-- +goose UpCREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT);
-- +goose DownDROP TABLE users;goose -dir migrations postgres "$DSN" upgoose -dir migrations postgres "$DSN" downgoose -dir migrations postgres "$DSN" statusgolang-migrate: похожая структура, два файла 001_create_users.up.sql / 001_create_users.down.sql.
Best practices:
- Никогда не редактируй уже применённые миграции — только новые.
- Up + Down всегда.
- В production миграции — через CLI на CI/CD, не на старте приложения (иначе при 10 репликах будет конкуренция).
- Если миграция упала в середине → “dirty” state, нужно ручное вмешательство (
force version).
6. Архитектура и паттерны
Заголовок раздела «6. Архитектура и паттерны»6.1 Clean Architecture (Uncle Bob) в Go
Заголовок раздела «6.1 Clean Architecture (Uncle Bob) в Go»Слои (от внутреннего к внешнему):
- Entities (domain models) — структуры бизнес-объектов.
- Use Cases (services) — бизнес-логика.
- Interface Adapters (controllers, presenters, gateways).
- Frameworks & Drivers (HTTP, DB, gRPC).
Главное правило: зависимости направлены внутрь. Внутренние слои не знают о внешних. Между ними — интерфейсы.
Пример раскладки:
internal/ domain/ # сущности (User struct) usecase/ # UserService с интерфейсами UserRepo, Notifier repository/ # реализация UserRepo на pgx delivery/ http/ # handlers grpc/ # grpc-серверы6.2 DDD (базовый уровень)
Заголовок раздела «6.2 DDD (базовый уровень)»- Entity — объект с идентичностью (User by ID).
- Value Object — объект без идентичности, неизменяемый (Money, Email).
- Aggregate — корень + сущности, изменяемые только через корень.
- Repository — интерфейс доступа к persistence.
- Domain Service — операция, не относящаяся к одной entity.
- Bounded Context — границы между поддоменами.
Для Middle 1 — знать терминологию, понимать суть. Применение DDD в полном объёме — Senior.
6.3 Hexagonal / Ports & Adapters
Заголовок раздела «6.3 Hexagonal / Ports & Adapters»Похоже на Clean Architecture. Ядро (domain + application) общается с внешним миром через ports (интерфейсы), которые реализуются adapters (БД, HTTP, очереди).
- Driving (primary) port — приходящие запросы (HTTP handler вызывает use case).
- Driven (secondary) port — исходящие (use case вызывает Repository, который реализован Postgres-адаптером).
6.4 Repository pattern
Заголовок раздела «6.4 Repository pattern»// domaintype UserRepository interface { GetByID(ctx context.Context, id int) (*User, error) Save(ctx context.Context, u *User) error}
// infrastructure/postgrestype userRepo struct { db *pgxpool.Pool }func (r *userRepo) GetByID(ctx context.Context, id int) (*User, error) { /* ... */ }
// usecasetype UserService struct { repo UserRepository }6.5 Dependency Injection
Заголовок раздела «6.5 Dependency Injection»Подходы:
- Ручной DI (compose root) — лучший выбор для большинства проектов:
func main() { db := mustConnect() repo := postgres.NewUserRepo(db) svc := usecase.NewUserService(repo) h := http.NewHandler(svc) http.ListenAndServe(":8080", h)}- Google Wire — code-gen DI, compile-time:
func InitializeApp() *App { wire.Build(NewDB, NewUserRepo, NewUserService, NewHandler, NewApp) return nil}- Uber fx — runtime DI с lifecycle:
app := fx.New( fx.Provide(NewDB, NewUserRepo, NewUserService), fx.Invoke(StartServer),)Совет: начинай с ручного DI. Wire — для больших статичных графов. Fx — когда нужен lifecycle и dynamic.
6.6 SOLID в Go
Заголовок раздела «6.6 SOLID в Go»- S — Single Responsibility: маленькие функции и интерфейсы.
- O — Open/Closed: расширяй через новые типы, реализующие интерфейс.
- L — Liskov Substitution: интерфейсы должны быть полноценно заменяемы.
- I — Interface Segregation: главное в Go. Маленькие интерфейсы (io.Reader, io.Writer), интерфейс определяется на стороне потребителя.
- D — Dependency Inversion: зависимости через интерфейсы → можно мокать в тестах.
Идиома Go: “Accept interfaces, return structs”. Принимай абстракции (Reader), возвращай конкретику (*MyStruct).
6.7 Project layout
Заголовок раздела «6.7 Project layout»github.com/golang-standards/project-layout — НЕОФИЦИАЛЬНЫЙ стандарт, относись скептически. Минимум, который реально полезен:
cmd/myapp/main.go— entrypoint.internal/— пакеты, которые НЕ должны импортироваться извне (Go enforce’ит это).pkg/— публичные пакеты (опционально).api/— proto-файлы, OpenAPI specs.migrations/— SQL миграции.deploy/— k8s манифесты, helm чарты.Makefile,Dockerfile,.golangci.yml,go.mod.
Принцип: простой проект — flat. Сложный — внутри internal/ группируй по домену, не по техническому слою.
7. HTTP и API
Заголовок раздела «7. HTTP и API»7.1 net/http глубоко
Заголовок раздела «7.1 net/http глубоко»mux := http.NewServeMux()mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK)})
srv := &http.Server{ Addr: ":8080", Handler: mux, ReadTimeout: 5 * time.Second, ReadHeaderTimeout: 2 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 60 * time.Second, MaxHeaderBytes: 1 << 20, // 1MB}Go 1.22 добавил pattern matching в http.ServeMux:
mux.HandleFunc("GET /users/{id}", userHandler)id := r.PathValue("id")7.2 Роутеры
Заголовок раздела «7.2 Роутеры»| Роутер | Особенности |
|---|---|
| chi | Идиоматичен net/http, легковесный, отличная компоновка middleware, подходит для большинства задач. Хороший выбор по умолчанию. |
| gin | Самый популярный (88k+ stars), быстрый, удобный API, BindJSON и т.д. Не на чистом net/http. |
| echo | Похож на gin, баланс между фичами и простотой. |
| fiber | На fasthttp (не net/http), очень быстрый, но несовместим со стандартными middleware и http.Handler. Не рекомендуется без явных причин. |
| stdlib net/http (1.22+) | После добавления path patterns достаточен для многих задач без сторонних роутеров. |
Совет для middle: знай chi и net/http глубоко. gin — уметь читать существующий код. Fiber — знать, что он не на стандартном net/http и почему это часто проблема.
7.3 Middleware
Заголовок раздела «7.3 Middleware»func loggingMiddleware(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)) })}
// Цепочкаhandler := loggingMiddleware(authMiddleware(mux))В chi:
r := chi.NewRouter()r.Use(middleware.RequestID)r.Use(middleware.Logger)r.Use(middleware.Recoverer)r.Use(middleware.Timeout(30 * time.Second))r.Get("/", indexHandler)7.4 Validation
Заголовок раздела «7.4 Validation»github.com/go-playground/validator/v10:
type CreateUserRequest struct { Email string `json:"email" validate:"required,email"` Password string `json:"password" validate:"required,min=8"` Age int `json:"age" validate:"gte=0,lte=130"`}
var validate = validator.New()if err := validate.Struct(req); err != nil { /* ... */ }7.5 OpenAPI / Swagger
Заголовок раздела «7.5 OpenAPI / Swagger»Два подхода:
- swag (
swaggo/swag) — генерирует Swagger UI из комментариев. Code → docs. - oapi-codegen (
deepmap/oapi-codegen) — пишешь OpenAPI yaml, генерируешь handlers, models. Docs → code. Предпочтительный современный подход.
7.6 gRPC
Заголовок раздела «7.6 gRPC»Структура proto:
syntax = "proto3";package user.v1;option go_package = "myapp/internal/gen/user/v1;userv1";
service UserService { rpc GetUser(GetUserRequest) returns (User); rpc ListUsers(ListUsersRequest) returns (stream User); // server stream rpc UploadFile(stream Chunk) returns (UploadResult); // client stream rpc Chat(stream Message) returns (stream Message); // bidi}Сервер:
type server struct { userv1.UnimplementedUserServiceServer }func (s *server) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.User, error) { /* ... */ }
func main() { lis, _ := net.Listen("tcp", ":50051") s := grpc.NewServer() userv1.RegisterUserServiceServer(s, &server{}) s.Serve(lis)}Клиент:
conn, _ := grpc.NewClient("localhost:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))client := userv1.NewUserServiceClient(conn)u, _ := client.GetUser(ctx, &userv1.GetUserRequest{Id: 1})Знать: 4 типа RPC (unary, server stream, client stream, bidi), status.Errorf(codes.NotFound, ...), deadline propagation, interceptors.
7.7 Graceful shutdown
Заголовок раздела «7.7 Graceful shutdown»ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)defer stop()
srv := &http.Server{Addr: ":8080", Handler: handler}go func() { if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Fatalf("listen: %v", err) }}()
<-ctx.Done()log.Println("shutting down")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil { log.Printf("shutdown: %v", err)}// Закрыть DB, очереди, и т.д.В k8s обязательно добавь паузу между SIGTERM и фактическим shutdown — балансер не успевает удалить pod из endpoints за 0 секунд.
8. Observability
Заголовок раздела «8. Observability»8.1 Структурированные логи
Заголовок раздела «8.1 Структурированные логи»Go 1.21+ стандартная библиотека log/slog:
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelInfo,}))slog.SetDefault(logger)
slog.Info("user signed in", slog.Int("user_id", 42), slog.String("ip", ip),)// {"time":"...","level":"INFO","msg":"user signed in","user_id":42,"ip":"..."}Альтернативы:
- zap (
go.uber.org/zap) — самый быстрый, чуть больше allocations. - zerolog — fluent API, zero allocations в большинстве случаев.
- logrus — устаревший, но ещё много кода.
Лучшие практики:
- Используй structured (key-value), не printf.
- Уровни: Debug/Info/Warn/Error.
- Контекстные поля через
slog.With()илиlogger = logger.With("request_id", id). - Не логируй секреты (PII, токены).
- В production — JSON, в dev — text/console.
8.2 Prometheus метрики
Заголовок раздела «8.2 Prometheus метрики»import "github.com/prometheus/client_golang/prometheus"
var ( httpRequests = prometheus.NewCounterVec( prometheus.CounterOpts{Name: "http_requests_total"}, []string{"method", "endpoint", "status"}, ) httpDuration = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "http_request_duration_seconds", Buckets: prometheus.DefBuckets, }, []string{"endpoint"}, ))
func init() { prometheus.MustRegister(httpRequests, httpDuration)}
http.Handle("/metrics", promhttp.Handler())4 типа метрик:
- Counter — монотонно растущий (запросы, ошибки).
- Gauge — может расти/падать (горутины, активные коннекшены).
- Histogram — распределение (latency).
- Summary — quantile, считается клиентом.
Правило RED: Rate, Errors, Duration — минимум для HTTP API. Правило USE: Utilization, Saturation, Errors — для ресурсов.
8.3 OpenTelemetry — distributed tracing
Заголовок раздела «8.3 OpenTelemetry — distributed tracing»import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" sdktrace "go.opentelemetry.io/otel/sdk/trace")
exp, _ := otlptracegrpc.New(ctx)tp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exp))otel.SetTracerProvider(tp)defer tp.Shutdown(ctx)
tr := otel.Tracer("myapp")ctx, span := tr.Start(ctx, "GetUser")defer span.End()span.SetAttributes(attribute.Int("user.id", 42))Концепции: Trace (всё путешествие запроса), Span (одна операция), Context propagation (через HTTP headers / gRPC metadata).
8.4 Correlation IDs
Заголовок раздела «8.4 Correlation IDs»В каждом запросе — Trace ID + Request ID. Прокидывать через context и логи.
func RequestID(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { id := r.Header.Get("X-Request-ID") if id == "" { id = uuid.NewString() } ctx := context.WithValue(r.Context(), reqIDKey, id) w.Header().Set("X-Request-ID", id) next.ServeHTTP(w, r.WithContext(ctx)) })}9. Docker и деплой
Заголовок раздела «9. Docker и деплой»9.1 Dockerfile для Go (multi-stage)
Заголовок раздела «9.1 Dockerfile для Go (multi-stage)»# --- Builder ---FROM golang:1.24-alpine AS builderWORKDIR /srcCOPY go.mod go.sum ./RUN go mod downloadCOPY . .RUN CGO_ENABLED=0 GOOS=linux go build \ -ldflags="-s -w" \ -trimpath \ -o /out/app ./cmd/app
# --- Runtime (distroless) ---FROM gcr.io/distroless/static-debian12:nonrootCOPY --from=builder /out/app /appUSER nonroot:nonrootEXPOSE 8080ENTRYPOINT ["/app"]Финальный образ — ~5-15MB вместо ~700MB.
FROM scratch — ещё меньше (~3MB), но:
- Нужно вручную копировать
/etc/ssl/certs/ca-certificates.crtдля HTTPS. - Нет shell, debug сложен.
- distroless/static — лучший компромисс: есть TLS сертификаты, timezone, non-root юзер, но нет shell.
-ldflags="-s -w" убирает debug info из бинаря.
-trimpath убирает абсолютные пути (reproducible builds).
CGO_ENABLED=0 — статичный бинарь без glibc.
9.2 docker-compose для локалки
Заголовок раздела «9.2 docker-compose для локалки»services: app: build: . ports: ["8080:8080"] environment: DATABASE_URL: postgres://user:pass@db:5432/app?sslmode=disable depends_on: db: { condition: service_healthy } db: image: postgres:16-alpine environment: POSTGRES_USER: user POSTGRES_PASSWORD: pass POSTGRES_DB: app healthcheck: test: ["CMD", "pg_isready", "-U", "user"] interval: 5s volumes: [db-data:/var/lib/postgresql/data] redis: image: redis:7-alpinevolumes: db-data:9.3 Kubernetes базы (что Middle 1 должен знать)
Заголовок раздела «9.3 Kubernetes базы (что Middle 1 должен знать)»- Pod — наименьшая единица, 1+ контейнеров с общей сетью.
- Deployment — управляет ReplicaSet, обеспечивает rolling update.
- Service — стабильный endpoint для группы pod’ов (ClusterIP, NodePort, LoadBalancer).
- Ingress — L7 роутинг.
- ConfigMap / Secret — конфигурация / секреты.
- HPA — горизонтальный автоскейлинг по метрикам.
- Liveness / Readiness probes — k8s через HTTP/exec проверяет, жив ли pod / готов ли к трафику.
Минимальный manifest:
apiVersion: apps/v1kind: Deploymentmetadata: { name: myapp }spec: replicas: 3 selector: { matchLabels: { app: myapp } } template: metadata: { labels: { app: myapp } } spec: containers: - name: myapp image: myapp:1.0 ports: [{ containerPort: 8080 }] livenessProbe: { httpGet: { path: /health, port: 8080 } } readinessProbe: { httpGet: { path: /ready, port: 8080 } } resources: requests: { cpu: 100m, memory: 128Mi } limits: { cpu: 500m, memory: 512Mi }Не нужно уметь админить кластер. Нужно: писать манифесты для своего сервиса, читать логи через kubectl logs, понимать, почему pod в CrashLoopBackOff.
10. Очереди и кэши
Заголовок раздела «10. Очереди и кэши»10.1 Redis (go-redis)
Заголовок раздела «10.1 Redis (go-redis)»import "github.com/redis/go-redis/v9"
rdb := redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, PoolSize: 50,})
// Кэшerr := rdb.Set(ctx, "user:42", data, 10*time.Minute).Err()val, err := rdb.Get(ctx, "user:42").Result()if errors.Is(err, redis.Nil) { /* miss */ }
// Pub/Subsub := rdb.Subscribe(ctx, "channel")for msg := range sub.Channel() { /* ... */ }rdb.Publish(ctx, "channel", "hello")
// Distributed lock (упрощённо — лучше использовать redsync)ok, _ := rdb.SetNX(ctx, "lock:job", "owner", 30*time.Second).Result()Паттерны:
- Cache-aside: код пытается прочитать из Redis, если miss → читает из БД, кладёт в Redis.
- Write-through: пишешь в БД + Redis одновременно.
- Distributed lock:
SET NX EX+ либоredsync(Redlock).
10.2 Kafka
Заголовок раздела «10.2 Kafka»segmentio/kafka-go (proper Go-нативный) или confluentinc/confluent-kafka-go (на librdkafka, быстрее, требует C).
w := &kafka.Writer{ Addr: kafka.TCP("localhost:9092"), Topic: "events", Balancer: &kafka.Hash{},}w.WriteMessages(ctx, kafka.Message{Key: []byte(userID), Value: payload})
r := kafka.NewReader(kafka.ReaderConfig{ Brokers: []string{"localhost:9092"}, Topic: "events", GroupID: "my-consumer-group",})for { m, _ := r.FetchMessage(ctx) process(m) r.CommitMessages(ctx, m) // явный коммит после успешной обработки}Знать: topic, partition, offset, consumer group, semantics (at-least-once / at-most-once / exactly-once).
10.3 NATS
Заголовок раздела «10.3 NATS»Лёгкая альтернатива Kafka для месседжинга:
nc, _ := nats.Connect(nats.DefaultURL)defer nc.Close()nc.Publish("subject", []byte("data"))nc.Subscribe("subject", func(m *nats.Msg) { /* ... */ })JetStream в NATS — persistence + replay (аналог Kafka, но проще).
10.4 RabbitMQ
Заголовок раздела «10.4 RabbitMQ»rabbitmq/amqp091-go. AMQP-модель: exchanges → queues → consumers.
conn, _ := amqp.Dial("amqp://guest:guest@localhost:5672/")ch, _ := conn.Channel()ch.QueueDeclare("tasks", true, false, false, false, nil)ch.Publish("", "tasks", false, false, amqp.Publishing{Body: data})10.5 Memcached
Заголовок раздела «10.5 Memcached»bradfitz/gomemcache — простой клиент. Для базового кэша, без persistence. В РФ-проектах сейчас в основном Redis вместо Memcached.
10.6 Что выбирать (по контексту 2025)
Заголовок раздела «10.6 Что выбирать (по контексту 2025)»- Простой кэш / pub-sub / locks → Redis.
- Event streaming, аналитика, log pipelines → Kafka.
- Лёгкая шина между микросервисами → NATS.
- Сложный роутинг сообщений, work queues → RabbitMQ.
- Background jobs → Asynq (на Redis) или Faktory.
11. Типичные вопросы на собеседовании Middle 1 Go
Заголовок раздела «11. Типичные вопросы на собеседовании Middle 1 Go»Ниже 80 вопросов с подробными ответами. Они отсортированы по категориям и основаны на реальных собесах в Ozon, Avito, Яндекс, T-Bank, VK, Сбер в 2025-2026.
Категория A: Базовые типы и слайсы (1-12)
Заголовок раздела «Категория A: Базовые типы и слайсы (1-12)»1. Что такое slice и чем отличается от array?
Array — фиксированный размер, value type (копируется при присваивании). Slice — структура {*data, len, cap}, ссылается на underlying array. Передача slice по значению — копируется заголовок, но не данные.
2. Что произойдёт при append если cap не хватает?
Создаётся новый underlying array (обычно в 2 раза больше для маленьких, ~1.25х для больших), данные копируются, slice указывает на новый массив. Старый — кандидат на GC.
3. Покажи код, который покажет неожиданное поведение append:
a := []int{1, 2, 3, 4, 5}b := a[:3] // len=3 cap=5c := append(b, 99) // НЕ создаёт новый массив, перезаписывает a[3]fmt.Println(a) // [1 2 3 99 5]4. Как удалить элемент из слайса по индексу?
s = append(s[:i], s[i+1:]...) — теряет порядок не сохраняется, или для O(1) без сохранения порядка: s[i] = s[len(s)-1]; s = s[:len(s)-1]. С Go 1.21 есть slices.Delete.
5. Сложность операций на слайсе?
s[i]— O(1).append— амортизированно O(1), в худшем случае O(n).- Удаление из середины — O(n).
- Поиск — O(n).
6. Что такое map изнутри? Hash table с buckets. Каждый bucket = 8 пар (key, value) + overflow pointer. При коллизии — добавляется в bucket или overflow. При load factor 6.5 — рост (incremental rehashing, эвакуация старых buckets в новые).
7. Почему iteration по map даёт разный порядок? Намеренно: Go рандомизирует стартовый bucket для предотвращения зависимостей на порядок.
8. Что произойдёт при concurrent чтении и записи в map?
Runtime panic: fatal error: concurrent map read and map write (с Go 1.6+, есть детектор). Нужен sync.Mutex или sync.Map.
9. Когда использовать sync.Map?
- Ключ пишется один раз, читается много (cache). 2) Горутины работают с непересекающимися наборами ключей. В остальных случаях
Mutex+mapбыстрее.
10. Чем строки отличаются от []byte?
Строка immutable, {*data, len}. Конвертация string([]byte) и []byte(string) копирует данные. Для безаллокационной конвертации — unsafe.String/Slice.
11. strings.Builder vs += vs bytes.Buffer для конкатенации?
+=в цикле — O(n²) и аллокации каждый раз.bytes.Buffer— был стандартным, но конвертация в string копирует.strings.Builder— best, no-copyString(). МожноGrow(n)для преаллокации.
12. Как работает range по строке?
По rune (UTF-8), не по байтам. i — байтовый индекс, r — rune. Для байтов используй for i := 0; i < len(s); i++ { c := s[i] }.
Категория B: Указатели, методы, интерфейсы (13-25)
Заголовок раздела «Категория B: Указатели, методы, интерфейсы (13-25)»13. Когда использовать value, а когда pointer receiver?
- Pointer: мутирующий метод; большая структура; единообразие (если хоть один pointer — все pointer).
- Value: маленький immutable тип; safer (нельзя случайно изменить).
14. Может ли interface быть nil? Да, но interface == nil ⇔ оба (tab, data) == nil. Если data == nil, но tab != nil — interface не nil:
var p *MyErr = nilvar err error = pfmt.Println(err == nil) // false!Это частый bug: возврат *MyErr где нужно error.
15. Что такое type assertion?
Извлечение конкретного типа из interface: s := i.(string) — panic при неудаче. s, ok := i.(string) — safe.
16. Type switch — синтаксис?
switch v := i.(type) {case int: ...case string: ...default: ...}17. Можно ли реализовать интерфейс в другом пакете? Да — Go использует структурную (duck) типизацию. Не нужно явно объявлять “implements”.
18. Что такое empty interface (any)?
interface{} (или any с Go 1.18) — интерфейс без методов, удовлетворяется любым типом. Внутри — eface{*_type, data}.
19. Чем отличается embedded interface от embedded struct?
- Embedded struct — поднимает поля и методы.
- Embedded interface (в struct) — добавляет required методы (struct ДОЛЖЕН их реализовать или будет panic при вызове).
- Embedded interface в interface — композиция требований.
20. Чему равен размер interface{} в памяти?
16 байт на 64-битной системе: 2 указателя.
21. Как работает динамический диспатч в Go?
Через itab: вызов v.Method() → ищем в itab.fun[i] адрес конкретного метода → вызываем с data в качестве receiver.
22. Почему интерфейсы в Go “implicit”? Это позволяет добавлять интерфейсы к существующим типам без модификации исходного кода → decoupling.
23. Что такое stringer?
Интерфейс Stringer { String() string } — кастомизирует вывод в fmt.Println, %v.
24. Как тестировать через интерфейс? Зависимости в коде — через интерфейсы. В тестах — mock-реализация.
25. Что вернёт reflect.TypeOf(nil)?
nil. У nil нет конкретного типа. Похожая ловушка с typed nil — см. №14.
Категория C: Goroutines & Channels (26-42)
Заголовок раздела «Категория C: Goroutines & Channels (26-42)»26. Чем горутина отличается от потока ОС?
- Горутина: ~2KB стек на старте, управляется Go runtime, до миллионов.
- OS thread: ~1MB+ стек, управляется ядром, тысячи.
- M:N модель — много горутин на меньшем числе OS-потоков.
27. Что такое GOMAXPROCS? Число P в GMP-модели = число горутин, которые могут выполняться параллельно в данный момент. По умолчанию = число логических ядер.
28. Объясни буферизованный vs небуферизованный канал.
- Небуферизованный (
make(chan int)) — синхронный, send блокирует до recv. - Буферизованный (
make(chan int, 10)) — асинхронный до заполнения буфера.
29. Что произойдёт при close() на канале?
- После закрытия read возвращает zero value +
ok=false. - Запись в закрытый канал → panic.
- Закрытие закрытого → panic.
rangeзавершается, когда канал закрыт и пуст.
30. Закрывает писатель или читатель канал? Писатель. Правило: “Don’t close a channel from the receiver side, and don’t close a channel if the channel has multiple concurrent senders”.
31. Как работает select?
Случайно выбирает один из готовых cases. Если ни один не готов и есть default — выполняет его. Иначе блокируется на всех каналах одновременно.
32. Что такое sudog? Внутренняя структура runtime — представление горутины, ждущей на канале. В send/recv queue канала это связанный список sudog.
33. Напиши worker pool:
func main() { jobs := make(chan int, 100) results := make(chan int, 100) var wg sync.WaitGroup for w := 1; w <= 5; w++ { wg.Add(1) go func(id int) { defer wg.Done() for j := range jobs { results <- j * 2 } }(w) } for j := 1; j <= 20; j++ { jobs <- j } close(jobs) go func() { wg.Wait(); close(results) }() for r := range results { fmt.Println(r) }}34. Как ограничить число одновременных горутин?
Buffered канал как семафор (см. раздел 2.6) или errgroup.SetLimit(n) (с Go 1.20).
35. Что такое goroutine leak? Приведи пример. Горутина зависла навсегда. Пример: горутина пишет в небуферизованный канал, для которого нет читателей (например, читатель завершился по timeout, но горутина не знает):
result := make(chan int)go func() { result <- compute() }() // leak если никто не читаетselect {case r := <-result: fmt.Println(r)case <-time.After(1*time.Second): return // горутина выше зависла}Решение: buffered (make(chan int, 1)) или передавать ctx и checked в горутине.
36. Как избежать race condition без mutex? Каналы (passing ownership), atomic, immutable data, sync.Once.
37. Что такое data race? Конкурентный доступ к памяти без синхронизации, где хоть один — write. Поведение UB.
38. Команда для проверки race conditions?
go test -race, go run -race, go build -race.
39. select с одним каналом и time.After — паттерн? Timeout:
select {case v := <-ch:case <-time.After(5*time.Second): return errors.New("timeout")}Caveat: time.After создаёт новый таймер при каждом вызове в цикле — leak. Лучше t := time.NewTimer(5*time.Second); defer t.Stop().
40. Найди баг:
for i := 0; i < 10; i++ { go func() { fmt.Println(i) }()}До Go 1.22 — все горутины захватывают одну и ту же переменную i, выведут 10 десять раз. Исправление: go func(i int) { ... }(i). Go 1.22 изменил семантику — в каждой итерации новая i, проблема решилась автоматически.
41. Чем sync.WaitGroup отличается от семафора? WaitGroup — ждать завершения N задач (Add → Done → Wait). Семафор (buffered chan) — ограничить параллельность.
42. Как корректно завершить горутины при остановке сервиса?
Через context.Context — каждая горутина слушает <-ctx.Done() и при отмене завершается. Главная горутина закрывает контекст и wg.Wait().
Категория D: Memory, GC, runtime (43-55)
Заголовок раздела «Категория D: Memory, GC, runtime (43-55)»43. Объясни tri-color mark-and-sweep. Объекты делятся на white/gray/black. Roots → gray. Из gray сканируется, ссылки делаются gray, объект → black. Когда gray пуст — white удаляются.
44. Что такое write barrier? Hook, запускающийся при записи указателя во время GC mark — гарантирует, что GC не пропустит объект, переставленный в уже отсканированную часть графа.
45. STW (stop-the-world) в Go — длительность? В современном Go (1.20+) — ~10-100μs на типичных нагрузках. Двойное STW: на старте GC и mark termination.
46. Что такое GOGC? Параметр (default 100), задающий: GC запускается, когда heap вырос на GOGC% от размера live data после прошлого цикла.
47. Что такое GOMEMLIMIT? Soft limit на heap (Go 1.19+). GC становится агрессивнее при приближении к лимиту. Полезно в контейнерах с фиксированной памятью.
48. Escape analysis — что попадает на heap?
- Указатель уходит за границы функции.
- Конвертация в interface{}.
- Замыкания, ловящие переменную по ссылке.
- Slice/map переменного размера.
- Слишком большие объекты.
49. Как посмотреть escape analysis?
go build -gcflags="-m" main.go.
50. Чем отличается стек горутины от стека ОС? Стек горутины: растёт динамически (старт 2KB, max ~1GB), сегментированный (с 1.4 — copy-resize). OS-стек: фиксированный (обычно 1-8MB), однородный.
51. Что такое preemption? Прерывание выполнения горутины планировщиком. Go 1.14+ — асинхронная preemption через сигналы (раньше — только на функциях с stack check).
52. Как профилировать heap?
go tool pprof http://.../debug/pprof/heap → top/list/web. Метрики: inuse_space (текущее), alloc_space (за всё время).
53. Когда полезен sync.Pool? Часто создаваемые короткоживущие объекты (buffers, encoders). Не для долгоживущих! GC может очистить pool.
54. Что такое work stealing? Когда P опустошает свою local run queue, M (привязанный к этому P) ворует половину очереди у другого P.
55. Что такое handoff в scheduler? Когда горутина уходит в блокирующий syscall, M отвязывается от P, новый M подхватывает P для обработки других G.
Категория E: Тестирование, debugging (56-62)
Заголовок раздела «Категория E: Тестирование, debugging (56-62)»56. Что такое table-driven test?
Тест с массивом случаев, итерация через t.Run. Стандартный идиоматичный паттерн.
57. testify vs стандартные асёрты?
testify (require, assert) даёт богатые сообщения, require останавливает тест при ошибке. В std-библиотеке — t.Errorf, t.Fatalf.
58. Чем require отличается от assert в testify?
require зовёт t.FailNow() (стоп теста). assert — t.Fail() (продолжение).
59. Что такое t.Parallel()?
Запуск этого subtest’а параллельно с другими subtest’ами того же родителя. Часто комбинируется с tt := tt (или с 1.22 — без него).
60. Как мокать time в тестах?
Использовать абстракцию (Clock interface) или библиотеку (benbjohnson/clock, jonboulle/clockwork). Передавать в зависимости.
61. Чем integration отличается от unit-теста?
Unit — изолирует под тестом. Integration — с реальными зависимостями (БД, HTTP). Запуск integration — отдельный шаг (например, //go:build integration).
62. Что такое coverage и какая цель?
go test -cover — процент строк кода, выполненных тестами. Цель 70-80% на бизнес-логику. 100% не нужно.
Категория F: HTTP, gRPC, API (63-69)
Заголовок раздела «Категория F: HTTP, gRPC, API (63-69)»63. Чем gRPC отличается от REST?
- gRPC: HTTP/2, protobuf, кодогенерация, streaming, низкая латентность.
- REST: HTTP/1.1, JSON, человекочитаемо, проще debug, кешируемо.
- Часто внутри — gRPC, наружу — REST.
64. Когда middleware вызывается в chi?
До основного хендлера (request flow) и в обратном порядке после (response flow). next.ServeHTTP(w, r) — переход к следующему.
65. Что делает http.Server.Shutdown()?
Перестаёт принимать новые соединения, ждёт завершения текущих, закрывает idle. Получает context для timeout.
66. Зачем нужен ReadHeaderTimeout? Защита от slowloris-атак, где клиент медленно отправляет заголовки и держит соединение.
67. Что такое HTTP/2 multiplexing? Множество запросов в одном TCP-соединении параллельно (vs HTTP/1.1, где один запрос за раз или pipelining без перемешивания).
68. Что такое graceful shutdown? Прекратить принимать новые запросы, дать текущим завершиться, закрыть ресурсы (БД, очереди). Сигналы: SIGTERM (Kubernetes), SIGINT (Ctrl+C).
69. Как ограничить размер тела запроса?
http.MaxBytesReader(w, r.Body, maxBytes).
Категория G: Базы данных (70-75)
Заголовок раздела «Категория G: Базы данных (70-75)»70. Что такое connection pool? Кеш открытых соединений к БД. Зачем: TCP+TLS+auth дорого, переиспользовать.
71. Чем prepared statement отличается от обычного запроса? Сервер один раз парсит и планирует запрос, потом выполняешь с параметрами. Защита от SQL injection. В pgx — кеш auto.
72. ACID — расшифруй.
- Atomicity — всё или ничего.
- Consistency — переход между валидными состояниями.
- Isolation — параллельные транзакции не мешают.
- Durability — после commit данные сохранены.
73. Что такое индекс? Когда не нужно создавать? Структура (обычно B-tree) для ускорения поиска. Не нужно: на маленьких таблицах, на колонках с низкой кардинальностью, на write-heavy таблицах (индекс замедляет вставки).
74. SELECT N+1 — что это? Антипаттерн: 1 запрос на список + N запросов на детали. Решение: JOIN или batch.
75. Уровни изоляции в PostgreSQL?
- READ COMMITTED (default) — нет dirty reads.
- REPEATABLE READ — снапшот на старт транзакции.
- SERIALIZABLE — полная изоляция через SSI.
Категория H: Архитектура, кодогенерация, прочее (76-80)
Заголовок раздела «Категория H: Архитектура, кодогенерация, прочее (76-80)»76. Что такое Clean Architecture? Слои: entities → use cases → adapters → frameworks. Зависимости направлены внутрь, между слоями — интерфейсы.
77. Что такое idiomatic Go?
- Маленькие интерфейсы.
- Возврат ошибок (не паника).
- Композиция > наследование.
gofmt-форматирование.- Краткие имена в коротком scope.
- Accept interfaces, return structs.
78. Что такое “errors as values”?
Ошибки — обычные значения, не исключения. Возвращаешь (T, error), проверяешь if err != nil. С 1.13+ — errors.Is, errors.As, обёртка через fmt.Errorf("%w", err).
79. Чем errors.Is отличается от ==?
errors.Is разворачивает цепочку обёрнутых ошибок (через %w) и сравнивает на каждом уровне.
80. Что такое golangci-lint? Мета-линтер, агрегирует ~80 анализаторов (govet, staticcheck, errcheck, gosec и др.). Стандарт де-факто в Go-проектах.
12. Pet-проекты для портфолио Middle 1
Заголовок раздела «12. Pet-проекты для портфолио Middle 1»Для Middle 1 нужно показать production-ready код: тесты, observability, Docker, миграции, чистая архитектура.
Проект 1: URL Shortener
Заголовок раздела «Проект 1: URL Shortener»Стек: Go + chi + PostgreSQL (pgx + sqlc) + Redis + Prometheus + slog + Docker. Фичи:
- POST /shorten — короткая ссылка (base62 от ID или nanoid).
- GET /{code} — редирект.
- Аналитика (счётчик кликов в Redis, batch flush в Postgres).
- Rate limiter (token bucket).
- Open API + Swagger.
- Тесты (unit + integration через testcontainers).
- Метрики, healthcheck, graceful shutdown.
Проект 2: Чат-сервис с WebSocket / gRPC streaming
Заголовок раздела «Проект 2: Чат-сервис с WebSocket / gRPC streaming»Стек: Go + gRPC (или gorilla/websocket) + Redis pub/sub + PostgreSQL + Docker. Фичи:
- Каналы (rooms), приватные сообщения.
- История сообщений в PG.
- Доставка через Redis pub/sub.
- JWT auth.
- gRPC bidirectional streaming.
- Тесты на конкурентность.
Проект 3: Job Queue / Worker система
Заголовок раздела «Проект 3: Job Queue / Worker система»Стек: Go + PostgreSQL + Redis (asynq) или Kafka + Prometheus. Фичи:
- API для постановки задач.
- Worker pool с graceful shutdown.
- Retry с exponential backoff.
- Dead letter queue.
- Метрики throughput, latency, failure rate.
- OpenTelemetry trace через всю цепочку.
Проект 4: API Gateway / Reverse Proxy
Заголовок раздела «Проект 4: API Gateway / Reverse Proxy»Стек: Go (net/http/httputil) + Redis (rate limit) + OpenTelemetry. Фичи:
- Маршрутизация (routes config из yaml/json).
- Аутентификация (JWT).
- Rate limiting (token bucket per user).
- Circuit breaker.
- Логи + трейсы.
Проект 5: Real-time analytics pipeline
Заголовок раздела «Проект 5: Real-time analytics pipeline»Стек: Go + Kafka + ClickHouse + Prometheus + Grafana. Фичи:
- HTTP endpoint собирает события.
- Producer пишет в Kafka.
- Consumer’ы batches пишут в ClickHouse.
- Dashboard в Grafana.
Для каждого проекта:
- README с диаграммой архитектуры.
- Makefile (build, test, lint, run, migrate).
- Docker + docker-compose.
- .github/workflows/ci.yml (lint, test, build).
- go.mod чистый.
- Структура с
internal/.
13. Ресурсы для обучения
Заголовок раздела «13. Ресурсы для обучения»Книги (must-read для Middle)
Заголовок раздела «Книги (must-read для Middle)»- “100 Go Mistakes and How to Avoid Them” — Teiva Harsanyi (2022, Manning). Перевод на русский есть. Топ-1 книга для Middle: 100 типовых ошибок, охватывает concurrency, тесты, типы, GC, контекст.
- “Concurrency in Go” — Katherine Cox-Buday (O’Reilly). Глубокое погружение в каналы, sync, паттерны.
- “The Go Programming Language” — Donovan & Kernighan (“K&R Go”). Классика.
- “Learn Go with Tests” — Chris James (бесплатно на GitHub) — TDD через стандартную библиотеку.
- “Efficient Go” — Bartłomiej Płotka (O’Reilly, 2022) — performance и профилирование на Go.
- “Distributed Services with Go” — Travis Jeffery — построение распределённой системы с нуля.
Официальная документация
Заголовок раздела «Официальная документация»- The Go Programming Language — go.dev.
- Effective Go — must-read.
- Go Memory Model.
- Go GC Guide.
- Go blog — особенно про generics, slog, PGO.
Курсы (РФ/СНГ)
Заголовок раздела «Курсы (РФ/СНГ)»- Yandex Practicum — Backend-разработчик на Go.
- Otus — Golang Developer (basic + advanced).
- Karpov.courses — Go-developer (с уклоном в data engineering).
- Учебник Tinkoff Education — бесплатные конспекты по Go (education.tbank.ru).
- balun.courses — глубокие курсы по Go, concurrency и собесам.
YouTube/Telegram (русскоязычные)
Заголовок раздела «YouTube/Telegram (русскоязычные)»- Канал “Анатолий Александрович” (YouTube) — разборы вопросов с собесов.
- Антон Жуков (gophers.com.ua) — про concurrency и performance.
- Канал “GoCloud” — про распределённые системы.
- Telegram-каналы:
@golang_news— релизы, статьи.@golang_interview— вопросы с собесов.@gogolang— community.@golangquiz— квизы для подготовки.
- GolangConf (golangconf.ru) — записи докладов.
Блоги на английском
Заголовок раздела «Блоги на английском»- Dave Cheney (dave.cheney.net) — гуру Go, статьи про производительность и идиомы.
- Bill Kennedy / Ardan Labs (ardanlabs.com/blog) — глубокие материалы по runtime.
- Three Dots Labs (threedots.tech) — DDD, Clean Architecture в Go.
- Boldly Go (boldlygo.tech) — обновления и патерны (2025).
- VictoriaMetrics blog — производительность Go в реальных приложениях.
- Allegro Tech Blog — про GC и production.
- Eli Bendersky (eli.thegreenplace.net) — глубокая теория.
Practice
Заголовок раздела «Practice»- leetcode.com — алгоритмы.
- gophercises.com — практические упражнения.
- exercism.org/tracks/go — задачи с менторингом.
14. План перехода Junior → Middle 1 (6 месяцев)
Заголовок раздела «14. План перехода Junior → Middle 1 (6 месяцев)»Реалистичный план для junior разработчика, который уже пишет на Go.
Месяц 1: Внутреннее устройство языка
Заголовок раздела «Месяц 1: Внутреннее устройство языка»- Неделя 1: Slice, map, string под капотом. Прочитать соответствующие главы из “100 Go Mistakes”. Решить 5 задач на манипуляции со слайсами.
- Неделя 2: Интерфейсы (iface, eface, itab). Прочитать
go-internals/chapter2_interfaces. Понять nil interface ловушку. Реализовать свой Stringer. - Неделя 3: Generics. Прочитать “When to use generics”. Сделать generic функции
Map,Filter,Reduce,Stack[T]. - Неделя 4: Reflect, unsafe (базы), escape analysis. Прогонять программы с
-gcflags=-m.
Чекпоинт: ответить на 25 вопросов из категорий A и B этого roadmap.
Месяц 2: Concurrency
Заголовок раздела «Месяц 2: Concurrency»- Неделя 1: GMP-модель, goroutines, scheduler. Просмотреть видео “Go Scheduler Deep Dive”. Реализовать примеры с GOMAXPROCS.
- Неделя 2: Channels (hchan), select внутри. Решить задачи: merge channels, fan-in, fan-out, pipeline.
- Неделя 3: sync (Mutex, RWMutex, Once, Pool, Cond, Map), atomic, context. Прочитать главу про context в “100 Go Mistakes”.
- Неделя 4: Race detector, goroutine leaks, паттерны (worker pool, semaphore). Использовать
goleakв тестах.
Чекпоинт: написать с нуля worker pool с graceful shutdown, провалидировать через -race и goleak.
Месяц 3: Performance + тестирование
Заголовок раздела «Месяц 3: Performance + тестирование»- Неделя 1: pprof (cpu, heap, goroutine, block). Подключить к pet-проекту, найти 3 узких места.
- Неделя 2: Benchmarks, benchstat. Написать бенчмарки на критические функции, сравнить варианты.
- Неделя 3: GC, GOGC, GOMEMLIMIT, escape analysis. Прочитать “Go GC Guide”.
- Неделя 4: Table-driven tests, testify, mockery, httptest, testcontainers. Покрыть pet-проект 80%+.
Чекпоинт: профилируешь свой сервис, оптимизируешь самую медленную функцию, измеряешь benchstat’ом.
Месяц 4: БД + observability
Заголовок раздела «Месяц 4: БД + observability»- Неделя 1: database/sql, pgx, connection pool. Настроить лимиты в реальном сервисе.
- Неделя 2: Транзакции, изоляции, миграции (goose/golang-migrate).
- Неделя 3: sqlc, sqlx, squirrel. Переписать ручные запросы на sqlc.
- Неделя 4: slog, prometheus, OpenTelemetry. Добавить метрики и трейсы в pet-проект.
Чекпоинт: свой сервис показывает метрики в Grafana через Prometheus + trace в Jaeger.
Месяц 5: Архитектура + HTTP/gRPC + деплой
Заголовок раздела «Месяц 5: Архитектура + HTTP/gRPC + деплой»- Неделя 1: Clean Architecture, hexagonal. Перестроить pet-проект по слоям.
- Неделя 2: net/http, chi, middleware, validation. Реализовать API с swagger.
- Неделя 3: gRPC: proto, server, client, streaming, interceptors.
- Неделя 4: Docker multi-stage, distroless, docker-compose, базы k8s (Pod, Deployment, Service, probes).
Чекпоинт: pet-проект в Docker с docker-compose, манифесты для k8s.
Месяц 6: Очереди + собесы + финальный pet-проект
Заголовок раздела «Месяц 6: Очереди + собесы + финальный pet-проект»- Неделя 1: Redis (кэш, pub/sub), Kafka producer/consumer.
- Неделя 2: Завершение pet-проекта № 2 уровня middle (см. секцию 12).
- Неделя 3: Mock-интервью. Прорешать 80 вопросов из секции 11. Системный дизайн: URL shortener, rate limiter, чат.
- Неделя 4: Алгоритмы (топ-50 leetcode задач). Финализировать резюме и GitHub-портфолио.
Чекпоинт: прошёл 2-3 mock-интервью, готов идти на реальные собесы.
Общие правила
Заголовок раздела «Общие правила»- Каждую неделю — commits в pet-проект (минимум 3-5).
- Каждые 2 недели — статья/перевод на Habr или Medium (не обязательно, но качает понимание).
- Каждый месяц — митап или конференция (онлайн ок: GolangConf, GoCloud Conf).
- Когда чувствуешь, что готов на 70% — подавай резюме. Реальные собесы — лучший учитель.
Sources / Источники
Заголовок раздела «Sources / Источники»- Что спрашивают на собесах в 2025–2026 (Хабр)
- Реальные задачи с собеседований Яндекс/VK/Ozon/Сбер (Хабр)
- Собеседование Go-разработчика middle/middle+/senior (Yandex Practicum)
- Go — 100 вопросов/заданий с собеседований (Хабр)
- Собеседование Golang Часть II — Concurrency (Хабр)
- goavengers/go-interview (GitHub)
- Собеседование на Middle Golang Developer (hacksobesov.com)
- Tinkoff Career — Go Interview
- Ozon Tech — Подготовка к интервью для Go-разработчиков
- Avito Weekend Offer 2025 Go
- Go Internals — Chapter II Interfaces
- Go Interfaces — runtime/iface.go
- Go Channels: A Runtime Internals Deep Dive (2025)
- The Go Scheduler Deep Dive 2025
- Go GC Guide (official)
- How GC Works in Go and How It Affects Your Programs (Allegro Tech)
- Garbage Collection in Go: From Reference Counting to Green Tea (2025)
- Escape Analysis in Go: Stack vs Heap (DEV)
- The Go Memory Model
- When To Use Generics
- Go 1.25 Highlights: Generics and Performance
- Структурированное логирование с slog (Go blog)
- Complete Guide to slog (2025)
- Best Go Logging Tools in 2025 (Dash0)
- Go context — official package docs
- Context deadlines (Boldly Go 2025)
- 7 Powerful Golang Concurrency Patterns 2025
- Scaling With Go in 2025: Futures, Pipelines, Worker Pools
- Data Race Detector (official)
- uber-go/goleak
- Goroutine Leaks in Go 1.26 (DEV)
- Profiling Go Programs (Go blog)
- Go Performance Optimization 2025
- Mock Testing with Testify and Mockery (2025)
- Go Testing Excellence: Table-Driven Tests and Mocking (2026)
- Go Fuzzing — official tutorial
- pq or pgx — Which Driver to Use
- How Golang Talks to PostgreSQL (Simple vs Extended)
- Choose the Right Golang ORM 2025 (Bytebase)
- ORM to Use in Go: GORM, sqlc, Ent, Bun
- Configuring sql.DB (Alex Edwards)
- PostgreSQL Transaction Isolation (official)
- Goose Migration Tool (GitHub)
- Combining DDD, CQRS, and Clean Architecture in Go (Three Dots Labs)
- Clean Architecture in Go (Leapcell)
- Go DI Approaches Wire vs fx vs Manual (Leapcell)
- chi router (GitHub)
- Popular Go Web Frameworks 2026 (JetBrains)
- Gin vs Echo vs Fiber 2026 (Encore)
- gRPC Streaming Best Practices (DEV)
- Graceful Shutdown in Go (VictoriaMetrics)
- Instrumenting a Go application for Prometheus (official)
- OpenTelemetry Go (official)
- Go Observability Stack 2026
- How to Containerize Go Apps with Multi-Stage Dockerfiles (2026)
- Alpine, Distroless, or Scratch (Google Cloud Community)
- Kubernetes Deployments (official)
- What’s your go-to message queue 2025 (Lobsters)
- Чёрная магия unsafe в Go (Хабр)
- 100 Go Mistakes and How to Avoid Them (Manning)
- GoBooks (dariubs)
- GolangConf 2025