Библиотека Go для собеса | вопросы с собеседований
7.43K subscribers
263 photos
10 videos
1 file
806 links
Вопросы с собеседований по Go и ответы на них.

Покажем, как запустить своего ии-агента: https://clc.to/tvpmD

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

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

Наши каналы: https://t.me/proglibrary/9197
Download Telegram
Что такое TUI и чем он отличается от GUI

TUI — текстовый пользовательский интерфейс, работающий в терминале. В отличие от GUI использует символы для отрисовки интерфейса, работает в консоли.

Примеры TUI: midnight commander, htop, vim. Примеры GUI: VSCode, браузеры, графические редакторы.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱5👍32👾2😁1
Почему при передаче слайса в функцию изменения элементов видны снаружи функции

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

Однако если вы делаете append и слайс перевыделится, то внутри функции слайс начинает указывать на новый массив, а снаружи остается ссылка на старый.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍72
Какой тип не может быть константой

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

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Что происходит, когда у слайса не хватает ёмкости для добавления элемента

Под капотом:

1. Выделяется новый массив с большей ёмкостью
2. Копируются все существующие элементы в новый массив
3. Добавляется новый элемент
4. Возвращается новый слайс, указывающий на новый массив

Четвёртый пункт — это одна из причин, почему важно всегда присваивать результат append(). Старый слайс может указывать на старый массив, а новый слайс — на новый.

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

sync.WaitGroup — это примитив синхронизации из стандартного пакета sync, предназначенный для ожидания завершения группы горутин.

WaitGroup использует внутренний счётчик:

• Счётчик увеличивается при добавлении новых задач
• Уменьшается при завершении каждой задачи
• Когда счётчик достигает нуля — все задачи выполнены

Пример использования:
func main() {
var wg sync.WaitGroup

numWorkers := 3
wg.Add(numWorkers) // Устанавливаем счётчик равным количеству горутин

for i := 1; i <= numWorkers; i++ {
go worker(i, &wg) // Запускаем горутины
}

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


🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8😁1
Какие есть гарантии того, что defer выполнится

Defer гарантированно выполнится при нормальном выходе из функции — это его основное предназначение. Но важно понимать нюансы:

• При обычном return из функции — defer отработает до возврата управления

• При панике — defer выполнится перед раскруткой стека

• При достижении конца функции — все отложенные вызовы выполнятся в обратном порядке

Когда defer НЕ выполнится:

• os.Exit() — немедленно завершает программу, defer'ы игнорируются

• Фатальные ошибки рантайма

SIGKILL, принудительное завершение

• Бесконечные циклы — если функция никогда не завершится, defer не выполнится

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Для чего используется wg.Done()

wg.Done() используется для сигнализации завершения задачи в sync.WaitGroup. Это метод уменьшает внутренний счетчик WaitGroup на 1.

Пример:
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // Гарантированный вызов wg.Done()
time.Sleep(time.Duration(id) * time.Second)
fmt.Printf("Worker %d завершен\n", id)
}


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

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

Пример:
package main

import "fmt"

func main() {
var fib func(n int) int // Объявляем переменную с типом функции
fib = func(n int) int { // Теперь можем ссылаться на fib внутри
if n < 2 {
return n
}
return fib(n-1) + fib(n-2) // Рекурсивный вызов
}
fmt.Println(fib(7)) // Вывод: 13
}


Вложенные функции подходят для задач вроде обхода деревьев или чисел Фибоначчи.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3🤔1🌚1
Как выйти из горутины

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

• Через done-канал
done := make(chan struct{})

go func() {
for {
select {
case <-done:
return // выходим из горутины
default:
// делаем работу
}
}
}()

// когда нужно остановить
close(done)


• Через context
ctx, cancel := context.WithCancel(context.Background())

go func() {
for {
select {
case <-ctx.Done():
return
default:
// делаем работу
}
}
}()

// когда нужно остановить
cancel()


Нельзя «убить» горутину извне силой и нет аналога thread.kill() из других языков.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
😍5🔥3🥱1
Что возвращает функция recover()

Функция recover() в Go возвращает значение, переданное в panic(), если она вызвана во время активной паники в текущей горутине и находится внутри отложенной функции.

Пример использования:
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Паника перехвачена:", r) // Вывод: Паника перехвачена: ошибка!
}
}()
panic("ошибка!")
fmt.Println("Этот код не выполнится") // Пропускается
}


🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Каким должен быть ключ в мапе

Ключ должен быть comparable типом — то есть поддерживать операции == и !=.

var m map[string]int        //  работает
var m map[int]string // работает
var m map[[]byte]int // ошибка компиляции: slice не comparable
var m map[map[string]int]int // ошибка: map не comparable


Что можно использовать как ключ:

• bool
• int, int8, int16, int32, int64
• uint, uint8, uint16, uint32, uint64, uintptr
• float32, float64
• complex64, complex128
• string
• pointer (*T)
• channel (chan T)
• interface — если динамический тип comparable
• struct — если все поля comparable
• array [N]T, если T comparable

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
5👍2
В чём разница между nil-слайсом и пустым слайсом

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

Обе формы безопасны: len, cap дают 0, append работает одинаково, цикл range не выполнится. Разница видна при JSON-маршалинге: nil-слайс → null, пустой → [].

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
4🎉3
Можно ли получить указатель на value в мапе

В Go невозможно взять указатель на значение, хранящееся в мапе.

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

Код, который выдаст ошибку
m := map[string]int{"a": 1}
p := &m["a"] // cannot take the address of m["a"]


Что делать вместо этого

Хранить указатели в мапе:
m := map[string]*int{}
val := 1
m["a"] = &val


Извлечь значение, изменить, записать обратно:
m := map[string]int{"a": 1}
val := m["a"]
val++
m["a"] = val


Использовать структуры:
type Data struct { value int }
m := map[string]*Data{"a": {1}}
m["a"].value++ // работает


🐸Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍83
К каким типам циклов применяется анализатор forvar — ко всем for или только к range

Согласно документации, анализатор forvar применяется только к range циклам.

Причина в том, что проблема с переиспользованием переменной цикла существовала именно в контексте range:
// До Go 1.22 - проблемный код
for _, x := range items {
go func() {
fmt.Println(x) // все горутины видели последнее значение x
}()
}

// Решение до Go 1.22
for _, x := range items {
x := x // создание новой переменной для каждой итерации
go func() {
fmt.Println(x) // теперь каждая горутина видит свое значение
}()
}


В обычных for циклах такой проблемы не было, так как переменная счетчика и так создавалась заново:
// Здесь проблемы не было даже до Go 1.22
for i := 0; i < 10; i++ {
go func() {
fmt.Println(i) // каждая итерация имела свой i
}()
}


🐸Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2😁1
Почему defer работает по LIFO

Каждый defer добавляет вызов в стек текущей горутины. При возврате из функции runtime.deferreturn разматывает стек снизу вверх, LIFO-порядок.

🐸Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
🥰2
Что Go-компилятор может сделать с кодом во время компиляции

Если выражение состоит из констант, компилятор считает результат сразу. const x = 5 * 10 + 3 становится 53. Работает для арифметики, строк, булевых операций

Простые функции встраиваются в место вызова. Компилятор подставляет тело, убирает call/ret overhead.

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

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👾2
Можно ли вызвать анонимную функцию сразу при объявлении

Да, добавьте () с аргументами в конце для немедленного вызова. Компилятор Go это поддерживает нативно.

result := func(a, b int) int { return a + b }(3, 5)
fmt.Println(result) // 8


Функция создаётся и исполняется сразу.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1😢1
Как Go обрабатывает операции ввода-вывода с файлами

Go предоставляет встроенные инструменты для работы с файловой системой: пакет os для базовых операций открытия, создания и закрытия файлов, а также io и bufio для буферизованного ввода-вывода.

Чтение файла

Типичный паттерн: открыть файл через os.Open(), затем прочитать содержимое построчно с помощью bufio.Scanner:
package main

import (
"bufio"
"fmt"
"log"
"os"
)

func main() {
file, err := os.Open("example.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}

if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}


Запись и дозапись в файл

Для создания нового файла используйте os.Create(), для дозаписи — os.OpenFile() с флагами os.O_APPEND|os.O_WRONLY:
// Создание и запись
file, err := os.Create("output.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()

file.WriteString("Hello, Go!\n")

// Дозапись в существующий файл
file, err := os.OpenFile("output.txt",
os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()

file.WriteString("Appended line\n")


Используйте bufio.Writer для буферизации при записи больших объёмов данных — это значительно ускорит операции ввода-вывода.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍1
Что возвращает range для строки

В цикле for index, value := range str строка str итерируется по UTF-8 рунам. index – байтовый индекс начала руны, value – значение руны.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
Почему в Go 1.18 добавили алиас any, а не убрали interface{}

Чтобы сохранить полную обратную совместимость, а не ломать миллионы строк существующего кода.

С точки зрения компилятора и поведения программы any и interface{} абсолютно эквивалентны.

🐸 Библиотека Go для собеса
Please open Telegram to view this post
VIEW IN TELEGRAM
😢2👍1
Что будет если вызвать WaitGroup.Add() с отрицательным числом

Поведение зависит от того, как это влияет на внутренний счётчик.

WaitGroup.Add() принимает любое целое число, включая отрицательные. Это можно использовать для уменьшения счётчика:
var wg sync.WaitGroup
wg.Add(5) // счётчик = 5
wg.Add(-2) // счётчик = 3


Если Add() делает внутренний счётчик отрицательным, то возникнет паника:

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