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

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

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

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

Наши каналы: https://t.me/proglibrary/9197
Download Telegram
В чем преимущество iter.Seq перед использованием каналов для обхода коллекций

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

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

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

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
Начать рассказывать интервьюеру, как вы ловко дёргаете ручки API через базовый LangChain.

Звучит как отличный план, да? Нет, это мгновенный отказ.

В свежем отчёте по рынку GPU говорится, что 54% компаний стопают ИИ-внедрения тупо из-за конских затрат на инфраструктуру. На серверах более 70% стоимости — это видеокарты. Поэтому на собесах сейчас спрашивают не про красивые промпты, а про жёсткую экономику агентов.

По сути, от вас ждут понимания, как лимитировать ресурсы на лету, роутить запросы и дебажить отказы через механизм time-travel в LangGraph. Если вы до сих пор собираете ботов в ноутбуках, гляньте обновлённый курс «Разработка ИИ-агентов» — фокус там смещён с игрушечных концепций на суровый энтерпрайз.

Что требуют от мидлов и выше:

— интеграция мультиагентных систем по стандарту MCP;
— суровый AgentOps: метрики, трейсинг, защита от деградации пайплайнов;
— локальный деплой Open Source под 152-ФЗ (без этого в финтех можно даже не стучаться).

Прямо сейчас можно урвать курс с увесистой скидкой (49 000 ₽ 62 990 ₽ за базовый тариф и 99 000 ₽ 124 990 ₽ за продвинутый трек), но стоит поторопиться — на потоке осталось всего 5 мест.

👉 Подтянуть архитектуру до уровня прода
🥱3😁1
В чем основная идея пакета bufio и какую проблему он решает

Стандартные интерфейсы io.Reader и io.Writer при каждом вызове Read или Write обычно инициируют системный вызов. Системные вызовы обходятся дорого, так как требуют переключения контекста между пространством пользователя и ядром ОС.

bufio решает эту проблему путем создания промежуточного буфера в оперативной памяти. При чтении bufio.Reader заполняет этот буфер один раз за один системный вызов, а затем отдает данные из памяти по запросу.

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

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

В высоконагруженных системах постоянное создание новых bufio.Reader/Writer создает нагрузку на сборщик мусора. Вместо создания нового объекта для каждого нового HTTP-запроса или файла, можно использовать метод Reset(io.Reader).

Этот метод позволяет взять старый экземпляр bufio.Reader, подставить в него новый источник данных и продолжить работу, используя тот же самый массив памяти в качестве буфера. Это идеальный кандидат для совместного использования с sync.Pool.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔1
Где чаще всего хранится значение Go без указателя, объявленное как локальная переменная

Значения без указателей в локальных переменных обычно размещаются на стеке горутины. Это называется выделением памяти на стеке: компилятор резервирует место на стеке в момент входа в функцию и освобождает его при выходе.

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

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
5👾1
Почему выделение памяти на стеке эффективнее, чем на куче

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

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

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
Что делает OnceValue и зачем он нужен

OnceValue принимает функцию и возвращает обёртку, которая гарантирует вызов этой функции ровно один раз, сколько бы раз ни вызвали обёртку.

Результат кэшируется и возвращается при всех последующих вызовах. Используется для ленивой и дорогостоящей инициализации.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Что произойдёт с памятью, которую держит OnceValue, если обёртка больше не используется

Обёртка это замыкание, которое держит ссылки на once, result и f. Пока есть хотя бы одна ссылка на обёртку, GC не освободит эти данные. Если f тяжёлая инициализация, например, загрузка большого файла, а обёртка живёт в глобальной переменной, то данные живут вечно.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
😁1
😱 Если ваш продукт не умеет отдавать данные в формате, понятном AI-агенту, то вас просто не существует

Скрипт не будет кликать по красивым кнопкам в браузере, он уйдёт к конкуренту с нормальным API. Перестроить архитектуру под машинных клиентов — это уже не хайп, а необходимое условие сохранения конкурентоспособности.

Как адаптировать продукт и не исчезнуть из выдачи:

— интегрировать MCP и A2A-взаимодействие, чтобы агенты могли вас читать;
— научиться контролировать стоимость (лимиты, кэш, роутинг между моделями);
— настроить AgentOps: трейсинг, логирование и отлов регрессий.

Всё это ждёт вас на обновлённом курсе «Разработка AI-агентов». Мы специально сделали фокус на утилитарном инжиниринге и production-ready решениях.

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

Зафиксировать цену и начать деплоить агентов без слива бюджета 👈
😁31🌚1
Можно ли вызывать возвращённую функцию в OnceValue конкурентно

Да, это прямо указано в документации: «The returned function may be called concurrently». OnceValue безопасен для использования из нескольких горутин одновременно.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21
Объясните, для чего нужен пакет singleflight

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

Пакет предоставляет единственную структуру Group, и основной метод Do(key, fn). Если вызов с таким key уже выполняется, новый вызов блокируется и ждёт. Когда первый завершается, все получают одинаковые value, err и флаг shared, сигнализирующий, что результат был разделён.

var g singleflight.Group

func fetchData(key string) (any, error) {
v, err, _ := g.Do(key, func() (any, error) {
return loadFromDB(key) // выполнится только один раз
})
return v, err
}


🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍91
Что делает io.Seeker

Интерфейс с одним методом Seek(offset int64, whence int) (int64, error).

Устанавливает позицию курсора для следующего Read или Write. Возвращает новое абсолютное смещение от начала файла и ошибку если что-то пошло не так.

Ключевое слово «для следующего». Seek сам ничего не читает и не пишет, он только перемещает внутренний указатель. Следующий вызов Read начнёт именно с этой позиции.

Стандартные типы реализующие Seeker:

os.File — файлы на диске
strings.Reader — строки в памяти
bytes.Reader — байтовые срезы в памяти
io.SectionReader — ограниченный участок другого ReaderAt

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
Какой способ для проверки пустой строки лучше: s != "" или len(s) == 0

С точки зрения читаемости, предпочтительнее s != "" — сразу очевидно, что s является строкой. len(s) == 0 более универсальный подход, подходящий для слайсов, мап и других типов, поэтому может быть менее выразительным в контексте строк.

С точки зрения производительности разницы практически нет. Строка в Go — это структура с указателем и длиной, и пустая строка "" не создаётся заново при каждом сравнении. Компилятор с высокой вероятностью оптимизирует оба варианта к одной и той же проверке длины.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👾1
Пишете на Go? Приходите на ВК ДжейТи Митап!

Реальные кейсы и решения из производства, общение с коллегами — всё это в офисах VK:
📍 в Москве — 4 апреля
📍 в Санкт-Петербурге — 11 апреля

В программе два доклада от инженеров VK и неформальная часть:
Москва
• как сократить число запросов с 400 млн до 200 тысяч и построить безопасную платформу мини-приложений
• после — трек на выбор: архитектурная задача с разбором от экспертов VK или обсуждение инженерных новостей в записи подкаста с Никитой Галушко, ведущим разработчиком API ВКонтакте, членом программного комитета Golang Conf/Голанг Конф

Санкт-Петербург
• как построить безопасную платформу мини-приложений и создать высокопроизводительный клиент для Tarantool на Go
• решение архитектурной задачи с обратной связью от инженеров VK
• неформальное общение с коллегами.

Только офлайн. Успевайте зарегистрироваться по ссылке.
Что такое канонизация строк

Канонизация строк это приведение строки к единственной стандартной форме перед сравнением или обработкой.

Одна и та же строка может быть представлена по-разному. Классический пример это Unicode. Символ é можно записать:

- как один кодпоинт U+00E9
- как два кодпоинта e + U+0301

Байтово это разные строки. Визуально — одинаковые. Без канонизации == вернёт false.

В стандартной библиотеке есть пакет golang.org/x/text/unicode/norm, который реализует четыре формы нормализации по стандарту Unicode:
import "golang.org/x/text/unicode/norm"

a := "é" // precomposed
b := "e\u0301" // decomposed

// Без канонизации
fmt.Println(a == b) // false

// С канонизацией
normA := norm.NFC.String(a)
normB := norm.NFC.String(b)
fmt.Println(normA == normB) // true


Где это важно на практике

- Сравнение имён пользователей и email
- Работа с файловой системой (macOS использует NFD, Linux — NFC)
- Поиск по тексту
- Безопасность: канонизация помогает избежать обходов валидации через визуально идентичные, но байтово разные строки

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
8😁1
Что делает io.WriterTo и как он связан с io.Copy

Интерфейс:
type WriterTo interface {
WriteTo(w io.Writer) (n int64, err error)
}


Суть: тип сам берёт управление на себя и решает, как именно записать свои данные в переданный Writer.

Зачем это нужно

io.Copy по умолчанию работает через буфер 32KB и читает кусок из источника, пишет в назначение, и так по кругу. Данные гоняются через userspace.

Но некоторые типы знают более эффективный путь. Например, *os.File на Linux может использовать системный вызов sendfile и данные перемещаются на уровне ядра, без копирования в память процесса.

Как io.Copy это использует

Перед тем как запустить свой буферный цикл, она проверяет интерфейсы:
// 1. Источник умеет писать сам?
if wt, ok := src.(WriterTo); ok {
return wt.WriteTo(dst)
}
// 2. Назначение умеет читать само?
if rf, ok := dst.(ReaderFrom); ok {
return rf.ReadFrom(src)
}
// 3. Fallback — буфер 32KB


Если источник реализует WriterTo, то io.Copy просто уступает ему управление.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍81
Как корректно прокинуть context.Context в функцию внутри OnceValue

Напрямую никак: OnceValue принимает функцию без аргументов.

Контекст нужно захватить в замыкании. Но это опасно, контекст может быть отменён к моменту первого вызова:
// Плохо: ctx может быть уже отменён
ctx := context.Background()
get := sync.OnceValue(func() *DB {
return connect(ctx) // ctx захвачен в замыкании
})


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

Когда горутин много, останавливать каждую вручную не получится. Нужен способ послать сигнал сразу всем:
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

for i := 0; i < 5; i++ {
go func(id int) {
for {
select {
case <-ctx.Done():
fmt.Printf("горутина %d остановлена\n", id)
return
default:
fmt.Printf("горутина %d работает\n", id)
time.Sleep(500 * time.Millisecond)
}
}
}(i)
}

time.Sleep(2 * time.Second)
cancel()
time.Sleep(100 * time.Millisecond)
}


context.WithCancel возвращает контекст и функцию cancel. Контекст передаётся во все горутины. Каждая горутина слушает ctx.Done() через select. Когда вызывается cancel(), канал ctx.Done() закрывается, и все горутины, которые его слушают, получают сигнал одновременно. defer cancel() в начале гарантирует, что ресурсы освободятся даже если функция завершится раньше времени.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍82
Скомпилируется ли код при циклической зависимости

В
Go компилятор запрещает циклические зависимости между пакетами. Если пакет A импортирует B, а B импортирует A, то код просто не соберётся.

Go намеренно не даёт собрать такой код по двум причинам. Первая: порядок инициализации. Если A зависит от B, а B от A, непонятно, что инициализировать первым. Вторая это структура кода. Цикл между пакетами почти всегда сигнализирует о проблеме в архитектуре.

Как решить проблему

Обычно помогает одно из трёх. Вынести общий код в отдельный пакет, от которого зависят оба. Использовать интерфейс вместо прямого импорта. Пересмотреть границы пакетов: если два пакета так тесно связаны, возможно, это один пакет.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
3
Почему append на nil-срезе работает, а запись в nil-мапу вызывает панику

nil-срез это валидное значение с len == 0 и cap == 0. Когда вызывается append(), Go сам выделяет новую память под капотом. Явная инициализация не нужна.

var a []int
a = append(a, 4, 5, 6) // Go выделяет память сам
fmt.Println(a) // [4 5 6]


nil-мапа не инициализирована, у неё нет памяти для хранения данных. Любая попытка записи немедленно вызывает панику в рантайме. Перед использованием нужно явно вызвать make.

var m map[int]int
// m[1] = 1 ← паника!

m = make(map[int]int)
m[1] = 1 // теперь работает


append это функция, которая сама управляет памятью и возвращает новый срез. Мапа же это указатель на структуру данных, и без инициализации этой структуры просто не существует.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Как работает sync.WaitGroup изнутри

Внутри WaitGroup хранит счётчик. Вы управляете им через три метода:

Add(n) увеличивает счётчик на n. Вызывать его нужно до запуска горутин, а не внутри них — иначе возникает гонка данных: Wait может сработать раньше, чем Add успеет зарегистрировать горутину.

Done() уменьшает счётчик на 1. Его вызывают внутри горутины, когда работа завершена. Чтобы не забыть вызвать его даже при панике, принято писать defer wg.Done() в самом начале горутины.

Wait() блокирует выполнение до тех пор, пока счётчик не вернётся к нулю. Как только все горутины вызвали Done() — основной поток продолжает работу.

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