Golang | Вопросы собесов
4.53K subscribers
32 photos
1 video
801 links
Download Telegram
🤔 Что можешь сказать о HTTP3?

HTTP/3, работающий на основе QUIC, обеспечивает быструю передачу данных поверх UDP. Для его использования в Go можно применять библиотеки вроде quic-go, позволяющие интегрировать поддержку HTTP/3.


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

Один из полезных паттернов, который я использовал в реальном проекте на Go — это Worker Pool (пул воркеров).
Этот паттерн помогает ограничить количество горутин при обработке задач, чтобы не перегружать систему.

🚩Задача

В моём проекте был сервис, который обрабатывал тысячи файлов параллельно. Если запускать новую горутину на каждый файл, то память быстро заканчивалась, и сервер "умирал".
Решение: вместо создания тысяч горутин я использовал Worker Pool с фиксированным числом воркеров (например, 5), которые брали задачи из очереди.

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

Есть канал задач (jobs).
Есть пул воркеров (workers), которые читают задачи из канала.
Каждый воркер обрабатывает одну задачу и берёт следующую.
package main

import (
"fmt"
"sync"
"time"
)

// Количество воркеров
const workerCount = 5

// Воркер, который берет задачи из канала и обрабатывает их
func worker(id int, jobs <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
time.Sleep(time.Second) // Имитация обработки
}
}

func main() {
jobs := make(chan int, 10)
var wg sync.WaitGroup

// Запускаем воркеров
for i := 1; i <= workerCount; i++ {
wg.Add(1)
go worker(i, jobs, &wg)
}

// Отправляем задачи в канал
for j := 1; j <= 10; j++ {
jobs <- j
}
close(jobs) // Закрываем канал, чтобы воркеры знали, что задачи кончились

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


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
💊3👍1🤔1
🤔 Для чего нужны оконные функции?

Оконные функции (window functions) — это SQL-функции, которые выполняются по строкам с учётом контекста «окна» (группы строк).
Примеры использования:
- Нумерация (ROW_NUMBER())
- Скользящие суммы/средние (SUM() OVER(...))
- Сравнение соседних строк (LAG(), LEAD())
- Ранжирование (RANK())
Они позволяют делать аналитику прямо в SQL, без подзапросов и группировок.


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

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

🚩Почему нужен Prometheus?

🟠Мониторинг состояния системы
позволяет отслеживать производительность, загрузку CPU, память, сетевой трафик и другие параметры.
🟠Автоматический сбор метрик
использует модель pull (запрашивает данные у сервисов), а не push (как, например, Graphite). Это удобнее для управления и отказоустойчивости.
🟠Гибкость в сборе данных
поддерживает кастомные метрики, которые можно интегрировать в своё приложение.
🟠Продвинутая система алертинга
позволяет настроить уведомления при достижении критических значений.
🟠Хранение исторических данных
помогает анализировать тренды и предсказывать возможные сбои.
🟠Масштабируемость
легко развертывается в облаке, Kubernetes, Docker и других средах.

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

🟠Экспортеры метрик
собирают данные.
🟠Prometheus
опрашивает эти экспортеры по HTTP (pull-модель).
🟠PromQL
(язык запросов) используется для анализа данных.
🟠Grafana
или другие инструменты визуализируют метрики.
🟠Alertmanager
отправляет уведомления (Slack, Email, Telegram и др.).

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

Для интеграции в Go-приложение можно использовать пакет prometheus/client_golang
package main

import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)

var httpRequests = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
)

func handler(w http.ResponseWriter, r *http.Request) {
httpRequests.Inc() // Увеличиваем счетчик запросов
w.Write([]byte("Hello, Prometheus!"))
}

func main() {
// Регистрируем метрику
prometheus.MustRegister(httpRequests)

// Эндпоинт для сбора метрик
http.Handle("/metrics", promhttp.Handler())
http.HandleFunc("/", handler)

http.ListenAndServe(":8080", nil)
}


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

1. С помощью литералов: slice := []int{1, 2, 3}.
2. Через make: slice := make([]int, length, capacity), где length — длина, а capacity — ёмкость.
3. Пустой слайс: var slice []int.


Ставь 👍 если знал ответ, 🔥 если нет
Забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Forwarded from easyoffer
Ура, друзья! Изиоффер переходит в публичное бета-тестирование!

🎉 Что нового:
🟢Анализ IT собеседований на основе 4500+ реальных интервью
🟢Вопросы из собеседований с вероятностью встречи
🟢Видео-примеры ответов на вопросы от Senior, Middle, Junior грейдов
🟢Пример лучшего ответа
🟢Задачи из собеседований
🟢Тестовые задания
🟢Примеры собеседований
🟢Фильтрация всего контента по грейдам, компаниям
🟢Тренажер подготовки к собеседованию на основе интервальных повторений и флеш карточек
🟡Тренажер "Реальное собеседование" с сценарием вопросов из реальных собеседований (скоро)
🟢Автоотклики на HeadHunter
🟢Закрытое сообщество easyoffer


💎 Акция в честь открытия для первых 500 покупателей:
🚀 Скидка 50% на PRO тариф на 1 год (15000₽ → 7500₽)

🔥 Акция уже стартовала! 👉 https://easyoffer.ru/pro
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔 Можно ли изменить определенный символ в строке?

Нет, строки (string) в Go неизменяемы (immutable). Это значит, что нельзя просто изменить один символ в строке.

🚩Почему нельзя изменить символ?

Строка в Go — это байтовый срез ([]byte), но неизменяемый. Когда вы создаёте строку
s := "hello"


Ошибка при попытке изменения символа напрямую
s := "hello"
s[0] = 'H' // Ошибка: cannot assign to s[0]


🚩Как изменить символ в строке?

Поскольку строка неизменяема, вам нужно создать новую строку с заменённым символом.
Способ 1: Преобразовать в []byte (для ASCII-строк)
Если строка содержит только английские буквы и символы ASCII, её можно преобразовать в []byte, заменить символ и создать новую строку.
package main

import "fmt"

func main() {
s := "hello"
b := []byte(s) // Преобразуем в изменяемый []byte
b[0] = 'H' // Меняем первый символ
s = string(b) // Преобразуем обратно в строку

fmt.Println(s) // "Hello"
}


Способ 2: Преобразовать в []rune (для Unicode)
Если строка содержит русские буквы, эмодзи или другие многобайтовые символы, используйте []rune.
package main

import "fmt"

func main() {
s := "привет"
r := []rune(s) // Преобразуем в []rune (массив символов)
r[0] = 'П' // Меняем первый символ
s = string(r) // Преобразуем обратно в строку

fmt.Println(s) // "Привет"
}


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

rune — это псевдоним для int32, который представляет один символ Unicode. Используется для работы с многоязычными текстами и символами, особенно когда требуется обработка не-ASCII символов. Это полезно в случаях, когда нужно обрабатывать строки на уровне символов, а не байтов.

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

Горутины и потоки (треды) в традиционном понимании операционных систем — это две различные концепции параллельного выполнения кода, каждая из которых имеет свои особенности и преимущества. Вот ключевые различия между ними.

🚩Модель управления

Горутины — это легковесные "зеленые" потоки, управляемые Go runtime. Они не являются потоками операционной системы, и Go runtime отвечает за их планирование и выполнение на доступных физических потоках. Это позволяет создавать тысячи и даже миллионы горутин в рамках одного приложения с относительно небольшими затратами памяти и CPU.
Треды — это потоки выполнения, управляемые непосредственно операционной системой. Каждый тред занимает значительно больше ресурсов, чем горутина, особенно в плане памяти и времени на создание и управление. Треды более подходят для задач, требующих высокой вычислительной мощности и прямого взаимодействия с операционной системой.

🚩Затраты ресурсов

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

🚩Масштабируемость

Горутины могут масштабироваться до большого количества параллельных задач благодаря меньшим требованиям к ресурсам и управлению со стороны runtime Go.
Треды ограничены в масштабируемости физическими ресурсами системы и более высокими затратами на управление.

🚩Контекст переключения

Горутины имеют намного более эффективный контекст переключения, так как Go runtime оптимизирован для работы с большим количеством горутин и их переключением.
Треды терпят большие затраты времени на переключение контекста, так как операционной системе требуется больше времени для управления потоками.
package main

import (
"fmt"
"time"
)

func say(text string) {
for i := 0; i < 5; i++ {
fmt.Println(text)
time.Sleep(time.Millisecond * 500)
}
}

func main() {
go say("Hello")
say("World")
}


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

Линтеры интегрируются в IDE или CI/CD пайплайны. Например, golangci-lint используется для анализа Go-кода.


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

Это неизменяемые последовательности байтов, обычно представляющие текст в кодировке UTF-8. Работа с ними в Go основывается на использовании встроенных функций, методов стандартной библиотеки и специальных типов данных.

🟠Конкатенация строк
Самый простой способ объединить строки — использовать оператор + или функцию fmt.Sprintf.
package main

import "fmt"

func main() {
s1 := "Привет"
s2 := "Мир"
result := s1 + ", " + s2 + "!"
fmt.Println(result) // Привет, Мир!

// Через fmt.Sprintf
formatted := fmt.Sprintf("%s, %s!", s1, s2)
fmt.Println(formatted) // Привет, Мир!
}


🟠Получение длины строки
Функция len возвращает длину строки в байтах, а не в символах.
package main

import "fmt"

func main() {
str := "Привет"
fmt.Println(len(str)) // 12, так как кириллические символы занимают 2 байта
}


🟠Итерация по строке
Строки можно обойти как в виде байтов, так и в виде рун (Unicode символов).
package main

import (
"fmt"
"unicode/utf8"
)

func main() {
str := "Привет"

// Итерация по байтам
for i := 0; i < len(str); i++ {
fmt.Printf("Байт: %x\n", str[i])
}

// Итерация по символам (рунам)
for _, runeValue := range str {
fmt.Printf("Символ: %c\n", runeValue)
}

// Количество рун
fmt.Println("Количество символов:", utf8.RuneCountInString(str))
}


🟠Извлечение подстроки
Так как строки неизменяемы, для извлечения подстроки используется срез (slice).
package main

import "fmt"

func main() {
str := "Привет, Мир!"
substring := str[8:] // С 8-го байта до конца
fmt.Println(substring) // Мир!
}


🟠Разделение и объединение строк
Функции strings.Split и strings.Join из пакета strings позволяют разбивать строку на части или объединять части в строку.
package main

import (
"fmt"
"strings"
)

func main() {
str := "apple,banana,cherry"

// Разделение строки
parts := strings.Split(str, ",")
fmt.Println(parts) // [apple banana cherry]

// Объединение строк
joined := strings.Join(parts, " | ")
fmt.Println(joined) // apple | banana | cherry
}


🟠Поиск и замена подстроки
Для поиска подстроки можно использовать strings.Contains, strings.Index или strings.HasPrefix/strings.HasSuffix. Для замены — strings.Replace.
package main

import (
"fmt"
"strings"
)

func main() {
str := "Go — это круто!"

// Поиск подстроки
fmt.Println(strings.Contains(str, "круто")) // true
fmt.Println(strings.Index(str, "это")) // 4

// Замена подстроки
newStr := strings.Replace(str, "круто", "отлично", 1)
fmt.Println(newStr) // Go — это отлично!
}


🟠Преобразование регистра
Стандартная библиотека предоставляет функции для изменения регистра строки: strings.ToLower и strings.ToUpper.
package main

import (
"fmt"
"strings"
)

func main() {
str := "Hello, Go!"
fmt.Println(strings.ToUpper(str)) // HELLO, GO!
fmt.Println(strings.ToLower(str)) // hello, go!
}


🟠Тримминг строки
Удаление пробелов или других символов с начала/конца строки выполняется с помощью strings.Trim, strings.TrimSpace и других функций.
package main

import (
"fmt"
"strings"
)

func main() {
str := " Hello, Go! "
fmt.Println(strings.TrimSpace(str)) // "Hello, Go!"
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2🤔1
🤔 Чем Mongo отличается от Postgres?

MongoDB:
- Документо-ориентированная база данных.
- Хранит данные в формате BSON (похож на JSON).
- Подходит для гибкой схемы или её отсутствия.
- Хороша для быстрого прототипирования и хранения неструктурированных данных.
PostgreSQL:
- Реляционная СУБД с жёсткой схемой.
- Использует таблицы и SQL-запросы.
- Сильная поддержка транзакций, связей, индексов и сложных запросов.
Mongo — для гибкости и масштабируемости, Postgres — для структурированных и строгих данных.


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

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

🚩Основные характеристики

🟠Универсальность
Пустой интерфейс может содержать значение любого типа, потому что все типы в Go автоматически реализуют пустой интерфейс.
🟠Использование
Пустой интерфейс широко используется для создания обобщенных (generic) структур данных, функций и методов, которые могут работать с данными любых типов.

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

🟠Присваивание значений разного типа
Пустой интерфейс может использоваться для хранения значений различных типов в одной переменной.
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 printValue(v interface{}) {
fmt.Println(v)
}

func main() {
printValue(42)
printValue("hello")
printValue(true)
}


🟠Универсальные структуры данных
Пустой интерфейс используется для создания структур данных, которые могут хранить значения различных типов.
package main

import "fmt"

func main() {
var values []interface{}
values = append(values, 42, "hello", true)

for _, v := range values {
fmt.Println(v)
}
}


🟠Обработка разнородных данных
Пустой интерфейс используется для обработки данных различных типов, например, при парсинге JSON.
package main

import (
"encoding/json"
"fmt"
)

func main() {
jsonData := `{"name": "Alice", "age": 30}`
var result map[string]interface{}
json.Unmarshal([]byte(jsonData), &result)

fmt.Println(result)
}


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

🟠Утверждение типа
Утверждение типа позволяет проверить и преобразовать значение пустого интерфейса к конкретному типу.
package main

import "fmt"

func main() {
var i interface{} = "hello"

s, ok := i.(string)
if ok {
fmt.Println("String:", s) // Output: String: hello
} else {
fmt.Println("Not a string")
}

n, ok := i.(int)
if ok {
fmt.Println("Integer:", n)
} else {
fmt.Println("Not an integer")
}
}


🟠Использование `switch` для проверки типа
Конструкция 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
👍4🔥1
🤔 Зачем нужно кэширование?

Кэширование помогает:
- Ускорить доступ к часто используемым данным.
- Снизить нагрузку на сервер или БД.
- Сократить сетевой трафик и задержки.
- Обеспечить плавную работу при временных перебоях внешних сервисов.
Кэш может быть в памяти, на диске, на клиенте, в браузере или на уровне CDN.


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

В Go обычный map не потокобезопасен. Если несколько горутин одновременно записывают в map, возникнет ошибка "fatal error: concurrent map writes".
Решение: использовать синхронизацию через sync.Mutex, sync.RWMutex или sync.Map.

🚩Использование `sync.Mutex` (мьютекс)

Блокируем доступ на запись и чтение через sync.Mutex.
package main

import (
"fmt"
"sync"
)

type SafeMap struct {
mu sync.Mutex
m map[string]int
}

func (s *SafeMap) Set(key string, value int) {
s.mu.Lock() // Блокируем доступ
defer s.mu.Unlock()
s.m[key] = value
}

func (s *SafeMap) Get(key string) int {
s.mu.Lock() // Блокируем на чтение
defer s.mu.Unlock()
return s.m[key]
}

func main() {
safeMap := SafeMap{m: make(map[string]int)}
var wg sync.WaitGroup

for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
safeMap.Set(fmt.Sprintf("key%d", i), i)
}(i)
}

wg.Wait()
fmt.Println("Готово:", safeMap.Get("key5")) // Получаем значение без гонок данных
}


🚩Использование `sync.RWMutex` (оптимизация для чтения)

sync.RWMutex позволяет нескольким горутинам читать одновременно, но блокирует запись.
type SafeMap struct {
mu sync.RWMutex
m map[string]int
}

func (s *SafeMap) Set(key string, value int) {
s.mu.Lock() // Блокируем только на запись
defer s.mu.Unlock()
s.m[key] = value
}

func (s *SafeMap) Get(key string) int {
s.mu.RLock() // Разрешаем множественное чтение
defer s.mu.RUnlock()
return s.m[key]
}


🟠Использование `sync.Map` (встроенный потокобезопасный `map`)
sync.Map из стандартной библиотеки уже потокобезопасен, но работает немного медленнее обычного map из-за внутренних механизмов.
package main

import (
"fmt"
"sync"
)

func main() {
var m sync.Map

var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
m.Store(i, i*10) // Потокобезопасная запись
}(i)
}

wg.Wait()

val, ok := m.Load(5) // Потокобезопасное чтение
if ok {
fmt.Println("Значение:", val)
}
}


Использование канала (`chan`) вместо `map`
Вместо map можно передавать данные через канал (chan), если логика позволяет.
package main

import (
"fmt"
)

func main() {
ch := make(chan map[string]int, 1)
ch <- make(map[string]int)

go func() {
m := <-ch
m["key"] = 42
ch <- m
}()

m := <-ch
fmt.Println(m["key"]) // 42
}


Ставь 👍 и забирай 📚 Базу знаний
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2