Библиотека Go для собеса | вопросы с собеседований
7.42K subscribers
258 photos
10 videos
1 file
766 links
Вопросы с собеседований по Go и ответы на них.

По рекламе: @proglib_adv

Учиться у нас: https://proglib.io/w/0b524a15

Для обратной связи: @proglibrary_feeedback_bot

Наши каналы: https://t.me/proglibrary/9197
Download Telegram
Как ведут себя срезы в Go на граничных значениях

Срез это не массив, а заголовок из трёх полей: указатель на данные, длина и ёмкость. Это важно понимать, прежде чем говорить о границах.

Правило границ. При нарезке a[low:high] должно выполняться 0 <= low <= high <= cap(a). Примечательно, что high ограничен именно cap, а не len — это позволяет «заглянуть» вперёд за текущую длину, если базовый массив это допускает.

Нарушение границ — паника в рантайме. Компилятор не проверяет корректность индексов — это делает рантайм. a[0:len(a)+1] скомпилируется, но упадёт с slice bounds out of range при выполнении.

Пустой срез — не nil. a[2:2] — валидный срез с длиной 0. Он инициализирован и указывает на память. var s []int — другое: nil-срез, у которого указатель равен nil. len и cap у обоих равны нулю, но s == nil вернёт true только для первого.

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

Трёхиндексная нарезка. a[low:high:max] задаёт ёмкость результата явно: cap = max - low. Используется, чтобы append не «прорвался» за нужный регион и не затронул соседние данные в исходном массиве.

Рост при append. Когда len == cap, Go создаёт новый массив, копирует данные и возвращает срез с новым указателем. С этого момента два среза больше не делят память — и это один из самых частых источников неожиданного поведения на практике.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Чем отличаются Lock-Free и Wait-Free алгоритмы

Lock-Free алгоритмы это безмьютексные структуры данных, где хотя бы один поток всегда завершает операцию за конечное число шагов:

• Гарантия: система в целом прогрессирует (нет глобальной блокировки).

• Минус: некоторые потоки могут «застревать» в бесконечных retry-циклах (livelock).

• Плюсы: высокая производительность, проще в реализации.

Wait-Free алгоритмы это строгий подкласс lock-free, где каждый поток завершает операцию за конечное число шагов независимо от других:

• Гарантия: индивидуальный прогресс для всех (полная справедливость, нет голодания ресурсов).

• Минус: сложнее реализовать, ниже производительность из-за оверхеда на координацию.

• Когда использовать: в реал-тайм системах, например, ABA-free структуры.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🥱1
Как передавать сигналы в каналах

Первый и самый простой это пустая структура. Она не занимает памяти и означает только одно: событие произошло:
done := make(chan struct{})

go func() {
// работа выполнена
close(done)
}()

<-done // ждём сигнала


close — лучший способ сигналить завершение, потому что все читатели получат сигнал одновременно.

Второй способ это передать булево значение или ошибку, когда важно передать не только факт события, но и его результат:
result := make(chan error, 1)

go func() {
err := doSomething()
result <- err
}()

if err := <-result; err != nil {
log.Fatal(err)
}


Третий способ — context.Context. Это стандарт в продакшн-коде. Контекст несёт в себе сигнал отмены, дедлайн и значения.

Внутри он тоже использует канал:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

select {
case result := <-work(ctx):
fmt.Println(result)
case <-ctx.Done():
fmt.Println("timeout:", ctx.Err())
}


🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
//go:build vs // +build — в чём разница

// +build — старая форма, использовалась до Go 1.17. Требует пустой строки после блока директив, а логика объединяется через пробелы (AND) и запятые (OR), что легко перепутать:
// +build linux darwin
// +build amd64

package main


//go:build это новая форма, появилась в Go 1.17. Синтаксис стал читаемым: используются обычные логические операторы &&, ||, !:
//go:build (linux || darwin) && amd64

package main


🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍71
Почему в go switch отличается от других языков

В большинстве языков switch требует явного break в каждом case, иначе выполнение провалится в следующий блок. Go сделал иначе.

В Go каждый case автоматически завершается — никакого проваливания:
switch status {
case 1:
fmt.Println("one") // выполнится только это
case 2:
fmt.Println("two") // сюда не попадём
}


В C/Java без break выполнились бы оба блока.

Есть fallthrough, C-стайл поведение можно воспроизвести явно:
switch status {
case 1:
fmt.Println("one")
fallthrough // явно говорим "провалиться" дальше
case 2:
fmt.Println("two") // выполнится тоже
}


Несколько значений в одном case:
switch day {
case "Saturday", "Sunday":
fmt.Println("выходной")
case "Monday", "Friday":
fmt.Println("почти выходной")
}


🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5
🏃‍♀️ Мы собрали бесплатный мега-гайд по ии-агентам 👇

В первой части постов навалили жесткой базы, чтобы вправить мозги на место. Во второй дали конкретные инструменты, фреймворки и пошаговые инструкции, что нужно кодить прямо сейчас.

Часть 1. Введение, юзкейсы и реальность
Разбираемся с терминами, снимаем розовые очки и смотрим, где ИИ реально приносит бабки, а где только жжет нервы:

1. «Так что вообще считается AI-агентом?»
2. «Где тут бот, а где уже AI-агент?»
3. «Не надо пихать AI-агента в каждую задачу»
4. «Что уже можно спокойно делать через AI-агентов?»
5. «А что через AI-агентов пока лучше не трогать?»

Часть 2. Изнанка, ошибки и архитектура
Как всё это устроено под капотом, чтобы не слить бюджет и не наломать дров на старте:

6. «Можно ли просто сесть вечером и собрать себе AI-агента?»
7. «С чего вообще начать, если хочется попробовать AI-агентов»
8. «Почему AI-агент может внезапно начать творить дичь»
9. «Где AI-агенты реально экономят время, а где только добавляют возни»
10. «Почему они жрут столько денег?»

Часть 3. Хардкорная практика (Что делать руками)
Хватит теории. Открываем ноут, запускаем Cursor и делаем нормальные, отказоустойчивые системы:

11. «Почему одного промпта мало?»
12. «Почему AI-агенту мало просто “дать доступ к данным”»
13. «Если не следить за AI-агентом, он быстро начинает жить своей жизнью»
14. «Собрать демку легко. Но как же сделать нормально»
15. «Как сделать, чтобы это не развалилось через неделю?»

👍 Сохраняйте пост в избранное, чтобы не потерять.

🤫 А завтра стартует наш курс по ии-агентам
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
Что происходит со стеком горутины при росте

Когда стека не хватает, рантайм не паникует. Он выделяет новый стек большего размера, копирует туда всё содержимое старого, обновляет указатели и освобождает старый.

Для производительности важны два момента.

1. Копирование не бесплатно. Если горутина постоянно растёт и сжимается на границе текущего размера, рантайм будет копировать снова и снова. Это stack thrashing и в горячем пути это даёт ощутимый overhead.

2. После копирования все указатели на стековые переменные перезаписываются. Поэтому нельзя просто передать указатель на стек в C через cgo, стек может переехать в любой момент, и указатель станет невалидным.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
Как можно развернуть срез без использования дополнительной памяти

Два указателя, движущихся навстречу друг другу:
func reverse(s []int) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}


Начиная с Go 1.21 в стандартной библиотеке есть готовая функция slices.Reverse(), которая делает то же самое под капотом:
import "slices"

s := []int{1, 2, 3, 4, 5}
slices.Reverse(s) // [5, 4, 3, 2, 1]


Оба варианта работают in-place, без выделения дополнительной памяти.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13
Что такое префиксная сумма и где её использовать

Префиксная сумма это массив, где каждый элемент prefix[i] хранит сумму всех элементов исходного массива от 0 до i включительно.

Построение:
func buildPrefix(arr []int) []int {
prefix := make([]int, len(arr)+1)
for i, v := range arr {
prefix[i+1] = prefix[i] + v
}
return prefix
}

func rangeSum(prefix []int, l, r int) int {
return prefix[r+1] - prefix[l]
}


Задача: количество подмассивов с суммой K

func subarraySum(nums []int, k int) int {
count := 0
prefix := 0
seen := map[int]int{0: 1}

for _, v := range nums {
prefix += v
count += seen[prefix-k] // если ключа нет — вернёт 0, это фича Go
seen[prefix]++
}

return count
}


Где использовать:

• Запросы суммы на отрезке — самый очевидный случай. Если массив не меняется, а запросов много, строишь префикс один раз и отвечаешь за O(1).

• Поиск подмассива с заданной суммой — сводишь к задаче "найти два индекса префикса с разностью K", решается через хэшмап за O(n).

• Задачи на чётность/нечётность суммы — считаешь префикс по модулю 2, ищешь совпадения.

• Задачи на матрицах — 2D префикс даёт сумму любого прямоугольника за O(1), используется в задачах с изображениями, тепловыми картами, grid-задачах.

• Sliding window с условием на сумму — иногда проще через префикс, чем двумя указателями, особенно если окно не фиксированное.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
Какие преимущества даёт синглтон в Go

+ Единая точка доступа к ресурсу

Синглтон гарантирует, что во всём приложении существует только один экземпляр объекта — например, пул соединений к базе данных или менеджер конфигурации. Это исключает дублирование ресурсов и случайное создание конкурирующих экземпляров.

+ Ленивая инициализация через sync.Once

В Go синглтон удобно реализуется через sync.Once: объект создаётся только при первом обращении. Это экономит память и время запуска, если ресурс вообще не понадобится в ходе работы программы.

+ Глобальное состояние без глобальных переменных

Синглтон позволяет инкапсулировать общее состояние внутри структуры с методами, избегая «голых» глобальных переменных. Это улучшает читаемость кода и упрощает контроль за изменениями состояния.

+ Потокобезопасная инициализация из коробки

При использовании sync.Once Go гарантирует, что функция инициализации будет вызвана ровно один раз, даже если несколько горутин обратятся к синглтону одновременно. Разработчику не нужно писать дополнительную логику блокировок.

🐸Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
3
Какие проблемы могут возникнуть при использовании синглтона в многозадачных приложениях

- Проблемы с потокобезопасностью

Если синглтон не был правильно реализован с учётом многозадачности, могут возникнуть гонки данных, когда несколько горутин одновременно пытаются создать или получить доступ к экземпляру синглтона.

- Проблемы с масштабируемостью

Если приложение становится более сложным и распределённым, синглтон может стать ограничением для масштабируемости. В распределённых системах или микросервисах использование синглтонов может привести к проблемам с состоянием и затруднить масштабирование приложения.

- Проблемы с производительностью

Синглтон может стать узким местом в многозадачных приложениях, особенно если доступ к нему синхронизирован с использованием блокировок, таких как мьютексы. Если горутины часто обращаются к синглтону и блокируют его, это может существенно снизить производительность программы.

- Проблемы с инициализацией синглтона

Если синглтон требует сложной инициализации, например, создание нескольких объектов, настройка зависимостей, это может замедлить работу приложения, особенно если инициализация не оптимизирована.

🐸Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🥱2
Чем OnceValue лучше глобальной переменной с init()

init() запускается при старте программы, даже если значение никогда не понадобится.

OnceValue — ленивая инициализация: вычисление происходит только при первом вызове. Это полезно для тяжёлых операций (соединения с БД, чтение файлов) и упрощает тестирование.

🐸Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Где и когда уместно применять синглтон в Go

Пул соединений с базой данных

Создание нового соединения это дорогостоящая операция. Синглтон гарантирует, что во всём приложении используется один общий пул соединений (*sql.DB), что снижает накладные расходы и предотвращает исчерпание лимитов на стороне базы данных.

Менеджер конфигурации

Параметры приложения: файл конфигурации, переменные окружения. Они читаются один раз при старте. Синглтон даёт удобный единый доступ к этим данным из любой части программы без повторного чтения и парсинга.

Логгер

Глобальный логгер: он должен быть один, настроен единожды (уровень логирования, вывод, формат) и доступен из любого пакета. В Go это часто реализуется через log/slog или сторонние библиотеки вроде zap.

Кэш в памяти

Если приложению нужен разделяемый кэш (например, результаты тяжёлых вычислений или ответы внешних API), синглтон обеспечивает единое хранилище для всех горутин. Важно сочетать его с sync.RWMutex или использовать sync.Map для безопасного доступа.

🐸Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
1
🏃‍♀️ Как провести вечер вторника с пользой для карьеры?

Включайте кружок там личное приглашение от спикера. 👆

Уже завтра в прямом эфире, разбираем архитектуру контекста в мультиагентных системах.

🤫 Секретный лут:
промик на 5.000₽. Он достанется только тем, кто придет на прямой эфир.

👉 Регистрируйтесь на трансляцию
Please open Telegram to view this post
VIEW IN TELEGRAM
Когда случается коллизия у хэш-функции

Коллизия возникает, когда два разных ключа дают одинаковый хэш. Это не баг конкретной реализации, любая хэш-функция с ограниченным выходным пространством теоретически может выдать одинаковый результат для разных входных данных.

Коллизии сами по себе не ломают мапу, она продолжает работать корректно. Но если коллизий становится много, поиск деградирует от O(1) до O(n), потому что вместо прямого доступа начинается перебор.

🐸Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
Осталось всего 4 места на курс по ИИ-агентам. 30 апреля закрываем набор окончательно.

В ГС честно рассказали:
— Кому курс не подойдет;
— Какой хардкор в программе (LangGraph, AutoGen, CrewAI);
— Как мы даем токены, чтобы вы не тратили свои деньги.

🏃‍♀️ Записаться, пока есть места
Please open Telegram to view this post
VIEW IN TELEGRAM
Почему в Go невозможно поставить открывающую фигурную скобку на новой строке? Это ограничение компилятора, линтера или что-то более фундаментальное

Это фундаментальное требование грамматики языка, обусловленное работой лексера.

В Go лексер автоматически вставляет точку с запятой в конец строки, если последний токен перед переносом строки является:

• идентификатор: foo, x, myVar
• литерал: 42, "hello", true
break, continue, fallthrough, return, ++, --, ), ], }

Это означает, что код вида:
func foo()
{
}


лексер преобразует в:
func foo();  // ← `;` вставлена после `)`
{
}


🐸Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
3🔥6
Зачем нужен Makefile

Makefile это файл с описанием целей сборки, который запускается утилитой make.

В Go-проектах он используется для автоматизации повторяющихся операций: форматирования, линтинга, статического анализа и компиляции.

Вместо того чтобы каждый раз вручную запускать go fmt, go vet и go build, достаточно одной команды make и все шаги выполнятся в нужном порядке.

🐸Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
2
Как можно сделать разреженный массив

Вместо того чтобы перечислять значения, достаточно указать только значимые позиции.

Нужно в литерале массив указать индекс элемента явно через индекс: значение. Все значения, что не будут указаны заполнятся дефолтными значениями.

Пример:
var x = [12]int{1, 5: 4, 6, 10: 100, 15}


🐸Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍112
Насколько безопасно передавать слайсы в горутины

Если несколько горутин читают и пишут в один слайс одновременно — это гонка данных, даже если они обращаются к разным индексам.

append может вызвать реаллокацию: старый массив заменяется новым, и другие горутины продолжат работать с устаревшим указателем

Передача слайса по значению копирует только заголовок , но не данные — горутины всё равно разделяют память

Что делать:

sync.Mutex / sync.RWMutex — если нужен общий доступ с защитой

• передавать копию данных через канал, если горутина должна работать независимо

• sync.Map или разбиение на независимые диапазоны, если горутины работают с непересекающимися частями

Важный нюанс: если горутины работают со строго непересекающимися индексами и append не вызывается — гонки нет. Но доказать это на ревью сложно, поэтому явная синхронизация предпочтительнее.

🐸Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
2