Оплачиваемая стажировка в банке собственной разработки: Go, SQL, алгоритмы и реальные задачи в микросервисной архитектуре. Переход в штат через ~6 месяцев.
Фишка: Ozon Банк — банк как IT-продукт: процессинг, учётное ядро, СБП, карты и рассрочка разрабатываются in-house на микросервисах без жёсткой иерархии; стажёры с первых дней работают с реальными сервисами.
| Этап | Длительность | Что проверяют |
|---|---|---|
| Скрининг / HR | 20–30 мин | Мотивация, готовность к офису в Москве 40 ч/нед, совмещение с учёбой (3+ курс), базовое знакомство с Go и backend |
| Техническое интервью | 60–90 мин | Вопросы по Go (горутины, каналы, слайсы, интерфейсы, ошибки), простая алгоритмическая задача, SQL-запросы, разбор кода «что выведет» |
| Встреча с командой | 30–60 мин | Культурный fit, интерес к финтеху и банковским системам, вопросы о стажировке и наставничестве |
Обязательный минимум
Плюсом будет
Что такое горутина?
Легковесный поток, планируется runtime (GMP). Стартует через go func(). Дешевле OS-thread, стек от ~2 КБ, растёт динамически.
Как устроен слайс?
Структура: указатель на массив, len, cap. Передаётся по значению (копируется заголовок), общий underlying array. append может реаллоцировать.
Что такое интерфейс?
Набор методов (implicit implementation). Внутри — пара (type, value). nil-интерфейс ≠ интерфейс с nil-значением внутри.
Как реализована инкапсуляция в Go?
Через видимость: заглавная буква — exported, строчная — package-private. Нет модификаторов public/private.
Какие знаешь встроенные типы в Go?
bool, string, числовые (int, float, complex), array, slice, map, chan, struct, pointer, interface, function.
Как устроена строка в Go?
Неизменяемый байтовый срез (обычно UTF-8). Конкатенация создаёт новую строку. range итерирует по рунам.
В чём разница между Go и другими языками?
Статическая типизация, GC, встроенная конкурентность, нет наследования/исключений, композиция через embedding, быстрая компиляция.
Ловушка: Частая ловушка на стажировке — задачи «что выведет» с append, указателями в range и defer. Прогоняй код на бумаге пошагово.
Совет: На intern-уровне достаточно уверенно объяснить слайс/map/указатель и написать простую функцию без IDE.
Какие есть типы каналов в Go?
Небуферизованный (синхронная передача) и буферизованный (make(chan T, n)). Направление: chan, <-chan, chan<-.
В чём разница между буферизованным и небуферизованным каналом?
Unbuffered: отправитель блокируется до получения. Buffered: блокировка только при заполнении/опустошении буфера.
Что происходит при записи в закрытый канал?
Panic. Чтение из закрытого — нулевое значение + ok=false. Закрывать должен отправитель.
Зачем нужен канал?
Коммуникация между горутинами, синхронизация, передача ownership данных. «Не общайтесь через shared memory — делитесь памятью через общение».
Что такое контекст?
context.Context — отмена, дедлайн, передача значений по цепочке вызовов. context.WithCancel/WithTimeout, всегда defer cancel().
Какие знаешь примитивы синхронизации?
sync.Mutex/RWMutex, sync.WaitGroup, sync.Once, sync.Map, atomic, каналы. Выбор зависит от паттерна доступа к данным.
Что такое Mutex?
Взаимное исключение: Lock/Unlock защищают критическую секцию. RWMutex — много читателей или один писатель.
Что такое WaitGroup в Go?
Add(n) перед запуском горутин, Done() в каждой, Wait() в main. Счётчик должен совпасть, иначе deadlock или panic.
Что такое паттерн GMP?
Модель планировщика Go: G (goroutine), M (OS thread), P (processor/context). P держит локальную очередь горутин, work stealing между P.
Что такое DeadLock?
Взаимная блокировка: горутины ждут друг друга. Классика — два Mutex в разном порядке или небуферизованный канал без читателя.
Что такое sync.Map?
Concurrent map для read-heavy сценариев. Под капотом — shards + atomic. Для write-heavy лучше Mutex + обычная map.
Фишка: Вопрос про GMP встречается именно на собесах Ozon — это маркер глубины понимания runtime Go.
Ловушка: select с time.After в цикле без break выходит только из select, не из for — классическая ошибка в задачах с тайм-аутом.
Что такое Error в Go?
Встроенный интерфейс error с методом Error() string. Ошибки — значения, возвращаются явно. errors.Is/As, fmt.Errorf с %w для wrapping.
Как работает обработка ошибок?
Нет try/catch. Проверка if err != nil после каждого вызова. Цепочка обёрток через %w, разбор через errors.Is/As.
Как происходит тестирование в Golang?
Пакет testing: TestXxx(t *testing.T), table-driven tests, t.Run subtests. go test, -race, -cover. Моки через интерфейсы.
Совет: На стажировке ценят умение написать table-driven тест для своей функции — это прямо в обязанностях вакансии.
Как устроена хеш-таблица (HashMap)?
Массив бакетов, хеш-функция → индекс. Коллизии: chaining или open addressing. В Go map — хеш-таблица с эвакуацией при росте.
Какая алгоритмическая сложность доступа по ключу в Map?
Амортизированно O(1). При деградации хеша или множестве коллизий — хуже. Итерация — O(n).
Базовые структуры данных из вакансии
Hash map — O(1) lookup. Связный список — вставка O(1), поиск O(n). Дерево (BST) — поиск/вставка O(log n) при балансировке.
Совет: Для стажировки достаточно уровня LeetCode Easy: два указателя, hash map для подсчёта, обход массива/строки.
Какие типы БД знаешь?
Реляционные (PostgreSQL, MySQL) — ACID, SQL. NoSQL: документные (MongoDB), key-value (Redis), колоночные (ClickHouse).
Для чего нужна транзакция?
Атомарность группы операций: либо все, либо ни одна. ACID. В банке критично для переводов и списаний.
Какие знаешь виды JOIN?
INNER — пересечение. LEFT/RIGHT — все строки одной таблицы + совпадения. FULL OUTER. CROSS — декартово произведение.
В чём разница между WHERE и HAVING?
WHERE фильтрует строки до GROUP BY. HAVING фильтрует группы после агрегации.
Расскажи про опыт работы с PostgreSQL
Даже учебный опыт: схемы, индексы (B-tree), EXPLAIN, миграции. Для стажировки важно понимание, а не годы практики.
Фишка: В банковском контексте часто спрашивают про race condition при списании баланса — связка SQL-транзакций и конкурентности.
Ловушка: SELECT + UPDATE баланса без транзакции и блокировки — классический антипаттерн, который ждут услышать в ответе.
Что такое HTTP?
Протокол запрос-ответ: метод (GET/POST/PUT/DELETE), статус-коды, заголовки, тело. Stateless. HTTPS = HTTP + TLS.
Что такое REST API?
Архитектурный стиль: ресурсы по URL, HTTP-методы как глаголы, JSON/XML. Идемпотентность GET/PUT/DELETE.
В чём разница между TCP и UDP?
TCP — надёжный, с установкой соединения, порядок пакетов. UDP — без гарантий, быстрее, для стриминга/DNS.
Что такое gRPC?
RPC-фреймворк на HTTP/2 + protobuf. Стриминг, контракт через .proto. Быстрее JSON REST для межсервисного общения.
Что такое Kafka?
Распределённый лог событий. Producer → topic/partitions → consumer groups. Используется для асинхронной коммуникации микросервисов.
Какие знаешь гарантии доставки в Kafka?
At most once, at least once, exactly once (с idempotent producer + transactions). Trade-off между надёжностью и производительностью.
Что такое Consumer group в Kafka?
Группа потребителей делит партиции: одна партиция — один consumer в группе. Rebalance при добавлении/удалении.
Расскажи про опыт работы с Linux
Базовые команды: ls, cd, grep, ps, kill, curl. Понимание процессов, файловой системы, прав доступа.
Как убить процесс?
kill PID (SIGTERM), kill -9 PID (SIGKILL). ps aux | grep, pkill. SIGTERM — graceful, SIGKILL — немедленно.
Совет: Для стажировки Kafka/gRPC — nice to have. Достаточно понимать зачем очередь в микросервисах, не углубляясь в настройку кластера.
Расскажи о себе
2–3 мин: курс/факультет, как пришёл в Go, 1–2 проекта (учебные/пет-проекты), почему backend и финтех.
Расскажи о своём опыте
Для стажировки: курсовые, хакатоны, open source, самообучение. Акцент на том, что уже пробовал писать на Go.
Какие ожидания от будущей работы?
Рост под менторством, реальные задачи, переход в штат. Покажи готовность учиться и работать в офисе.
Почему ищешь работу / почему Ozon Банк?
Интерес к банковскому домену, микросервисам, масштабу Ozon. Не «ради денег», а ради продукта и команды.
Какие у тебя есть вопросы?
Спроси про стек команды, онбординг стажёра, тип задач на первые месяцы, критерии перехода в штат.
Приведи пример выполнения сложной задачи
STAR без персональных деталей: учебный проект, где разобрался с concurrency/SQL/отладкой. Покажи процесс, не только результат.
Фишка: В Ozon ценят прямые вопросы и открытый диалог — задавай вопросы интервьюеру, это плюс, а не минус.
Анализ поведения append и передачи слайсов
Определить вывод fmt.Println после каждого шага: ```go slice := make([]int64, 0, 4) slice = append(slice, 1, 2) fmt.Println(slice) // ? append1(slice, 3) fmt.Println(slice) // ? mutate1(slice, 1, 99) fmt.Println(slice) // ? func append1(in []int64, value int64) { in = append(in, value) } func mutate1(in []int64, idx, value int64) { in[idx] = value } ```
Вывод: [1 2], [1 2], [1 99]. append1 не меняет исходный слайс (переприсваивание локальной копии заголовка). mutate1 меняет элемент по индексу в общем underlying array — [1 2] → [1 99].
Сложность: O(1) на операцию
Указатели в цикле range
Что выведет программа и почему? ```go first := []int{10, 20, 30, 40} second := make([]*int, len(first)) for i, v := range first { second[i] = &v } fmt.Println(*second[0], *second[1]) ```
Выведет два одинаковых числа (40 40 в Go 1.22+ или 40 40 в старых версиях — v переиспользуется). Все указатели ссылаются на одну переменную v. Исправление: second[i] = &first[i] или v := v; second[i] = &v.
Сложность: O(n)
Безопасное списание с баланса
Найти проблемы в коде списания баланса и предложить исправление: ```go func withdrawBalance(user_id int32, amount float32) { balance = exec('select balance from user where id=$1', user_id) if balance - amount >= 0.0 { exec('update user set balance=balance-$1 where id=$2', amount, user_id) } } ```
Проблемы: race condition (два параллельных списания), нет транзакции, float для денег, нет проверки affected rows, TOCTOU. Исправление: BEGIN; SELECT ... FOR UPDATE; проверка; UPDATE; COMMIT. Использовать DECIMAL/копейки. Идемпотентность через idempotency key.
Сложность: O(1) при индексе по id
Разворот массива на месте
Написать функцию reverse(nums []int) []int, которая разворачивает слайс без дополнительной памяти (in-place).
```go
func reverse(nums []int) []int {
for i, j := 0, len(nums)-1; i < j; i, j = i+1, j-1 {
nums[i], nums[j] = nums[j], nums[i]
}
return nums
}
```Сложность: O(n) время, O(1) память
Чтение из канала с тайм-аутом
Объяснить вывод и исправить бесконечный timeout: ```go ch := make(chan int) go func() { for i := 0; i < 5; i++ { ch <- i time.Sleep(time.Second) } close(ch) }() for { select { case v := <-ch: fmt.Println(v) case <-time.After(3 * time.Second): fmt.Println("timeout") break } } ```
После 2-го значения срабатывает timeout (3 сек > 1 сек между сообщениями), но break выходит только из select, цикл продолжается. Исправление: использовать for-select с проверкой ok при чтении из закрытого канала, или return/label break из for.
Сложность: O(n)
3 дня
7 дней
14 дней
| Блок | Готов, если... |
|---|---|
| Go: основы | можешь объяснить устройство слайса и map, написать функцию с указателями и пройти задачу «что выведет» с append |
| Go: конкурентность | можешь объяснить разницу buffered/unbuffered каналов, написать worker pool с WaitGroup и отменой через context |
| Алгоритмы | решаешь Easy-задачи за 15–20 мин: hash map, два указателя, обход массива |
| SQL | пишешь JOIN + GROUP BY, объясняешь зачем транзакция и что такое race condition при списании |
| Поведение | есть 2-минутный питч, ответ на «почему Ozon Банк» и 3 вопроса интервьюеру |
В день собеседования