Golang | Вопросы собесов
4.35K subscribers
26 photos
1 video
713 links
Download Telegram
🤔 Как пустой интерфейс связан с обычным?

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

Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что может быть пустым интерфейсом?

Пустой интерфейс interface{} является универсальным контейнером, который может содержать значение любого типа. Это связано с тем, что в Go любой тип автоматически реализует пустой интерфейс, поскольку в нем нет методов, которые нужно реализовать.

🟠Примитивные типы
числа, строки, булевы значения и т.д.
🟠Композитные типы
массивы, срезы, карты, структуры.
🟠Функции
функции различных типов.
🟠Другие интерфейсы
значения, которые реализуют другие интерфейсы.

🚩Примеры

Примитивные типы
package main

import "fmt"

func main() {
var i interface{}
i = 42
fmt.Println(i) // Output: 42

i = "hello"
fmt.Println(i) // Output: hello

i = true
fmt.Println(i) // Output: true
}


Композитные типы
package main

import "fmt"

func main() {
var i interface{}

i = []int{1, 2, 3}
fmt.Println(i) // Output: [1 2 3]

i = map[string]int{"one": 1, "two": 2}
fmt.Println(i) // Output: map[one:1 two:2]

type Person struct {
Name string
Age int
}

i = Person{Name: "Alice", Age: 30}
fmt.Println(i) // Output: {Alice 30}
}


Функции
package main

import "fmt"

func main() {
var i interface{}

i = func() {
fmt.Println("Hello from function")
}

if f, ok := i.(func()); ok {
f() // Output: Hello from function
}
}


Другие интерфейсы
package main

import "fmt"

type Stringer interface {
String() string
}

type Person struct {
Name string
Age int
}

func (p Person) String() string {
return fmt.Sprintf("%s (%d years old)", p.Name, p.Age)
}

func main() {
var i interface{}

i = Person{Name: "Alice", Age: 30}

if str, ok := i.(Stringer); ok {
fmt.Println(str.String()) // Output: Alice (30 years old)
}
}


🚩Проверка и приведение типа

Утверждение типа
package main

import "fmt"

func main() {
var i interface{} = 42

if v, ok := i.(int); ok {
fmt.Println("Integer:", v) // Output: Integer: 42
} else {
fmt.Println("Not an integer")
}
}


Использование switch для проверки типа
package main

import "fmt"

func printType(i interface{}) {
switch v := i.(type) {
case string:
fmt.Println("String:", v)
case int:
fmt.Println("Integer:", v)
case bool:
fmt.Println("Boolean:", v)
default:
fmt.Printf("Unknown type: %T\n", v)
}
}

func main() {
printType("hello") // Output: String: hello
printType(42) // Output: Integer: 42
printType(true) // Output: Boolean: true
printType(3.14) // Output: Unknown type: float64
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что такое Grafana?

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


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что такое замыкание?

Замыкание (closure) — это функция, которая запоминает и использует переменные из своей внешней области видимости, даже если эта область видимости уже закончила своё выполнение.

🚩Как это работает?

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

import "fmt"

func counter() func() int {
count := 0
return func() int {
count++
return count
}
}

func main() {
inc := counter() // создаём замыкание
fmt.Println(inc()) // 1
fmt.Println(inc()) // 2
fmt.Println(inc()) // 3

newInc := counter() // новое замыкание с новой переменной count
fmt.Println(newInc()) // 1
}


🚩Где полезно использовать?

Счётчики (как в примере выше). Фабричные функции, создающие специфичные обработчики. Ограничение доступа к данным (инкапсуляция).

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Зачем нужен Prometheus?

Это система мониторинга и алертинга, разработанная для сбора метрик в реальном времени. Он хранит данные в timeseries формате и предоставляет мощный язык запросов (PromQL) для анализа.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 По какому алгоритму растет slice?

В Go срезы (slice) динамически изменяемы, и при добавлении новых элементов их вместимость (capacity) увеличивается по определённому алгоритму.

🚩Как растёт `slice` в Go?

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

🚩Алгоритм роста:

Если cap(slice) < 1024, то новая ёмкость (capacity) удваивается.
Если cap(slice) >= 1024, то увеличение идёт примерно на 25% от текущего размера.

package main

import "fmt"

func main() {
var s []int
prevCap := cap(s)

for i := 0; i < 20; i++ {
s = append(s, i)
if cap(s) != prevCap {
fmt.Printf("Len: %d, New Cap: %d (growth: %.2fx)\n", len(s), cap(s), float64(cap(s))/float64(prevCap))
prevCap = cap(s)
}
}
}


Выходные данные (может отличаться в зависимости от реализации Go)
Len: 1, New Cap: 1 (growth: Inf)
Len: 2, New Cap: 2 (growth: 2.00x)
Len: 3, New Cap: 4 (growth: 2.00x)
Len: 5, New Cap: 8 (growth: 2.00x)
Len: 9, New Cap: 16 (growth: 2.00x)
Len: 17, New Cap: 32 (growth: 2.00x)


🚩Почему так?

🟠Экономия памяти
если бы рост был на 1 элемент, то это вызывало бы частые копирования.
🟠Скорость работы
экспоненциальный рост уменьшает количество выделений памяти.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Зачем нужна Grafana?

Grafana нужна для гибкой визуализации данных из Prometheus и других хранилищ. Она позволяет:
- Создавать интерактивные панели мониторинга.
- Настраивать уведомления (alerts).
- Подключаться к разным источникам данных.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Знакомы ли с концепцией 12FA для проектирования SaaS приложений?

Да, знаком. 12FA (12-Factor App) — это набор принципов, созданных разработчиками Heroku для построения масштабируемых, надежных и удобных в развертывании SaaS-приложений. Эти принципы особенно полезны при разработке облачных сервисов (Cloud-Native).

🚩Основные 12 факторов

🟠Кодовая база (Codebase)
У приложения должна быть единая кодовая база (один репозиторий), независимо от количества развертываний (production, staging, dev).

🟠Зависимости (Dependencies)
Все зависимости должны явно указываться в go.mod / go.sum (для Go). Никаких глобальных зависимостей в системе.

🟠Конфигурация (Config)
Конфигурация должна храниться в переменных окружения, а не в коде.
export DATABASE_URL="postgres://user:pass@host:5432/db"


🟠Бэкенд-сервисы (Backing Services)
Внешние сервисы (БД, кэш, API) должны быть заменяемыми и подключаться через URL (без хардкода).

🟠Постоянная сборка, запуск и запуск (Build, Release, Run)
Сборка, релиз и запуск должны быть разделены. Например, Docker-контейнеры для каждой стадии.

🟠Процессы (Processes)
Приложение должно быть бесстатичным (не хранить файлы локально, использовать БД, S3 и т. д.).

🟠Привязка портов (Port Binding)
Приложение должно быть самодостаточным и слушать порт (например, через http.ListenAndServe).

🟠Параллелизм (Concurrency)
Масштабируемость должна обеспечиваться горизонтальным масштабированием (разделением на процессы).

🟠Отказоустойчивость (Disposability)
Приложение должно быстро запускаться и корректно завершаться (например, ловить SIGTERM).

🟠Совместимость Dev/Prod (Dev/Prod Parity)
Среды разработки и продакшена должны быть максимально похожи.

🟠Логирование (Logs)
Логи должны писаться в стандартный вывод и обрабатываться внешними системами (ELK, Loki, Grafana).

🟠Админ-процессы (Admin Processes)
Скрипты администрирования (миграции, отладка) должны выполняться как отдельные процессы.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Go — императивный или декларативный?

Это императивный язык программирования. Код пишется в виде последовательных команд, а не деклараций (как в SQL или Terraform). Однако в некоторых случаях (например, в Kubernetes) Go может использоваться в декларативном стиле.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 В чем разница между буферизированными и небуферизированными каналами?

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

🚩Небуферизированные каналы

Не имеют внутренней емкости, т.е. они не могут хранить значения. Эти каналы требуют, чтобы отправитель и получатель были готовы обмениваться данными одновременно. Если одна сторона не готова, другая будет заблокирована:
Отправка данных в него блокирует отправителя до тех пор, пока получатель не прочитает данные из канала.
Получение данных из него блокирует получателя до тех пор, пока другая горутина не отправит данные в канал.
ch := make(chan int) // Создание небуферизированного канала
go func() {
val := <-ch // Блокируется, ожидая данные
fmt.Println("Received:", val)
}()
ch <- 3 // Блокируется, пока данные не будут получены


🚩Буферизированные каналы

Имеют внутреннюю емкость, что позволяет хранить одно или несколько элементов без непосредственного получателя данных. Отправка или получение данных работает следующим образом:
Отправка блокируется, только если буфер заполнен. До этого момента данные могут быть отправлены без блокировки, даже если получатель не готов их принять.
Получение из буферизированного канала блокируется, только если канал пуст. Если в канале есть данные, получение происходит без блокировки.
ch := make(chan int, 2) // Создание буферизированного канала с емкостью 2
ch <- 1 // Отправка данных без блокировки
ch <- 2 // Отправка данных без блокировки
go func() {
val := <-ch // Получение данных без блокировки
fmt.Println("Received:", val)
}()


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Главный недостаток стандартного логгера?

Стандартный логгер (log из stdlib):
- Не поддерживает уровни логирования (INFO, ERROR).
- Не форматирует логи по умолчанию.
- Не поддерживает ротацию логов.
Для продакшена обычно используют logrus или zap.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что такое EXPLAIN?

EXPLAIN — это команда в SQL, которая показывает, как база данных выполняет SQL-запрос. Она помогает оптимизировать запросы, анализируя их план выполнения.

🚩Как работает?

Когда вы пишете EXPLAIN перед SQL-запросом, база данных не выполняет его, а показывает пошаговый процесс выполнения. Это помогает понять:
Какие индексы используются
Какие таблицы сканируются
Какие соединения (JOIN) выполняются
Оценить производительность запроса
EXPLAIN SELECT * FROM users WHERE age > 30;


Выход (пример)
Seq Scan on users  (cost=0.00..25.00 rows=10 width=100)


🚩`EXPLAIN ANALYZE` — глубокий анализ

Добавляет реальное время выполнения запроса
EXPLAIN ANALYZE SELECT * FROM users WHERE age > 30;


🚩Оптимизация запросов с `EXPLAIN`

Использование индексов
CREATE INDEX idx_users_age ON users(age);


Правильный порядок JOIN
EXPLAIN SELECT * FROM orders JOIN users ON orders.user_id = users.id;


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Есть ли для Go хороший ORM?

Да, популярные ORM для Go:
- GORM – самый распространенный, удобный, но может быть медленным.
- Ent – более производительный, но требует генерации кода.
- SQLX – не ORM, но расширяет database/sql и упрощает работу с SQL.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Как преобразовать строку в int и наоборот?

В Go есть несколько способов преобразования строки в число и числа в строку.

🟠Преобразование строки в `int`
Используется пакет strconv и функция strconv.Atoi() или strconv.ParseInt().
package main

import (
"fmt"
"strconv"
)

func main() {
str := "42"
num, err := strconv.Atoi(str) // Преобразуем строку в int
if err != nil {
fmt.Println("Ошибка:", err)
return
}
fmt.Println("Число:", num) // Выведет: Число: 42
}


Пример 2: strconv.ParseInt() (гибкое преобразование)
num, err := strconv.ParseInt("1234", 10, 64) 
// "1234" → строка, 10 → десятичная система, 64 → int64

if err != nil {
fmt.Println("Ошибка:", err)
} else {
fmt.Println("Число:", num) // 1234
}


🟠Преобразование `int` в строку
Используется strconv.Itoa() или strconv.FormatInt().
num := 42
str := strconv.Itoa(num) // 42 → "42"
fmt.Println("Строка:", str) // "42"


Пример 2: strconv.FormatInt() (int64 → строка)
num := int64(12345)
str := strconv.FormatInt(num, 10) // 10 — десятичная система
fmt.Println("Строка:", str) // "12345"


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Стандартный набор метрик Prometheus в Go-программе?

Prometheus в Go-программах собирает такие стандартные метрики:
- Go runtime метрики (go_gc_duration_seconds, go_memstats_heap_alloc_bytes).
- HTTP-метрики (если используется prometheus/client_golang).
- Метрики запросов (http_requests_total, request_duration_seconds).
- Производительность CPU и памяти (process_cpu_seconds_total, process_resident_memory_bytes).
Для их сбора нужно подключить prometheus/client_golang и зарегистрировать метрики в /metrics.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что такое SSL?

SSL (Secure Sockets Layer) — это протокол, который обеспечивает безопасность передачи данных в интернете, используя шифрование. Он был разработан для защиты данных, передаваемых между клиентом (например, веб-браузером) и сервером (например, веб-сайтом), от перехвата и манипуляций.

🚩Основные функции

🟠Шифрование:
Защита данных от перехвата и чтения посторонними лицами путем их шифрования.
🟠Аутентификация:
Подтверждение подлинности сервера (и иногда клиента) с помощью цифровых сертификатов, что позволяет клиенту убедиться, что он подключен к настоящему серверу.
🟠Целостность данных:
Проверка того, что данные не были изменены во время передачи, с помощью контрольных сумм и хеш-функций.

🚩Как работает

1⃣Установка соединения:
Клиент инициирует соединение с сервером, запрашивая защищенное соединение.
2⃣Передача сертификата:
Сервер отправляет свой цифровой сертификат, который содержит его публичный ключ и информацию о сервере.
3⃣Проверка сертификата:
Клиент проверяет сертификат, используя доверенные центры сертификации (CA), чтобы удостовериться в подлинности сервера.
4⃣Создание сессионных ключей:
Клиент и сервер используют асимметричное шифрование для обмена ключами сеанса, которые затем используются для симметричного шифрования данных в течение сессии.
5⃣Шифрование данных:
Все данные, передаваемые между клиентом и сервером, шифруются с использованием симметричных ключей, обеспечивая безопасность передачи.

🚩Пример использования

При посещении веб-сайта с использованием HTTPS (например, https://example.com), SSL обеспечивает шифрование и безопасность данных, передаваемых между вашим браузером и сервером.

🚩Развитие

🟠SSL 1.0:
Никогда не был выпущен публично из-за серьезных уязвимостей.
🟠SSL 2.0:
Выпущен в 1995 году, но вскоре был признан небезопасным из-за множества уязвимостей.
🟠SSL 3.0:
Выпущен в 1996 году, значительно улучшил безопасность, но со временем также был признан устаревшим из-за уязвимостей (например, POODLE-атака).

🚩TLS как наследник SSL

🟠TLS (Transport Layer Security):
SSL был заменен протоколом TLS, который является его преемником и предлагает улучшенную безопасность. Текущие версии TLS (1.2 и 1.3) используются вместо SSL.
🟠Основные отличия:
TLS обеспечивает более сильное шифрование, лучшее управление сессионными ключами и устранение уязвимостей, найденных в SSL.

Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Какие преимущества у горутин над тредами?

1. Легковесность – горутины потребляют меньше памяти, чем системные потоки.
2. Планировщик Go – позволяет управлять миллионами горутин без явного управления потоками.
3. Простота работы с конкурентностью – нет необходимости использовать mutex для синхронизации, благодаря каналам.
4. Низкие затраты на переключение контекста – переключение между горутинами быстрее, чем между потоками.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Зачем синхронизировать доступ данных?

В многопоточных (параллельных) программах горутины (goroutines) могут одновременно изменять одни и те же данные. Если не синхронизировать доступ, это приведёт к гонке данных (data race), когда несколько потоков читают/пишут одно и то же значение одновременно.

🚩`sync.Mutex` (мьютексы — блокировка данных)

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

import (
"fmt"
"sync"
)

var (
counter int
mutex sync.Mutex
)

func increment(wg *sync.WaitGroup) {
defer wg.Done()
mutex.Lock() // Блокируем доступ
counter++ // Изменяем данные
mutex.Unlock() // Разблокируем доступ
}

func main() {
var wg sync.WaitGroup

for i := 0; i < 1000; i++ {
wg.Add(1)
go increment(&wg)
}

wg.Wait()
fmt.Println("Итоговый счетчик:", counter) // 1000
}


🚩`sync.RWMutex` (разделяемая блокировка)

Позволяет нескольким горутинам читать данные одновременно, но блокирует запись.
var (
data int
mutex sync.RWMutex
)

func readData(wg *sync.WaitGroup) {
defer wg.Done()
mutex.RLock() // Разрешаем чтение
fmt.Println("Читаем данные:", data)
mutex.RUnlock()
}

func writeData(wg *sync.WaitGroup) {
defer wg.Done()
mutex.Lock() // Блокируем на запись
data++
mutex.Unlock()
}


🚩`sync.WaitGroup` (ожидание горутин)

Позволяет дождаться завершения всех горутин без блокировки данных.
var wg sync.WaitGroup

wg.Add(2) // Ожидаем 2 горутины
go func() {
defer wg.Done()
fmt.Println("Горутина 1 завершилась")
}()
go func() {
defer wg.Done()
fmt.Println("Горутина 2 завершилась")
}()

wg.Wait() // Ждём завершения всех горутин
fmt.Println("Все горутины завершены")


🚩`sync/atomic` (атомарные операции)

Атомарные операции быстрее мьютексов и гарантируют безопасное обновление переменных без гонок данных.
import "sync/atomic"

var counter int64

func incrementAtomic() {
atomic.AddInt64(&counter, 1) // Атомарное увеличение
}


🚩Каналы (лучший способ в Go)

В Go рекомендуется избегать блокировок и использовать каналы для передачи данных между горутинами.
package main

import "fmt"

func main() {
ch := make(chan int) // Канал для передачи данных

go func() {
ch <- 42 // Отправляем данные
}()

data := <-ch // Получаем данные
fmt.Println("Получено:", data)
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Какие есть недостатки у горутин относительно тредов?

1. Отсутствие привязки к конкретному ядру – горутины исполняются планировщиком Go, а не напрямую ОС.
2. Необходимость контроля за утечками – забытые горутины продолжают выполняться, потребляя ресурсы.
3. Ограниченная поддержка системных вызовов – при блокирующих операциях Go может выделять новый поток ОС.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Как слайсы работают?

Слайс (slice) — это динамический массив, который ссылается на часть массива в памяти. В отличие от массивов (array), слайсы могут изменять размер.

🚩Внутреннее устройство слайса

Слайс в Go — это структура
type SliceHeader struct {
Data uintptr // Указатель на массив в памяти
Len int // Длина слайса (количество элементов)
Cap int // Вместимость (capacity) — сколько элементов может вместить без перевыделения памяти
}


Пример структуры слайса
arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:4] // Берём срез от 2-го до 4-го элемента
fmt.Println(s) // [2 3 4]


🚩Создание слайсов

Есть несколько способов создать слайс:
Способ 1: Срез массива
arr := [5]int{10, 20, 30, 40, 50}
s := arr[1:4] // [20 30 40]


Способ 2: Использование make()
s := make([]int, 3, 5) // Длина 3, вместимость 5
fmt.Println(s, len(s), cap(s)) // [0 0 0] 3 5


Способ 3: Литерал (инициализация значениями)
s := []int{1, 2, 3}
fmt.Println(s) // [1 2 3]


🚩Изменение слайса

Слайсы можно изменять, используя append().
s := []int{1, 2, 3}
s = append(s, 4, 5)
fmt.Println(s) // [1 2 3 4 5]


🚩Как растёт `slice`?

Когда append() увеличивает slice, Go использует оптимизированный алгоритм роста:
- Если cap < 1024, слайс удваивает размер (cap *= 2).
- Если cap >= 1024, рост идёт примерно на 25% (cap += cap / 4).
s := []int{}
for i := 0; i < 10; i++ {
s = append(s, i)
fmt.Printf("Len: %d, Cap: %d\n", len(s), cap(s))
}


Выход (пример)
Len: 1, Cap: 1
Len: 2, Cap: 2
Len: 3, Cap: 4
Len: 5, Cap: 8
Len: 9, Cap: 16


🚩Как избежать неожиданных эффектов?

Так как слайсы хранят ссылку на массив, возможны побочные эффекты.
arr := [5]int{1, 2, 3, 4, 5}
s1 := arr[:3] // [1 2 3]
s2 := arr[2:] // [3 4 5]
s2[0] = 100 // Меняем первый элемент s2

fmt.Println(s1) // [1 2 100] ❗️ s1 тоже изменился


Решение: используйте copy() для создания нового массива.
s1 := []int{1, 2, 3}
s2 := make([]int, len(s1))
copy(s2, s1) // Копируем данные
s2[0] = 100
fmt.Println(s1) // [1 2 3] Оригинал не изменился


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Что можно делать с закрытым каналом?

- Читать данные, пока буфер не станет пустым.
- Использовать range, чтобы получить оставшиеся элементы.
- Проверять закрытие с помощью второго параметра (ok).
Записывать в закрытый канал нельзя – это приведет к panic.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM