Библиотека Go для собеса | вопросы с собеседований
6.84K subscribers
219 photos
6 videos
1 file
397 links
Вопросы с собеседований по Go и ответы на них.

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

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

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

Наши каналы: https://t.me/proglibrary/9197
Download Telegram
Как правильно использовать context в юнит-тестах

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

Чтобы избежать зависаний и утечек в юнит-тестах, всегда создавайте context с таймаутом:
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()


Это гарантирует:
Тест не будет висеть бесконечно
Ресурсы будут высвобождены
Горутины получат сигнал на завершение


Если ваш тест зависает — это может говорить о том, что где-то в коде игнорируется ctx.Done()

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
Какой способ инициализации Singleton в Go выбрать: sync.Mutex, sync.Once или init()

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
5👍3
Зачем использовать select, если можно просто читать из канала

В Go оператор select — это конкурентный аналог switch, предназначенный исключительно для работы с каналами.

С его помощью можно:
• Ждать сразу несколько операций с каналами (чтение/запись)
• Управлять конкурентными потоками без блокировок
• Не блокироваться, если добавить
default ветку

select {
case msg := <-ch1:
fmt.Println("Received from ch1:", msg)
case ch2 <- 42:
fmt.Println("Sent 42 to ch2")
default:
fmt.Println("Nothing ready")
}


Если ch1 или ch2 готовы — будет выполнен соответствующий case.
Если ни один канал не активен — выполняется default, и select не блокирует выполнение.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍52🤔1
Можно ли сделать Singleton тестируемым без нарушения его природы

Singleton может мешать тестированию, так как его состояние живёт весь runtime. Но есть обходные пути:

1️⃣ Вынос в интерфейс
type Storage interface {
Get(key string) string
}


2️⃣ Инъекция зависимости через параметр
func ProcessData(s Storage) { ... }


3️⃣ Сброс состояния Singleton (в тестах)
// В тестовых сборках
func resetSingleton() {
instance = nil
once = sync.Once{}
}


Warning: сброс Singleton — антипаттерн, но допустим в юнит-тестах

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

Одно из важных свойств select в Go — рандомность выбора, если сразу несколько каналов готовы к операции.
select {
case msg := <-ch1:
fmt.Println("ch1:", msg)
case msg := <-ch2:
fmt.Println("ch2:", msg)
}


Если и ch1, и ch2 доступны — Go случайным образом выберет один case.
Это исключает жёсткий приоритет каналов и распределяет нагрузку справедливо (не детерминированно).

Это нужно для предотвращения "голодания" менее приоритетных каналов, также это позволяет реализовать честные очереди и worker pool без ручного балансировщика

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
😁10👍3🌚2
Что такое отношение happens-before в модели памяти Go

Отношение happens-before гарантирует упорядоченность и видимость операций между разными горутинами. Если операция A happens-before операции B, то все записанные до A значения памяти гарантированно будут видны при выполнении B.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
😁3🎉1
Как вам вопросы прошедшей недели

Оцените их по шкале 🔥,❤️,👍,😢, 🥱,
где 🔥 — это супер, а 🥱 — это скучно.

Также приветствуется фидбек в комментариях.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍25🔥11😢75👾1
Как Redis реализует сохранение данных на диск

Redis поддерживает два механизма сохранения данных: Redis Database Dump, который сохраняет данные в момент времени, и Append-Only File, который записывает каждую операцию записи.

Эти два метода могут использоваться вместе для достижения компромисса между производительностью и надежностью.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥3
Какие правила happens-before действуют для атомарных операций из пакета sync/atomic

Атомарная операция Store happens-before последующей атомарной операции Load той же переменной (при отсутствии иных атомарных модификаций между ними).

Инициализация всех глобальных переменных happens-before старту функции main(), что гарантирует корректные первоначальные значения при запуске программы.

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

Паттерн «Функциональные опции» предоставляет удобный и гибкий способ конфигурации структур в Go без раскрытия их внутренних полей. Это решение помогает создать расширяемые и легко поддерживаемые объекты, что особенно важно, если в будущем предполагаются изменения или добавление новых параметров.

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

Пример без функциональных опций:
type Server struct {
host string
port int
protocol string
}

func NewServer(host string, port int) *Server {
return &Server{host: host, port: port, protocol: "http"}
}

Этот код создает сервер с параметрами host и port, но с изменениями в требованиях необходимо менять сигнатуру функции, что неудобно.

Реализация функциональных опций
1️⃣ Определим тип для опций:
type ServerOption func(*Server)


2️⃣ Функция для изменения порта:
func WithPort(port int) ServerOption {
return func(s *Server) {
s.port = port
}
}


3️⃣ Модифицируем функцию NewServer:
func NewServer(host string, opts ...ServerOption) *Server {
server := &Server{host: host, port: 443, protocol: "https"}
for _, opt := range opts {
opt(server)
}
return server
}


Теперь можно гибко настроить параметры:
server1 := NewServer("localhost")               // с портом по умолчанию
server2 := NewServer("localhost", WithPort(8080)) // с портом 8080

Этот подход позволяет добавлять параметры без изменения кода и нарушений совместимости.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14🤔7👏1
Какова цель и принцип работы планировщика Go на базовом уровне

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

➡️ Модель M:N — в Go используется модель M:N, при которой M горутин могут быть назначены на N потоках ОС. Это позволяет создавать тысячи горутин с минимальными накладными расходами, связанными с управлением реальными потоками операционной системы.

➡️ G, M и P:
• G (goroutine) — горутина, выполняющаяся в рамках Go-программы
• M (machine thread) — поток ОС, который выполняет горутины
• P (processor) — контекст выполнения, который управляет очередью горутин и другими необходимыми данными. Обычно количество P соответствует числу ядер процессора.

➡️ Работа планировщика — во время выполнения горутины, она захватывает P и привязывается к M. Если горутина блокируется (например, из-за ожидания ввода/вывода), P освобождается и может быть назначен другой горутине, которая готова к выполнению.

➡️ Адаптация под нагрузку — планировщик динамически увеличивает или уменьшает количество потоков ОС (M) в зависимости от текущей нагрузки и блокировок.

➡️ Прерывание горутин — горутины могут быть прерваны планировщиком, чтобы освободить поток ОС для других горутин. Это гарантирует, что одна горутина не будет занимать процессорное время слишком долго, давая возможность другим горутинам быть выполненными.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥9🤔2
Что представляют собой дженерики в языке Go и в чем их преимущества

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

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

➡️ Преимущества:

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

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

• Увеличение производительности, избавление от преобразования типов позволяет улучшить скорость выполнения.

➡️ До дженериков

До введения дженериков, разработчики использовали интерфейсы и преобразование типов для обеспечения гибкости. Однако это часто приводило к потере производительности и снижению безопасности типов. Например, для создания функции поиска минимального значения в срезе, нужно было писать отдельные реализации для каждого типа:
func MinInts(arr []int) int { /*...*/ }
func MinFloats(arr []float64) float64 { /*...*/ }


➡️ С дженериками

Теперь с использованием дженериков можно создать одну универсальную функцию, которая работает с различными типами данных. Пример с функцией для нахождения минимального значения:
func Min[T comparable](arr []T) T { /*...*/ }

Здесь T — это параметр типа, а comparable указывает, что тип должен поддерживать операцию сравнения.

➡️ Пример с обобщенной структурой данных

Допустим, вам нужно создать структуру данных типа очередь (Queue).

Без дженериков:
type Queue struct {
data []interface{}
}

Использование interface{} требует преобразования типов, что снижает производительность.

С дженериками:
type Queue[T any] struct {
data []T
}

Здесь T может быть любым типом, и нет необходимости в преобразованиях.

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

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16
Релиз easyoffer 2.0 — сайта по подготовке к IT собеседованиям!

Разработку проекта поддержали 1600 айтишников, а суммарно на запуск было собрано 5 млн. руб. через краудфандинг.

«Всё в одном» для тех, кто ищет работу в IT:
🟢Аналитика собесов на основе 4500+ реальных интервью
🟢Вопросы и задачи из собеседований с вероятностью встречи
🟢Примеры видео-ответов от Senior/Middle разработчиков
🟢Тренажеры для подготовки
🟢Автоотклики на вакансии и другое.

В честь релиза первые 500 пользователей получат скидку 60% на годовой PRO-доступ

Что нужно сделать:

🔔 Подпишитесь на Telegram-канал проекта
https://t.me/+UYkjii31QQozZjgy Там появится анонс релиза раньше, чем где-либо ещё. Вы успеете попасть в число первых 500 и получить максимальную выгоду.

Реклама. ИП Кивайко Алексей Викторович, ИНН 532121460552. Erid 2VtzqvmGkoZ
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱1
В чем заключается принцип работы Escape analysis в Go

Escape analysis — это техника компилятора Go, предназначенная для определения оптимального местоположения переменных: в стеке или в куче. Этот процесс критичен для повышения производительности программы и эффективного использования памяти.

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

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

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

➡️ Переменная остается в рамках функции (стек):
func sum(a, b int) int {
result := a + b
return result
}

В данном случае переменная result остается в области видимости функции, поэтому она будет размещена на стеке.

➡️ Переменная выходит за пределы функции (куча):
func newInt() *int {
result := 42
return &result
}

Здесь переменная result выходит за пределы функции, потому что возвращается указатель на неё. Поэтому она будет размещена в куче.

➡️ Переменная сохраняется в глобальной переменной (куча):
var globalVar *int

func setGlobalVar() {
x := 100
globalVar = &x
}

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

Escape analysis помогает управлять памятью, решая, где лучше хранить переменные — в стеке или в куче.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍9👏2🤔1
🔥 Вы ещё можете застать старый добрый Proglib — с вечным доступом к курсам.

С 1 августа всё меняется: навсегда — останутся только те, кто успел купить сейчас.

-40% на все курсы. Включая обновлённый Python (кроме курса по AI-агентам)

Это не просто распродажа. Это — последняя точка входа в Proglib Academy по старым правилам.

📚 Выбрать и забрать свой курс навсегда → https://clc.to/TBtqYA
Как вам вопросы прошедшей недели

Оцените их по шкале 🔥,❤️,👍,😢, 🥱,
где 🔥 — это супер, а 🥱 — это скучно.

Также приветствуется фидбек в комментах.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14👍21
Каким образом в Go реализуется паттерн «Прототип»

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

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

➡️ Иерархия файловой системы как пример
Представим, что мы строим структуру файловой системы, которая состоит из директорий, содержащих как файлы, так и другие директории. Такая система может быть многоуровневой, где каждая директория может включать в себя другие элементы (файлы и подкаталоги).

Чтобы реализовать этот паттерн, мы будем использовать интерфейс Element, который будет представлять как файлы, так и директории. Каждая структура должна реализовывать два метода:
1. display — для отображения информации о текущем элементе.
2. clone — для создания копии элемента.


➡️ Пример 1: Файл (Document)
Файл в нашей файловой системе может быть представлен типом Document. Он имеет имя (например, Doc1), и когда мы хотим его клонировать, мы добавляем к имени суффикс "_copy" (например, Doc1_copy).

➡️ Пример 2: Директория (Directory)
Директория представляет собой коллекцию элементов, таких как файлы или другие директории. Она может содержать несколько вложенных объектов и поддерживает тот же интерфейс Element. Когда мы клонируем директорию, мы создаем копию самой директории, а также клонируем все ее элементы.

➡️ Принцип работы клонирования
Клонирование объекта вызывает метод клонирования для объекта, создает новый объект того же типа, копируя все значения оригинала. В случае с директориями это включает клонирование всех вложенных файлов и подкаталогов.

Рекурсивное клонирование — объект является директорией, клонирует не только саму директорию, но и все ее элементы, повторяя этот процесс для каждой вложенной директории и ее элементов.

Суффикс "_copy" клон получает суффикс "_copy" в своем имени, что позволяет легко отличить оригинальный объект от его копии. (Doc1 → Doc1_copy)


🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍63🤩2
This media is not supported in your browser
VIEW IN TELEGRAM
Для Серёжи рабочий день — не шум принтера и звук кофемашины. А друзья в команде, посиделки после работы и проекты по душе 😀

Сейчас Серёжа делает бэкенд всей VK более отказоустойчивым, и в его команде не хватает Go-разработчика, чтобы затащить эту цель. Возможно, это именно вы: откликайтесь, если откликается!
🥱21🤔1
Что подразумевается под inlining в Go и какова его цель

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

Пример:
func add(a, b int) int {
return a + b
}

func main() {
result := add(3, 4)
fmt.Println(result)
}


После inlining код может выглядеть так:
func main() {
result := 3 + 4
fmt.Println(result)
}


Основные моменты:
1️⃣Автоматическое решение компилятора, Go сам решает, следует ли выполнить inlining для функции, основываясь на её размере и сложности.
2️⃣Основное преимущество — это ускорение работы программы за счет снижения накладных расходов при многократных вызовах функции.
3️⃣Ограничения: не все функции можно встроить. Большие функции или те, что вызывают другие функции, скорее всего, не будут встроены. Чрезмерное использование inlining может увеличить размер исполняемого файла.
4️⃣Как увидеть решения компилятора — с помощью флага -gcflags="-m" можно увидеть, какие оптимизации и встраивания принял компилятор.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
6
Как в Go передаются параметры в функцию

➡️В Go параметры всегда передаются по значению. Это означает, что при передаче аргумента в функцию, функция получает копию исходного значения.

Чтобы работать с оригинальной переменной, а не с её копией, нужно передавать адрес этой переменной. Однако сам указатель будет передан по значению.

Передача базовых типов данных (int, string, bool и т.д.)
func modifyValue(x int) {
x = x * 2
}

func main() {
num := 10
modifyValue(num)
fmt.Println(num) // 10
}

В примере значение переменной num не изменяется, потому что в функцию передается копия этого значения.

Передача срезов и мап

Срезы и мапы передаются по ссылке, то есть функция получает ссылку на оригинальный объект. Изменения внутри функции отразятся на оригинале.
func modifySlice(s []int) {
s[0] = 100
}

func main() {
numbers := []int{1, 2, 3}
modifySlice(numbers)
fmt.Println(numbers) // [100 2 3]
}


Передача указателей

Указатели позволяют передавать ссылку на память, где хранится значение. Изменения через указатель будут видны за пределами функции.
func modifyWithPointer(x *int) {
*x = *x * 2
}

func main() {
num := 10
modifyWithPointer(&num)
fmt.Println(num) // 20
}


➡️ В Go параметры передаются по значению, что означает создание копий для базовых типов данных. Для изменения оригинальных данных, таких как срезы, мапы или через указатели, необходимо использовать ссылки.

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