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

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

@go_interview_lib

Вопросы с собеседований по Go и ответы на них.

Учиться у нас: clc.to/iEeaZw

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

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

Наши каналы: https://t.me/proglibrary/9197
Subscribers
7.46K
Photos
286
Videos
12
Links
874

Showing posts older than #1320 · Back to latest

Older Posts 20 shown
Post #1319 1.56K
Указатель на указатель: когда это реально нужно

Двойной указатель решает конкретную задачу: изменить указатель внутри функции так, чтобы изменение было видно снаружи.

Проблема без **:
func resetPointer(p *int) {
newVal := 0
p = &newVal // меняем локальную копию — снаружи ничего не изменится
}


p — это копия адреса. Переназначение p внутри функции не затрагивает оригинал.

Решение через **int:
func resetPointer(p **int) {
newVal := 0
*p = &newVal // меняем сам указатель — изменение видно снаружи
}

func main() {
a := 42
ptr := &a
resetPointer(&ptr)
fmt.Println(*ptr) // 0
}


Теперь функция получает адрес самого указателя и может подменить его цель.

Другие кейсы

Динамические структуры данных — при построении связных списков или деревьев, когда нужно менять корневой указатель:
func insertHead(head **Node, val int) {
newNode := &Node{val: val, next: *head}
*head = newNode
}


Инициализация через функцию — паттерн, когда объект создаётся внутри функции и возвращается через параметр, а не через return:
func initConfig(cfg **Config) {
*cfg = &Config{Timeout: 30}
}


Когда не нужен

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

🐸 Библиотека Go для собеса
  • 🤔 5
  • 👾 3
  • ❤ 2
  • 🌚 2
  • 👍 1
Post #1318 1.54K
Что такое указатель на указатель

Указатель хранит адрес переменной. Указатель на указатель хранит адрес другого указателя. Каждый уровень добавляет одну «звёздочку» к типу и одно разыменование для доступа к значению.

Пример:
a := 100
var b *int = &a // b хранит адрес a
var c **int = &b // c хранит адрес b


Цепочка в памяти выглядит так:
c → b → a → 100


Разыменование:
fmt.Println(a)   // 100          — исходное значение
fmt.Println(b) // 0xc000014090 — адрес a
fmt.Println(*b) // 100 — значение a через b
fmt.Println(c) // 0xc00000e028 — адрес b
fmt.Println(*c) // 0xc000014090 — адрес a через c
fmt.Println(**c) // 100 — значение a через c


Каждая * это один шаг по цепочке адресов.

**c позволяет не только читать значение a, но и менять сам указатель b, то есть переключать его на другую переменную. Именно это делает двойной указатель полезным, а не просто экзотикой.

🐸 Библиотека Go для собеса
  • 🔥 6
  • 🌚 5
  • 😁 3
  • ❤ 2
Post #1317 1.61K
Когда использовать package aliasing

📌 Кейсы использования

1. Конфликт имён

Два пакета с одинаковым именем. Без алиасов код не скомпилируется.
import (
sqlDB "project/sql/db"
nosqlDB "project/nosql/db"
)


2. Длинный путь импорта

Сокращает шум при частом обращении к пакету с громоздким путём:
import (
mh "myproject/subproject/module/helpers"
)


3. Неудобное или неочевидное имя пакета

Актуально для сгенерированного кода, когда имя пакета не совпадает с тем, что ожидает читатель:
import (
validator "github.com/myorg/gen/v2/validate_pb"
)


4. Пустой импорт _

Специальный алиас для импорта ради побочных эффектов: регистрация драйвера, init()-функция; без использования пакета в коде:
import _ "github.com/lib/pq" // регистрирует PostgreSQL-драйвер


5. Импорт в текущее пространство имён .

Позволяет обращаться к экспортируемым именам пакета без префикса:
import . "math"

r := Sqrt(16) // вместо math.Sqrt(16)


🐸 Библиотека Go для собеса
  • 👍 9
Post #1316 1.53K
Что такое package aliasing

Package aliasing это возможность присвоить псевдоним импортируемому пакету прямо в блоке import. Полезен в конкретных ситуациях, но злоупотреблять им не стоит: лишние алиасы усложняют чтение кода.

Синтаксис:
import fm "fmt"

fm.Println("hello") // вместо fmt.Println


Алиас указывается перед путём к пакету и полностью заменяет его имя в текущем файле.

🐸 Библиотека Go для собеса
  • 👍 2
Post #1315 1.68K
Что такое паттерн Circuit Breaker и зачем он нужен

Circuit Breaker это защитный паттерн для распределённых систем. Он автоматически «отключает» вызовы к нестабильному сервису, чтобы предотвратить каскадные сбои и снизить нагрузку на и без того падающий узел.

Аналогия проста: как автоматический выключатель в электрощитке защищает проводку от перегрузки, так и Circuit Breaker защищает систему от цепной реакции ошибок.

Два состояния

Замкнуто — штатный режим. Все запросы проходят в обычном режиме. Счётчик ошибок растёт при каждом сбое.

Разомкнуто — защитный режим. После превышения порога ошибок паттерн перестаёт вызывать сервис и сразу возвращает ошибку. Сервис получает время на восстановление.

Реализация:

type Circuit func(context.Context) (string, error)

func Breaker(circuit Circuit, failureThreshold uint) Circuit {
var consecutiveFailures int = 0
var lastAttempt = time.Now()
var m sync.RWMutex

return func(ctx context.Context) (string, error) {
m.RLock()
d := consecutiveFailures - int(failureThreshold)

if d >= 0 {
// Экспоненциальная выдержка: 2, 4, 8... секунд
shouldRetryAt := lastAttempt.Add(time.Second * 2 << d)
if !time.Now().After(shouldRetryAt) {
m.RUnlock()
return "", errors.New("service unreachable")
}
}
m.RUnlock()

response, err := circuit(ctx)

m.Lock()
defer m.Unlock()

lastAttempt = time.Now()
if err != nil {
consecutiveFailures++
return response, err
}

consecutiveFailures = 0 // Успех — сбрасываем счётчик
return response, nil
}
}


Что важно в этой реализации

sync.RWMutex защищает общее состояние при конкурентных вызовах

• Экспоненциальная выдержка (2 << d) даёт сервису всё больше времени на восстановление с каждой неудачной попыткой

• После успешного вызова счётчик сбрасывается — цепь «замыкается» обратно автоматически

• Функция возвращает тот же тип Circuit, что позволяет прозрачно встраивать Breaker без изменения кода клиента

🐸 Библиотека Go для собеса
  • 👍 8
  • 😁 1
Post #1314 1.51K
В Go существует несколько способов возврата структур или их частей. Назовите основные.

1. Возврат копии структуры

Функция возвращает значение, изменения копии не затронут оригинал:
func returnCopy() MyStruct {
return MyStruct{Value: 1}
}


2. Возврат указателя на структуру

Позволяет избежать копирования и работать напрямую с объектом в памяти:
func returnPointer() *MyStruct {
return &MyStruct{Value: 2}
}


3. Изменение через указатель-параметр

Функция принимает указатель и напрямую изменяет оригинальный объект без возврата:
func modifyStruct(s *MyStruct) {
s.Value = 3
}


4. Возврат части структуры

Возвращает только нужное поле, а не всю структуру целиком:
func returnValue(s MyStruct) int {
return s.Value
}


5. Возврат через интерфейс

Позволяет скрыть конкретную реализацию за абстракцией:
type MyInterface interface { DoSomething() }

func returnInterface() MyInterface {
return MyStruct{}
}


6. Срезы и мапы структур

Возврат набора структур через срез или ассоциативный массив:
func returnSlice() []MyStruct {
return []MyStruct{{Value: 4}, {Value: 5}}
}

func returnMap() map[string]MyStruct {
return map[string]MyStruct{
"first": {Value: 6},
"second": {Value: 7},
}
}


7. Возврат через канал


Передача структуры между горутинами для конкурентных сценариев:
func sendToChannel(ch chan MyStruct) {
ch <- MyStruct{Value: 8}
}


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

🐸 Библиотека Go для собеса
  • 🤔 15
  • ❤ 2
  • 🌚 2
  • 👍 1
  • 🤩 1
Post #1313 1.52K
Как работает sync.WaitGroup изнутри

Внутри WaitGroup хранит счётчик. Вы управляете им через три метода:

Add(n) увеличивает счётчик на n. Вызывать его нужно до запуска горутин, а не внутри них — иначе возникает гонка данных: Wait может сработать раньше, чем Add успеет зарегистрировать горутину.

Done() уменьшает счётчик на 1. Его вызывают внутри горутины, когда работа завершена. Чтобы не забыть вызвать его даже при панике, принято писать defer wg.Done() в самом начале горутины.

Wait() блокирует выполнение до тех пор, пока счётчик не вернётся к нулю. Как только все горутины вызвали Done() — основной поток продолжает работу.

🐸 Библиотека Go для собеса
  • 🤔 6
Post #1311 1.68K
Почему append на nil-срезе работает, а запись в nil-мапу вызывает панику

nil-срез это валидное значение с len == 0 и cap == 0. Когда вызывается append(), Go сам выделяет новую память под капотом. Явная инициализация не нужна.

var a []int
a = append(a, 4, 5, 6) // Go выделяет память сам
fmt.Println(a) // [4 5 6]


nil-мапа не инициализирована, у неё нет памяти для хранения данных. Любая попытка записи немедленно вызывает панику в рантайме. Перед использованием нужно явно вызвать make.

var m map[int]int
// m[1] = 1 ← паника!

m = make(map[int]int)
m[1] = 1 // теперь работает


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

🐸 Библиотека Go для собеса
  • 👍 5
Post #1310 1.8K
Скомпилируется ли код при циклической зависимости

В
Go компилятор запрещает циклические зависимости между пакетами. Если пакет A импортирует B, а B импортирует A, то код просто не соберётся.

Go намеренно не даёт собрать такой код по двум причинам. Первая: порядок инициализации. Если A зависит от B, а B от A, непонятно, что инициализировать первым. Вторая это структура кода. Цикл между пакетами почти всегда сигнализирует о проблеме в архитектуре.

Как решить проблему

Обычно помогает одно из трёх. Вынести общий код в отдельный пакет, от которого зависят оба. Использовать интерфейс вместо прямого импорта. Пересмотреть границы пакетов: если два пакета так тесно связаны, возможно, это один пакет.

🐸 Библиотека Go для собеса
  • ❤ 3
Post #1309 1.79K
Как отменить множество горутин одновременно

Когда горутин много, останавливать каждую вручную не получится. Нужен способ послать сигнал сразу всем:
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

for i := 0; i < 5; i++ {
go func(id int) {
for {
select {
case <-ctx.Done():
fmt.Printf("горутина %d остановлена\n", id)
return
default:
fmt.Printf("горутина %d работает\n", id)
time.Sleep(500 * time.Millisecond)
}
}
}(i)
}

time.Sleep(2 * time.Second)
cancel()
time.Sleep(100 * time.Millisecond)
}


context.WithCancel возвращает контекст и функцию cancel. Контекст передаётся во все горутины. Каждая горутина слушает ctx.Done() через select. Когда вызывается cancel(), канал ctx.Done() закрывается, и все горутины, которые его слушают, получают сигнал одновременно. defer cancel() в начале гарантирует, что ресурсы освободятся даже если функция завершится раньше времени.

🐸 Библиотека Go для собеса
  • 👍 8
  • ❤ 2
Post #1308 1.71K
Как корректно прокинуть context.Context в функцию внутри OnceValue

Напрямую никак: OnceValue принимает функцию без аргументов.

Контекст нужно захватить в замыкании. Но это опасно, контекст может быть отменён к моменту первого вызова:
// Плохо: ctx может быть уже отменён
ctx := context.Background()
get := sync.OnceValue(func() *DB {
return connect(ctx) // ctx захвачен в замыкании
})


🐸 Библиотека Go для собеса
  • 👾 1
Post #1307 1.66K
Что делает io.WriterTo и как он связан с io.Copy

Интерфейс:
type WriterTo interface {
WriteTo(w io.Writer) (n int64, err error)
}


Суть: тип сам берёт управление на себя и решает, как именно записать свои данные в переданный Writer.

Зачем это нужно

io.Copy по умолчанию работает через буфер 32KB и читает кусок из источника, пишет в назначение, и так по кругу. Данные гоняются через userspace.

Но некоторые типы знают более эффективный путь. Например, *os.File на Linux может использовать системный вызов sendfile и данные перемещаются на уровне ядра, без копирования в память процесса.

Как io.Copy это использует

Перед тем как запустить свой буферный цикл, она проверяет интерфейсы:
// 1. Источник умеет писать сам?
if wt, ok := src.(WriterTo); ok {
return wt.WriteTo(dst)
}
// 2. Назначение умеет читать само?
if rf, ok := dst.(ReaderFrom); ok {
return rf.ReadFrom(src)
}
// 3. Fallback — буфер 32KB


Если источник реализует WriterTo, то io.Copy просто уступает ему управление.

🐸 Библиотека Go для собеса
  • 👍 8
  • ❤ 1
Post #1306 1.57K
Что такое канонизация строк

Канонизация строк это приведение строки к единственной стандартной форме перед сравнением или обработкой.

Одна и та же строка может быть представлена по-разному. Классический пример это Unicode. Символ é можно записать:

- как один кодпоинт U+00E9
- как два кодпоинта e + U+0301

Байтово это разные строки. Визуально — одинаковые. Без канонизации == вернёт false.

В стандартной библиотеке есть пакет golang.org/x/text/unicode/norm, который реализует четыре формы нормализации по стандарту Unicode:
import "golang.org/x/text/unicode/norm"

a := "é" // precomposed
b := "e\u0301" // decomposed

// Без канонизации
fmt.Println(a == b) // false

// С канонизацией
normA := norm.NFC.String(a)
normB := norm.NFC.String(b)
fmt.Println(normA == normB) // true


Где это важно на практике

- Сравнение имён пользователей и email
- Работа с файловой системой (macOS использует NFD, Linux — NFC)
- Поиск по тексту
- Безопасность: канонизация помогает избежать обходов валидации через визуально идентичные, но байтово разные строки

🐸 Библиотека Go для собеса
  • ❤ 8
  • 😁 1
Post #1305 1.57K
Пишете на Go? Приходите на ВК ДжейТи Митап!

Реальные кейсы и решения из производства, общение с коллегами — всё это в офисах VK:
📍 в Москве — 4 апреля
📍 в Санкт-Петербурге — 11 апреля

В программе два доклада от инженеров VK и неформальная часть:
Москва
• как сократить число запросов с 400 млн до 200 тысяч и построить безопасную платформу мини-приложений
• после — трек на выбор: архитектурная задача с разбором от экспертов VK или обсуждение инженерных новостей в записи подкаста с Никитой Галушко, ведущим разработчиком API ВКонтакте, членом программного комитета Golang Conf/Голанг Конф

Санкт-Петербург
• как построить безопасную платформу мини-приложений и создать высокопроизводительный клиент для Tarantool на Go
• решение архитектурной задачи с обратной связью от инженеров VK
• неформальное общение с коллегами.

Только офлайн. Успевайте зарегистрироваться по ссылке.
Post #1304 1.59K
Какой способ для проверки пустой строки лучше: s != "" или len(s) == 0

С точки зрения читаемости, предпочтительнее s != "" — сразу очевидно, что s является строкой. len(s) == 0 более универсальный подход, подходящий для слайсов, мап и других типов, поэтому может быть менее выразительным в контексте строк.

С точки зрения производительности разницы практически нет. Строка в Go — это структура с указателем и длиной, и пустая строка "" не создаётся заново при каждом сравнении. Компилятор с высокой вероятностью оптимизирует оба варианта к одной и той же проверке длины.

🐸 Библиотека Go для собеса
  • 👾 1
Post #1303 1.68K
Что делает io.Seeker

Интерфейс с одним методом Seek(offset int64, whence int) (int64, error).

Устанавливает позицию курсора для следующего Read или Write. Возвращает новое абсолютное смещение от начала файла и ошибку если что-то пошло не так.

Ключевое слово «для следующего». Seek сам ничего не читает и не пишет, он только перемещает внутренний указатель. Следующий вызов Read начнёт именно с этой позиции.

Стандартные типы реализующие Seeker:

os.File — файлы на диске
strings.Reader — строки в памяти
bytes.Reader — байтовые срезы в памяти
io.SectionReader — ограниченный участок другого ReaderAt

🐸 Библиотека Go для собеса
Post #1302 1.79K
Объясните, для чего нужен пакет singleflight

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

Пакет предоставляет единственную структуру Group, и основной метод Do(key, fn). Если вызов с таким key уже выполняется, новый вызов блокируется и ждёт. Когда первый завершается, все получают одинаковые value, err и флаг shared, сигнализирующий, что результат был разделён.

var g singleflight.Group

func fetchData(key string) (any, error) {
v, err, _ := g.Do(key, func() (any, error) {
return loadFromDB(key) // выполнится только один раз
})
return v, err
}


🐸 Библиотека Go для собеса
  • 👍 9
  • ❤ 1
Post #1301 1.57K
Можно ли вызывать возвращённую функцию в OnceValue конкурентно

Да, это прямо указано в документации: «The returned function may be called concurrently». OnceValue безопасен для использования из нескольких горутин одновременно.

🐸 Библиотека Go для собеса
  • 👍 2
  • ❤ 1
Post #1300 1.77K
😱 Если ваш продукт не умеет отдавать данные в формате, понятном AI-агенту, то вас просто не существует

Скрипт не будет кликать по красивым кнопкам в браузере, он уйдёт к конкуренту с нормальным API. Перестроить архитектуру под машинных клиентов — это уже не хайп, а необходимое условие сохранения конкурентоспособности.

Как адаптировать продукт и не исчезнуть из выдачи:

— интегрировать MCP и A2A-взаимодействие, чтобы агенты могли вас читать;
— научиться контролировать стоимость (лимиты, кэш, роутинг между моделями);
— настроить AgentOps: трейсинг, логирование и отлов регрессий.

Всё это ждёт вас на обновлённом курсе «Разработка AI-агентов». Мы специально сделали фокус на утилитарном инжиниринге и production-ready решениях.

Кстати, до 29 марта можно забрать курс с большой скидкой, и стоит поторопиться — мест на потоке всё меньше.

Зафиксировать цену и начать деплоить агентов без слива бюджета 👈
  • 😁 3
  • ❤ 1
  • 🌚 1
Post #1299 1.65K
Что произойдёт с памятью, которую держит OnceValue, если обёртка больше не используется

Обёртка это замыкание, которое держит ссылки на once, result и f. Пока есть хотя бы одна ссылка на обёртку, GC не освободит эти данные. Если f тяжёлая инициализация, например, загрузка большого файла, а обёртка живёт в глобальной переменной, то данные живут вечно.

🐸 Библиотека Go для собеса
  • 😁 1
Older posts →
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →