Библиотека Go для собеса | вопросы с собеседований
Ir al canal en Telegram
Вопросы с собеседований по Go и ответы на них. Покажем, как запустить своего ии-агента: https://clc.to/tvpmD По рекламе: @proglib_adv Для обратной связи: @proglibrary_feeedback_bot Наши каналы: https://t.me/proglibrary/9197
Mostrar más7 429
Suscriptores
-324 horas
-167 días
+930 días
Archivo de publicaciones
❓ Можно ли переиспользовать WaitGroup
sync.WaitGroup можно переиспользовать, но только корректно завершив предыдущий цикл ожидания.
Переиспользовать один и тот же экземпляр можно для партий выполняемых задач: дождались первую партию: counter вернулся к 0, Wait() вышел; и после этого можно снова делать Add и запускать новый набор горутин.
🐸 Библиотека Go для собеса❓ Что будет если вызвать WaitGroup.Add() с отрицательным числом
Поведение зависит от того, как это влияет на внутренний счётчик.
WaitGroup.Add() принимает любое целое число, включая отрицательные. Это можно использовать для уменьшения счётчика:
var wg sync.WaitGroup
wg.Add(5) // счётчик = 5
wg.Add(-2) // счётчик = 3
Если Add() делает внутренний счётчик отрицательным, то возникнет паника:
🐸 Библиотека Go для собеса❓ Почему в Go 1.18 добавили алиас any, а не убрали interface{}
Чтобы сохранить полную обратную совместимость, а не ломать миллионы строк существующего кода.
С точки зрения компилятора и поведения программы
any и interface{} абсолютно эквивалентны.
🐸 Библиотека Go для собеса❓ Что возвращает range для строки
В цикле
for index, value := range str строка str итерируется по UTF-8 рунам. index – байтовый индекс начала руны, value – значение руны.
🐸 Библиотека Go для собесаМногопоточность в Golang с нуля. Бесплатный вебинар — 19 февраля в OTUS
Многопоточность — одна из причин популярности Go в серверной и системной разработке. Горутины и каналы позволяют писать производительный и масштабируемый код без сложных абстракций.
— На открытом уроке разберём базовые принципы параллелизма и конкурентности в Go.
— Поговорим о том, как работают горутины, зачем нужны каналы и в каких сценариях Go особенно эффективен.
— На практических примерах покажем типовые задачи, где многопоточность даёт реальный прирост производительности, и обсудим базовые best practices.
Урок подойдёт новичкам, которые только начинают знакомство с Go, а также разработчикам с опытом в других языках, желающим понять философию конкурентности в Go.👉 Записаться: https://clc.to/DXsJng Этот вебинар проходит в формате открытого урока в прямом эфире курса «Golang Developer. Basic». Реклама. ООО «Отус онлайн‑образование», ОГРН 1177746618576
❓ Как Go обрабатывает операции ввода-вывода с файлами
Go предоставляет встроенные инструменты для работы с файловой системой: пакет os для базовых операций открытия, создания и закрытия файлов, а также io и bufio для буферизованного ввода-вывода.
Чтение файла
Типичный паттерн: открыть файл через
os.Open(), затем прочитать содержимое построчно с помощью bufio.Scanner:
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
file, err := os.Open("example.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
Запись и дозапись в файл
Для создания нового файла используйте os.Create(), для дозаписи — os.OpenFile() с флагами os.O_APPEND|os.O_WRONLY:
// Создание и запись
file, err := os.Create("output.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
file.WriteString("Hello, Go!\n")
// Дозапись в существующий файл
file, err := os.OpenFile("output.txt",
os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
file.WriteString("Appended line\n")
Используйте bufio.Writer для буферизации при записи больших объёмов данных — это значительно ускорит операции ввода-вывода.
🐸 Библиотека Go для собесаИнтервью как ресурс: почему нельзя «сливать» попытки 💎
В текущем сезоне воронка найма сузилась 📉: получить приглашение на техническое собеседование стало сложнее, чем пройти его. В таких условиях каждый слот в календаре превращается в актив с высокой стоимостью, а импровизация при проверке hard-skills становится неоправданным риском.
Качественная подготовка требует выхода за рамки базового синтаксиса Go. Необходимо охватить весь технологический стек, включая базы данных и архитектурные паттерны. Систематизировать знания поможет подборка из 55 реальных вопросов, которые сейчас задают на собеседованиях.
Регулярный самоаудит по этому списку повышает шансы на успешный оффер 🧠.
❓ Можно ли вызвать анонимную функцию сразу при объявлении
Да, добавьте
() с аргументами в конце для немедленного вызова. Компилятор Go это поддерживает нативно.
result := func(a, b int) int { return a + b }(3, 5)
fmt.Println(result) // 8
Функция создаётся и исполняется сразу.
🐸 Библиотека Go для собеса❓ Что Go-компилятор может сделать с кодом во время компиляции
Если выражение состоит из констант, компилятор считает результат сразу.
const x = 5 * 10 + 3 становится 53. Работает для арифметики, строк, булевых операций
Функции до 80 байт встраиваются в место вызова. Компилятор подставляет тело, убирает call/ret overhead.
Если код не используется вовсе, то компилятор его удалит.
🐸 Библиотека Go для собеса❓Почему defer работает по LIFO
Каждый defer добавляет вызов в стек текущей горутины. При возврате из функции
runtime.deferreturn разматывает стек снизу вверх, LIFO-порядок.
🐸Библиотека Go для собеса❓ К каким типам циклов применяется анализатор forvar — ко всем for или только к range
Согласно документации, анализатор forvar применяется только к range циклам.
Причина в том, что проблема с переиспользованием переменной цикла существовала именно в контексте range:
// До Go 1.22 - проблемный код
for _, x := range items {
go func() {
fmt.Println(x) // все горутины видели последнее значение x
}()
}
// Решение до Go 1.22
for _, x := range items {
x := x // создание новой переменной для каждой итерации
go func() {
fmt.Println(x) // теперь каждая горутина видит свое значение
}()
}
В обычных for циклах такой проблемы не было, так как переменная счетчика и так создавалась заново:
// Здесь проблемы не было даже до Go 1.22
for i := 0; i < 10; i++ {
go func() {
fmt.Println(i) // каждая итерация имела свой i
}()
}
🐸Библиотека Go для собеса❓ Можно ли получить указатель на value в мапе
В Go невозможно взять указатель на значение, хранящееся в мапе.
Мапа в Go реализована как хеш-таблица, которая может динамически реорганизовываться при добавлении элементов. Когда мапа растёт, значения перемещаются в памяти, что делало бы любые указатели на них недействительными.
Код, который выдаст ошибку
m := map[string]int{"a": 1}
p := &m["a"] // cannot take the address of m["a"]
Что делать вместо этого
Хранить указатели в мапе:
m := map[string]*int{}
val := 1
m["a"] = &val
Извлечь значение, изменить, записать обратно:
m := map[string]int{"a": 1}
val := m["a"]
val++
m["a"] = val
Использовать структуры:
type Data struct { value int }
m := map[string]*Data{"a": {1}}
m["a"].value++ // работает
🐸Библиотека Go для собеса❓ В чём разница между nil-слайсом и пустым слайсом
nil-слайс и пустой слайс имеют схожий внешний вид и поведение в большинстве операций, но отличаются по внутренней структуре. nil-слайс не инициализирован и не имеет базового массива, в то время как пустой слайс инициализирован, но без элементов.
Обе формы безопасны:
len, cap дают 0, append работает одинаково, цикл range не выполнится. Разница видна при JSON-маршалинге: nil-слайс → null, пустой → [].
🐸 Библиотека Go для собеса❓ Каким должен быть ключ в мапе
Ключ должен быть comparable типом — то есть поддерживать операции
== и !=.
var m map[string]int // работает
var m map[int]string // работает
var m map[[]byte]int // ошибка компиляции: slice не comparable
var m map[map[string]int]int // ошибка: map не comparable
Что можно использовать как ключ:
• bool
• int, int8, int16, int32, int64
• uint, uint8, uint16, uint32, uint64, uintptr
• float32, float64
• complex64, complex128
• string
• pointer (*T)
• channel (chan T)
• interface — если динамический тип comparable
• struct — если все поля comparable
• array [N]T, если T comparable
🐸 Библиотека Go для собеса❓ Что возвращает функция recover()
Функция
recover() в Go возвращает значение, переданное в panic(), если она вызвана во время активной паники в текущей горутине и находится внутри отложенной функции.
Пример использования:
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Паника перехвачена:", r) // Вывод: Паника перехвачена: ошибка!
}
}()
panic("ошибка!")
fmt.Println("Этот код не выполнится") // Пропускается
}
🐸 Библиотека Go для собесаБот-сторож на Golang. Асинхронная верификация без паролей
Конкурентность в Go лучше всего понимается на практических задачах. В этом открытом уроке мы разберём реальный сценарий — систему входа без паролей с использованием горутин и каналов.
📅 На вебинаре 11 февраля создадим Telegram-бота для проверки пользователей, разберём архитектуру асинхронной верификации, научим бота взаимодействовать с веб-приложением через WebSocket и реализуем потокобезопасное хранилище сессий. По ходу занятия обсудим, как избегать гонок данных и проектировать конкурентные сервисы.
Урок будет полезен Go-разработчикам, которые уже пишут код и хотят разобраться в конкурентности на практике, а также тем, кто разрабатывает веб-сервисы с нестандартной логикой авторизации.👉 Записаться: https://clc.to/0ZEDNA Этот вебинар проходит в формате бесплатного открытого урока курса «Golang Developer. Basic» от OTUS. Реклама. ООО «Отус онлайн‑образование», ОГРН 1177746618576
❓ Как выйти из горутины
Горутина завершается автоматически, когда функция, которую она выполняет, завершает свою работу. Но есть несколько паттернов для контролируемого завершения.
• Через done-канал
done := make(chan struct{})
go func() {
for {
select {
case <-done:
return // выходим из горутины
default:
// делаем работу
}
}
}()
// когда нужно остановить
close(done)
• Через context
ctx, cancel := context.WithCancel(context.Background())
go func() {
for {
select {
case <-ctx.Done():
return
default:
// делаем работу
}
}
}()
// когда нужно остановить
cancel()
Нельзя «убить» горутину извне силой и нет аналога thread.kill() из других языков.
🐸 Библиотека Go для собеса❓ Можно ли вызвать вложенную функцию рекурсивно
Да, можно вызвать вложенную функцию рекурсивно, но для этого требуется специальный синтаксис: объявить переменную с типом функции до её определения.
Пример:
package main
import "fmt"
func main() {
var fib func(n int) int // Объявляем переменную с типом функции
fib = func(n int) int { // Теперь можем ссылаться на fib внутри
if n < 2 {
return n
}
return fib(n-1) + fib(n-2) // Рекурсивный вызов
}
fmt.Println(fib(7)) // Вывод: 13
}
Вложенные функции подходят для задач вроде обхода деревьев или чисел Фибоначчи.
🐸 Библиотека Go для собеса❓ Для чего используется wg.Done()
wg.Done() используется для сигнализации завершения задачи в sync.WaitGroup. Это метод уменьшает внутренний счетчик WaitGroup на 1.
Пример:
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // Гарантированный вызов wg.Done()
time.Sleep(time.Duration(id) * time.Second)
fmt.Printf("Worker %d завершен\n", id)
}
🐸 Библиотека Go для собеса❓ Какие есть гарантии того, что defer выполнится
Defer гарантированно выполнится при нормальном выходе из функции — это его основное предназначение. Но важно понимать нюансы:
• При обычном return из функции — defer отработает до возврата управления
• При панике — defer выполнится перед раскруткой стека
• При достижении конца функции — все отложенные вызовы выполнятся в обратном порядке
Когда defer НЕ выполнится:
• os.Exit() — немедленно завершает программу, defer'ы игнорируются
• Фатальные ошибки рантайма
• SIGKILL, принудительное завершение
• Бесконечные циклы — если функция никогда не завершится, defer не выполнится
🐸 Библиотека Go для собеса
¡Ya disponible! Investigación de Telegram 2025 — los principales insights del año 
