fa
Feedback
Библиотека Go для собеса | вопросы с собеседований

Библиотека Go для собеса | вопросы с собеседований

رفتن به کانال در Telegram

Вопросы с собеседований по Go и ответы на них. Покажем, как запустить своего ии-агента: https://clc.to/tvpmD По рекламе: @proglib_adv Для обратной связи: @proglibrary_feeedback_bot Наши каналы: https://t.me/proglibrary/9197

نمایش بیشتر
7 430
مشترکین
-124 ساعت
-137 روز
+730 روز
آرشیو پست ها
Какие основные типы context в Go вы знаете Go предоставляет функции, которые позволяют создавать производные контексты на основе базового
ctx := context.Background()
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
WithCancel — вручную отменяет контекст WithTimeout — отменяет через заданный интервал времени WithDeadline — отменяет к заданному моменту времени WithValue — добавляет пару ключ/значение в контекст (использовать осторожно) Каждая из этих функций создаёт дочерний контекст, и если родитель будет отменён — отменятся и все дочерние. 🐸 Библиотека Go для собеса

Почему ленивую инициализацию важно делать безопасно в многопоточном окружении Для ленивой инициализации (lazy init) в многопоточной среде можно использовать sync.Mutex. Это даёт максимальный контроль, но требует аккуратности. Рабочий пример:
package main

import (
    "fmt"
    "sync"
)

type singleton struct {
    data string
}

var (
    instance *singleton
    mu       sync.Mutex
)

func GetInstance() *singleton {
    if instance == nil {
        mu.Lock()
        defer mu.Unlock()
        if instance == nil {
            instance = &singleton{data: "initialized"}
        }
    }
    return instance
}

func main() {
    s := GetInstance()
    fmt.Println("Singleton data:", s.data)
}
Двойная проверка (Double-Checked Locking): 1️⃣ if instance == nil не требует блокировки и отсекает большинство вызовов 2️⃣ внутри mu.Lock() — необходима, чтобы избежать гонки между горутинами, прошедшими первую проверку одновременно ➡️ Рекомендуется применять только когда нужно встроить логику, контроль или откладку внутрь блокировки. В остальных случаях предпочтительнее sync.Once 🐸 Библиотека Go для собеса

Какой контекст выбрать: context.Background() или context.TODO() Когда вы работаете с Go-контекстами, легко запутаться: оба выглядят одинаково, оба ничего не делают — но используются по-разному. ➖context.Background() — это ваш "нулевой" контекст, от которого принято строить всё остальное. Он используется в main(), в инициализации, в серверах и корневых вызовах. Это базовая точка отсчёта.context.TODO() — маркер, который говорит: "я ещё не знаю, какой контекст здесь будет, но он точно появится". Такой контекст часто используется в заготовках, шаблонах, временном коде, пока архитектура не определена. 🐸 Библиотека Go для собеса

В чём суть и контекст применения Singleton в Go Паттерн Singleton (Одиночка) — это порождающий паттерн проектирования, цель которого — гарантировать, что структура (или тип) имеет только один экземпляр и предоставляет глобальную точку доступа к нему. ➡️Когда использовать: • Централизованное управление ресурсами: пул соединений, логгеры, кэш • Избегание дублирования состояния в памяти • Работа в многопоточной среде и потокобезопасный доступ к общей структуре ➡️ Когда не стоит использовать: • Singleton нарушает SRP (единственная ответственность) • Затрудняет тестирование (невозможно подменить зависимости) • Провоцирует на глобальные сингл-точки сбоя (если объект упал — всё развалилось) • Препятствует масштабируемости при переходе к микросервисной архитектуре ➡️ Важно: в Go нет классов и private конструкторов как в OOP-языках, поэтому реализация синглтона — технически иной подход, обычно через package-level переменные, sync и функции. 🐸 Библиотека Go для собеса

🔥 Последняя неделя перед стартом курса по AI-агентам Старт курса уже 5го числа! Если вы планировали вписаться — сейчас ПОСЛЕ
🔥 Последняя неделя перед стартом курса по AI-агентам Старт курса уже 5го числа! Если вы планировали вписаться — сейчас ПОСЛЕДНИЙ шанс забронировать место На курсе:разложим LLM по косточкам: токенизация, SFT, PEFT, инференс — соберём RAG и научимся оценивать его адекватно — построим настоящую мультиагентную систему — архитектуру, которая умеет расти — разберём CoPilot, сломаем через prompt injection (спасибо Максу) — и наконец, посмотрим, как это работает в MCP и реальных кейсах 📍 Это 5 живых вебинаров + раздатка + домашки + чат с преподавателями И главное — возможность реально разобраться, как проектировать системы на LLM, а не просто «поиграться с API» 👉 Курс здесь

В чём разница между следующими объявлениями массивов в Go
arr1 := [5]int{}
arr2 := [...]int{1, 2, 3}
arr3 := [...]int{}
1️⃣ вариант — arr1 := [5]int{}это явная инициализация массива длиной 5, где все элементы будут равны 0, потому что для типа int это значение по умолчанию. 2️⃣ — arr2 := [...]int{1, 2, 3}использует вывод длины массива: компилятор сам определяет, что длина должна быть 3, так как передано три элемента. Это удобно, когда длина очевидна из контекста. А вот 3️⃣ arr3 := [...]int{} — это ошибка компиляции. Компилятор не сможет вывести длину массива из пустого литерала, и поэтому такой код не скомпилируется. Чтобы создать пустой массив, нужно использовать явно указанную длину, например: arr3 := [0]int{}. 🐸 Библиотека Go для собеса

Чем горутины в Go отличаются от потоков (threads) Чтобы разобраться в том, как Go реализует параллельное выполнение задач, важно понимать различия между горутинами и потоками. Эти различия влияют на производительность, ресурсы и удобство работы с многозадачностью. Вот основные отличия горутин от потоков в Go: ➡️ Уровень абстракции Горутины представляют собой абстракцию, встроенную в язык Go, которая позволяет выполнять функции или методы параллельно. Потоки — это низкоуровневые объекты операционной системы, предназначенные для параллельного выполнения задач. ➡️ Размер стека Горутины начинают с небольшого стека (около 2 КБ), который может изменяться по мере необходимости. Потоки обычно имеют фиксированный размер стека, который значительно больше, часто от 1 МБ и выше. ➡️ Создание и переключение Горутины создаются с помощью ключевого слова go перед вызовом функции и требуют минимальных затрат на создание и переключение контекста. Потоки стоят дороже как по времени создания, так и по затратам на переключение контекста, поскольку для этого необходимо взаимодействовать с операционной системой. ➡️ Планировщик Горутины управляются планировщиком Go, работающим в пользовательском пространстве, который распределяет горутины по доступным потокам ОС (обычно один поток на одно ядро процессора). Потоки управляются планировщиком операционной системы. ➡️ Изоляция Ошибка в одной горутине (например, паника) может повлиять на остальные горутины в программе. 🐸 Библиотека Go для собеса

Какие изменения в В Go 1.24 коснулись срезов В Go 1.24 были улучшены возможности работы с срезами через пакет slices. Вот некоторые новые функции: ➖ Поиск элемента: slices.Contains(s, v) проверяет наличие элемента в срезе. ➖ Сортировка: slices.Sort(s)сортирует срезы для любых типов, поддерживающих интерфейс ordered. ➖ Поиск максимума: slices.Max(s)находит максимальное значение в срезе. ➖ Удаление элементов: slices.Delete(letters, 1, 4)удаляет элементы в указанном диапазоне. ➖ Сравнение срезов: slices.Compare(a, b)сравнивает два среза. ➖ slices.Compact: удаляет дубликаты подряд идущих элементов. ➖ slices.Clip: удаляет неиспользуемую емкость, возвращая срез с длиной и емкостью, равными длине. ➖ slices.Clone: возвращает копию среза. ➖ BinarySearch: выполняет бинарный поиск в отсортированном срезе. 🐸 Библиотека Go для собеса

В чем заключается механизм embedding в Go В Go нет традиционного наследования, как в ООП, но есть механизм embedding (встраивание), который позволяет одному типу включать поля и методы другого типа, обеспечивая схожий эффект. Важно знать следующее: ➡️ Композиция вместо наследования В Go предпочтение отдается композиции, а не наследованию. Один тип может включать другой, расширяя его функциональность. ➡️ Простота использования Встроить один тип в другой очень просто — достаточно определить один тип внутри другого. ➡️ Поведение и интерфейсы Если встроенный тип реализует интерфейс, то и тип, в который он встроен, автоматически реализует этот интерфейс.
type Engine struct {
    Power int
    Type  string
}

type Car struct {
    Engine  
    Brand   string
    Model   string
}

func main() {
    c := Car{
        Engine: Engine{Power: 150, Type: "Petrol"},
        Brand:  "Ford",
        Model:  "Fiesta",
    }
    fmt.Println(c.Power) // Выведет: 150
}
Встраивание интерфейсов:
type Writer interface {
    Write([]byte) (int, error)
}

type Logger struct {
    Writer
}
Теперь Logger автоматически реализует интерфейс Writer, если его встроенное поле Writer реализует этот интерфейс. Важные моменты: ➖Конфликты имен Если у встроенного и внешнего типов одинаковые поля или методы, приоритет отдается внешнему типу. ➖Неявное поведение Методы встроенного типа становятся частью внешнего, что может быть неочевидно при чтении кода. ➖Интерфейсы В Go можно встраивать интерфейсы, создавая сложные интерфейсы на основе существующих. 🐸 Библиотека Go для собеса

Какую роль выполняют функции make и new в Go, и чем они отличаются В Go, несмотря на наличие сборщика мусора, разработчикам всё равно необходимо выделять память для новых переменных. Для этого язык предоставляет две встроенные функции: new и make. ➡️ Функция new(T) выделяет память для переменной типа T, инициализирует её нулевым значением этого типа и возвращает указатель на эту переменную.
p := new(int)
В этом примере p будет указателем на новый int, инициализированный значением 0. ➡️ Функция make используется для инициализации срезов, карт и каналов, которые являются встроенными типами данных в Go и требуют дополнительной инициализации перед использованием. • Срезы: при создании среза с помощью make, задаются начальная длина и ёмкость, что позволяет Go заранее выделить нужное количество памяти
s := make([]int, 5, 10) 
Map: при создании map с помощью make осуществляется его инициализация перед добавлением элементов
m := make(map[string]int)
• Каналы: make также используется для создания каналов.
ch := make(chan int)
Основные различия между make и new ➡️ new возвращает указатель на тип, инициализированный нулевым значением этого типа. ➡️ make возвращает инициализированный экземпляр встроенного типа (среза, map или канала), готовый к использованию. ➡️ Обычно, для выделения памяти под переменные используют литералы типа или make. new используется реже и в основном в более специфичных случаях. 🐸 Библиотека Go для собеса

🤯 Мы больше года строим мультиагентные системы Грабли, находки, паттерны, эксперименты — всё это накопилось и в какой-то мом
🤯 Мы больше года строим мультиагентные системы Грабли, находки, паттерны, эксперименты — всё это накопилось и в какой-то момент стало жалко держать только у себя. Никита — рассказывает (и показывает) базу: токенизация, LLM, SFT, PEFT, локальный инференс + RAG и как оценивать его качество. Диана — как строят мультиагентные системы, какие есть паттерны проектирования и библиотеки. Макс — про инференс в проде + разберет CoPilot, соберет с вами из кусочков свой копайлот, а затем его сломает через prompt injection. // Макс фанат autogen (а если нет — он вас разубедит в своем классном канале) Финальным аккордом Дима углубится в MCP и соберет несколько кейсов повзрослее. Курс тут: https://clc.to/47pgYA Промокод: datarascals действует до 23:59 29 июня

Какую роль играет ключевое слово defer в Go defer в Go — это ключевое слово, которое откладывает выполнение функции или метода до завершения текущей функции. Когда встречается defer, Go добавляет вызов функции в стек отложенных операций и продолжает выполнение текущего кода. Применения defer: Закрытие ресурсов. Одно из самых частых применений defer — это гарантированное закрытие ресурсов (например, файлов, сетевых соединений или подключений к базе данных) после их использования.
file, err := os.Open("file.txt")
if err != nil {
    // обработка ошибки
}
defer file.Close()
Множественные отложенные вызовы. Вы можете использовать несколько defer в одной функции. Они будут выполняться в обратном порядке (LIFO — Last In, First Out).
func example() {
    defer fmt.Println("1")
    defer fmt.Println("2")
    fmt.Println("Function body")
}
Передача аргументов. Аргументы функции, вызванной через defer, вычисляются в момент вызова defer, а не при фактическом исполнении отложенной функции.
func example(a int) {
    defer fmt.Println(a)
    a *= 2
    return
}
example(5) // Выведет: 5
Использование с паникой. defer часто используется с recover() для обработки или логирования паники, если она возникает в функции.
func mightPanic() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
        }
    }()
    // код, который может вызвать панику
}
Зависимость от контекста. Отложенные функции могут иметь доступ к локальным переменным и изменять их значения, что делает defer удобным инструментом для выполнения завершающих операций перед выходом из функции. Затраты на производительность. Хотя defer очень удобно использовать, в циклах с интенсивными вычислениями его применение может незначительно повлиять на производительность из-за накладных расходов. 🐸 Библиотека Go для собеса

🏎 Вы уже сталкивались с «глухими» зависаниями и гонками данных в Go? Настало время взять каналы под контроль! 💻 На открытом
🏎 Вы уже сталкивались с «глухими» зависаниями и гонками данных в Go? Настало время взять каналы под контроль! 💻 На открытом уроке «Подводные камни каналов в Go — и как их обходить» 1 июля в 20:00 МСК мы не просто обсудим, что такое каналы: — покажем реальные кейсы; — узнаем, где без них не обойтись; — разберём частые ошибки, которые тормозят ваши сервисы. 🚀 Представьте: ваш сервис обрабатывает запросы параллельно, без блокировок и утечек. Вы глубоко понимаете, как каналы помогают строить конкурентный код и уверенно внедряете это на любом проекте. 👉 Регистрируйтесь сейчас и получите персональную скидку на курс «Golang Developer. Professional»: https://clc.to/0fYV1g Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru

Как реализованы интерфейсы и как их используют в коде Интерфейсы — это инструмент для задания поведения объектов. Они позволяют работать с разными типами, обеспечивая гибкость и полиморфизм. ➡️ Определение интерфейса Интерфейс в Go — это набор методов без реализации.
type Writer interface {
    Write([]byte) (int, error)
}
➡️ Реализация интерфейса Тип реализует интерфейс, если он предоставляет все методы интерфейса. В Go нет необходимости явно указывать реализацию — это происходит неявно. ➡️ Пустой интерфейс Интерфейс без методов называется пустым (interface{}). Любой тип удовлетворяет этому интерфейсу, что позволяет создавать универсальные функции и структуры. ➡️ Встраивание интерфейсов Можно комбинировать интерфейсы, например:
type ReadWriter interface {
    Reader
    Writer
}
➡️ Методы с указателями Если метод имеет указатель в качестве получателя, он может быть частью интерфейса только если используется указатель на тип. ➡️ Использование интерфейсов Интерфейсы задают требования к поведению типов, что позволяет функциям работать с различными типами через интерфейс. ➡️ Type assertion и type switch Для приведения типов и проверки типа значения интерфейса используют операции type assertion и type switch. ➡️ Значение интерфейса Интерфейс состоит из двух частей: указателя на значение и указателя на таблицу методов этого значения. ➡️ Nil интерфейса Интерфейс может содержать значение nil. При вызове метода на таком интерфейсе возникнет ошибка. 🐸 Библиотека Go для собеса

Как в Go организовано управление памятью В Go управление памятью полностью автоматизировано благодаря сборщику мусора. Это значит, что вам не нужно вручную выделять и освобождать память, как в C или C++. Однако важно быть внимательным, особенно при работе с большими структурами данных, чтобы избежать утечек памяти. Основные моменты: • Сборщик мусора: Алгоритм маркировки и освобождения автоматически помечает активные объекты, а затем очищает неактивные, освобождая память. • Указатели: В Go можно работать с указателями, но память выделяется при создании объектов и автоматически освобождается. Прямого управления выделением и освобождением через указатели нет. • Циклические ссылки: Хотя управление памятью автоматическое, неправильное использование, например, циклические ссылки, может привести к утечкам. Важно следить за ресурсами. • Срезы: Это динамические массивы, которые автоматически управляют памятью при изменении их размера. • Стек и куча: Память делится на стек (для локальных переменных) и кучу (для долгоживущих объектов). Управление памятью в куче осуществляется сборщиком мусора. • Escape analysis: Этот механизм анализирует, должен ли объект находиться на стеке или в куче, что позволяет Go более эффективно управлять памятью. 🐸 Библиотека Go для собеса

Тест для Golang-разработчиков, проверьте свои знания, готовы ли вы к обучению на курсе. 💻 Ответьте на 20 вопросов за 30 мину
Тест для Golang-разработчиков, проверьте свои знания, готовы ли вы к обучению на курсе. 💻 Ответьте на 20 вопросов за 30 минут и проверьте, готовы ли вы к обучению на онлайн-курсе «Golang Developer. Professional» от OTUS. Сейчас Go становится все востребованнее, благодаря своей производительности, масштабируемости и экосистеме. После 5 месяцев обучения вы сможете: — Писать production-ready код, многопоточные и конкурентные программы. — Понимать синтаксис и внутреннее устройство языка Go. — Разворачивать микросервисы с помощью Docker. — Проектировать и реализовывать микросервисную архитектуру на Go. Также вас ждет прокачка навыков на реальных коммерческих кейсах и под руководством экспертов в этой области. Возможна рассрочка. 👉 ПРОЙТИ ТЕСТ Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576, www.otus.ru

Какую ошибку можно ожидать при преобразовании интерфейса к несовместимому типу Если попытаться преобразовать интерфейс к несовместимому типу без проверки, то это приведет к панике. Когда тип данных в интерфейсе не совпадает с ожидаемым типом, программа вызовет ошибку типа:
panic: interface conversion: <interface type> is <actual type>, not <expected type>
Программа завершит выполнение с ошибкой, и никаких дальнейших операций не будет выполнено. Чтобы избежать этого, рекомендуется всегда использовать проверку типа с ok, чтобы обработать несовпадения типов безопасным способом. 🐸 Библиотека Go для собеса

Что такое тип map в Go В языке Go map представляет собой встроенный тип данных, реализующий структуру данных "хеш-таблица". Он позволяет хранить и эффективно извлекать пары ключ-значение, где каждый ключ уникален и связан с определённым значением. • Создание map 1. Использование функции make:
m := make(map[string]int)
m["a"] = 1
m["b"] = 2
2. Использование литерала карты:
m := map[string]int{
    "a": 1,
    "b": 2,
}
• Работа с элементами карты Добавление или обновление значения: m["c"] = 3 Получение значения: value := m["a"] Проверка наличия ключа: value, exists := m["b"] Удаление элемента: delete(m, "a") • Потокобезопасность Карты в Go не потокобезопасны. Для безопасного доступа из нескольких горутин используйте мьютексы (sync.Mutex) или sync.Map
var mu sync.Mutex
mu.Lock()
m["a"] = 1
mu.Unlock()
🐸 Библиотека Go для собеса

Какую роль играют пакеты в Go и почему они важны В Go пакет — это основная единица организации кода. Он представляет собой набор файлов в одной директории с общим именем, указанным в строке package в начале каждого файла. Пакеты позволяют структурировать программу, разделяя её на логические компоненты. Особенности в пакетах: • Объявление пакета — каждый файл начинается с директивы package <имя> • Импорт пакетов — используем директиву import для доступа к функциям и типам из других пакетов • Структура каталогов — пакет соответствует одной директории. Все файлы пакета должны быть в одной папке • Экспортируемые элементы пишутся с заглавной буквы экспортируются и доступны в других пакетах, с маленькой — локальны Пример:
var Pi = 3.14  // экспортируемая
var radius = 5 // неэкспортируемая
• Программы должны содержать пакет main и функцию main() Пакеты стандартной библиотеки fmt и math/rand • Сторонние пакеты — устанавливаются через go get и импортируются по полному пути • Модули в Go 1.11+ модули управляют зависимостями пакетов через файл go.mod • Тесты размещаются в файлах с суффиксом _test.go в том же пакете 🐸 Библиотека Go для собеса

В чем разница между массивами и срезами Массив — это структура данных с фиксированным размером, определенным при создании. Размер массива нельзя изменить. Как его создать: [capacity]data_type{element_values} Срез — это динамическая версия массива с переменной длиной, которая дает большую гибкость. Как его создать: s := []data_type{element1, element2, ...} Особенности срезов: • Для получения длины используйте len(), для емкостиcap(). • Для добавления элементов append(). • Разделение массивов через [first_index:second_index] или s[:]. • Удаление элементов append(s[:i], s[i+1:]...). • Многомерные срезы содержат в качестве элементов другие срезы, в скобках отмечаются родительские срезы. • Копирование с помощью copy(). 🐸 Библиотека Go для собеса