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

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

Ir al canal en Telegram

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

Mostrar más
7 422
Suscriptores
Sin datos24 horas
-77 días
+530 días
Archivo de publicaciones
⚡️Самые полезные каналы по Go в одной папке В ней: ➖интересные задачи ➖основной канал ➖книги по Go ➖лучшие вакансии из сферы ➖и наш чат, в котором можно общаться и задавать вопросы Добавляйте 👉 тык сюда

💬 Почему Go-процесс может использовать много виртуальной памяти? Аллокатор памяти в Go резервирует большую область виртуальной памяти как арену для выделения. Эта виртуальная память локальна для конкретного процесса Go; резервирование не лишает другие процессы памяти. Чтобы узнать количество фактически выделенной памяти процессу Go, можно использовать команду top в Unix (столбцы RES (Linux) или RSIZE (macOS)).

💬 Почему значение ошибки типа nil не равно nil? Под капотом интерфейсы реализованы как два элемента: тип T и значение V. V — это конкретное значение, такое как int, структура или указатель. Например, если мы сохраняем значение int 3 в интерфейсе, полученное значение интерфейса схематически будет (T=int, V=3). Значение V также известно как динамическое значение интерфейса, поскольку одна и та же переменная интерфейса может хранить разные значения V (и соответствующие типы T) во время выполнения программы. Значение интерфейса является nil только если и V, и T не установлены. В частности, nil интерфейс всегда будет содержать nil тип. Если мы сохраняем nil указатель типа *int внутри значения интерфейса, внутренний тип будет *int независимо от значения указателя: (T=*int, V=nil). Таким образом, значение интерфейса будет не nil, даже когда значение указателя V внутри является nil. Эта ситуация может быть запутанной и возникает, когда nil значение сохраняется внутри значения интерфейса, например, в возвращаемом значении ошибки:
func returnsError() error {
    var p *MyError = nil
    if bad() {
        p = ErrBad
    }
    return p // Всегда вернет не nil ошибку.
}
Если всё идет хорошо, функция возвращает nil p, так что возвращаемое значение — это значение интерфейса ошибки, содержащее (T=*MyError, V=nil). Это означает, что если вызывающий сравнивает возвращенную ошибку с nil, он всегда будет видеть, как будто произошла ошибка, даже если ничего плохого не случилось. Чтобы вернуть правильную nil ошибку вызывающему, функция должна явно вернуть nil:
func returnsError() error {
    if bad() {
        return ErrBad
    }
    return nil
}
Хорошей практикой для функций, возвращающих ошибки, является использование типа error в их сигнатуре (как сделано выше), а не конкретного типа, например *MyError, чтобы помочь гарантировать, что ошибка создается правильно.

💬 Что происходит с замыканиями, выполняемыми как горутины? При использовании замыканий с конкурентностью может возникнуть путаница. Рассмотрим пример:
func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    // ждем завершения всех горутин перед выходом
    for _ = range values {
        <-done
    }
}
Можно ошибочно ожидать увидеть следующий вывод: a, b, c. Вместо этого получаем c, c, c. Это происходит потому, что каждая итерация цикла использует один и тот же экземпляр переменной v, так что каждое замыкание делится этой единственной переменной. Когда замыкание выполняется, оно выводит значение v в момент выполнения fmt.Println, но v могла измениться с момента запуска горутины. Чтобы помочь обнаружить эту и другие проблемы до их возникновения, необходимо использовать go vet. Чтобы привязать текущее значение v к каждому замыканию при его запуске, необходимо изменить внутренний цикл так, чтобы на каждой итерации создавалась новая переменная. Один из способов — передать переменную в качестве аргумента замыканию:
for _, v := range values {
    go func(u string) {
        fmt.Println(u)
        done <- true
    }(v)
}
В этом примере значение v передается в качестве аргумента анонимной функции. Это значение затем доступно внутри функции как переменная u. Еще проще — создать новую переменную, используя стиль объявления, который может показаться странным, но в Go работает хорошо:
for _, v := range values {
    v := v // создаем новую 'v'.
    go func() {
        fmt.Println(v)
        done <- true
    }()
}

💬 Будет ли программа на Go работать быстрее при увеличении количества CPU? Ускорение работы программы с увеличением числа CPU зависит от решаемой задачи. Go предоставляет примитивы для работы с конкурентностью, такие как горутины и каналы, но конкурентность позволяет реализовать параллелизм только тогда, когда сама задача по своей природе параллельна. Задачи, которые по своей сути последовательны, не могут быть ускорены добавлением CPU, в то время как задачи, которые можно разбить на части, выполняемые параллельно, могут быть ускорены, иногда значительно. Иногда добавление большего количества CPU может замедлить программу. На практике программы, которые тратят больше времени на синхронизацию или коммуникацию, чем на полезные вычисления, могут испытывать снижение производительности при использовании нескольких потоков операционной системы. Это связано с тем, что передача данных между потоками влечет за собой смену контекстов, что обходится дорого, и эти затраты могут увеличиваться с ростом числа CPU. Например, пример с prime sieve из спецификации Go не имеет значительного параллелизма, хотя в нем запускается множество горутин; увеличение числа потоков (CPU) скорее замедлит его, чем ускорит.

Ищем Middle и Senior Гоферов, чтобы проводить собесы и менторить Ищем в Эйч Навыки — это менторская программа от гоферов для
Ищем Middle и Senior Гоферов, чтобы проводить собесы и менторить Ищем в Эйч Навыки — это менторская программа от гоферов для гоферов. Мы уже провели 200+ мок-интервью, но желающих пройти собес всё больше и больше, а нас, если честно, уже тупо не хватает на всех. Поэтому пришли сюда — чтобы найти новых классных коллег-менторов, которые хотят внести вклад в комьюнити и вырасти, обучая других. И готовы на доп занятость, пару часов в день. Надо будет проводить тестовые интервью с другими Go-разработчиками → оценивать уровень этих ребят → помогать им расти дальше. Для этого всего мы разработали систему грейдов и форму обратной связи. Если ты до этого никого не менторил, вообще не переживай — всему научим. Что взамен: 🔘 От 40К за 5-7 часов работы в неделю 🔘 Доступ к обучению и комьюнити других сильных менторов из Яндекса, Авито, Uber, Tinkoff и других 🔘 Возможность выступать на нашем YouTube-канале (у нас в среднем 5000 просмотров на видео) — так станешь заметнее на рынке 🔘 Жесткая прокачка софтов + классная строчка в резюме. «Провел(а) 100+ собеседований» — звучит круто, работодатели точно оценят. Если хочешь попробовать себя в роли ментора, заполни форму — мы свяжемся с тобой ➡️ https://forms.gle/EBe9EMrcj8ew1t5H6 Реклама: ООО “Эйч Карьера” erid: LjN8KVcva

💬Как можно гарантировать, что тип удовлетворяет интерфейсу? Мы можем «попросить» компилятор проверить, реализует ли тип T интерфейс I, попытавшись выполнить присваивание с использованием нулевого значения для T или указателя на T, в зависимости от ситуации:
type T struct{}
var _ I = T{}       // Проверяем, реализует ли T интерфейс I
var _ I = (*T)(nil) // Проверяем, реализует ли *T интерфейс I
Если T (или *T соответственно) не реализует I, ошибка будет обнаружена во время компиляции. Если мы хотим, чтобы реализация интерфейса была более очевидной, мы можем включить в определение интерфейса специальный метод с уникальным названием. Этот метод не выполняет никакой реальной функции, но служит как явное подтверждение того, что тип предназначен для соответствия данному интерфейсу. Таким образом, любой тип, который должен соответствовать интерфейсу, обязан реализовать этот метод, ясно демонстрируя свою совместимость с интерфейсом. Например:
type Fooer interface {
    Foo()
    ImplementsFooer()
}
Тип должен тогда реализовать метод ImplementsFooer, чтобы быть Fooer, ясно документируя этот факт и объявляя его в выводе go doc.
type Bar struct{}
func (b Bar) ImplementsFooer() {}
func (b Bar) Foo() {}
Большинство кода не использует такие ограничения, поскольку они ограничивают полезность идеи интерфейса. Однако иногда они необходимы для разрешения неоднозначностей между похожими интерфейсами.

💬 Чем пустой интерфейс отличается от nil интерфейса в Go? 1. Пустой интерфейс в Go не имеет определенных методов. Это означает, что он может содержать значение любого типа, так как каждый тип в Go, по умолчанию, удовлетворяет интерфейсу без методов. Пустые интерфейсы широко используются в Go для работы со значениями неизвестного типа, поскольку они могут хранить любой тип данных. 2. Nil интерфейс в Go — это интерфейс, у которого не установлены ни тип, ни значение. Такой интерфейс не ссылается ни на какой объект или значение. Важно отметить, что если интерфейс хранит указатель, который является nil, сам интерфейс при этом не будет nil. Это значит, что интерфейс с nil значением отличается от полностью nil интерфейса.

💬 Даны n каналов типа chan int. Напишите функцию, которая объединит все данные из этих каналов в один и вернет его. Решить эту задачу можно, запустив горутину для каждого входного канала, которая будет читать данные из этого канала и отправлять их в один общий канал. 📌 Вот как это можно реализовать:
package main

import (
    "fmt"
    "sync"
)

// mergeChannels объединяет данные из нескольких каналов в один
func mergeChannels(channels ...chan int) chan int {
    var wg sync.WaitGroup
    merged := make(chan int)

    output := func(c chan int) {
        for n := range c {
            merged <- n
        }
        wg.Done()
    }

    wg.Add(len(channels))
    for _, c := range channels {
        go output(c)
    }

    // Закрыть merged канал после завершения всех горутин
    go func() {
        wg.Wait()
        close(merged)
    }()

    return merged
}

func main() {
    // Пример использования
    c1 := make(chan int)
    c2 := make(chan int)
    c3 := make(chan int)

    // Заполнение каналов данными
    go func() { c1 <- 1; close(c1) }()
    go func() { c2 <- 2; close(c2) }()
    go func() { c3 <- 3; close(c3) }()

    // Объединение каналов
    for n := range mergeChannels(c1, c2, c3) {
        fmt.Println(n)
    }
}
Функция mergeChannels принимает переменное количество каналов chan int и возвращает один канал, в который будут направлены все данные из входных каналов. Для каждого входного канала создается горутина, которая читает данные из этого канала и отправляет их в общий канал merged. Использование sync.WaitGroup позволяет дождаться завершения всех горутин, после чего общий канал закрывается.

В образовательном проекте avito.code вышли ролики по Kubernetes. В первом видео — база по деплою, во втором — более сложные к
В образовательном проекте avito.code вышли ролики по Kubernetes. В первом видео — база по деплою, во втором — более сложные команды с kubectl и гайд по работе с интерфейсом k9s. Смотрите, чтобы научиться эффективно управлять приложением, быстро проверять и управлять его ресурсами.

💬 Существует ли в Go короткий синтаксис для объявления условного оператора if? Да, существует. Он позволяет объединить объявление переменной и условие в одном выражении. Этот синтаксис может пригодиться для управления областью видимости переменных и уменьшения объема кода. 📌 Простой пример:
if v, err := someFunction(); err != nil {
    // обработка ошибки
} else {
    // использование переменной v
}
Функция someFunction() возвращает два значения: результат (v) и ошибку (err). В блоке if сначала выполняется вызов функции, затем проверяется значение err. Если err не равно nil, выполняется блок кода для обработки ошибки. В противном случае, если err равно nil, выполняется блок else, где доступна переменная v.

💬 Что из себя представляет хеш-таблица в Go? Хеш-таблица в Go представлена ключевым словом map и может быть объявлена одним из способов ниже:

m := make(map[key_type]value_type)
m := new(map[key_type]value_type)
 var m map[key_type]value_type
m := map[key_type]value_type{key1: val1, key2: val2}
В Go, мапа использует массив бакетов для хранения пар ключ-значение. Ключи хешируются для определения, в какой бакет они должны быть помещены. При увеличении количества элементов в мапе, количество бакетов может удваиваться, чтобы поддерживать эффективность операций доступа. В случае коллизий используются дополнительные overflow бакеты. Эта структура позволяет оптимизировать производительность при различных операциях с данными.

💬 На вход подаются два неупорядоченных среза любой длины. Напишите функцию на Go, которая возвращает их пересечение. Несколько способов, которыми можно решить эту задачу: 1. Использование мапы: наиболее эффективный способ, особенно для больших срезов. Алгоритм: ☑️ Итерируем по первому срезу и добавляем каждый элемент в мапу. ☑️ Итерируем по второму срезу, проверяя наличие элемента в мапе. ☑️ Если элемент найден, добавляем его в результат. 2. Поэлементное сравнение: менее эффективный метод, особенно для больших срезов, поскольку его сложность — O(n*m), где n и m — размеры срезов. ☑️ Двойной цикл для сравнения каждого элемента одного среза с каждым элементом другого среза. ☑️ Если найдено совпадение, добавляем элемент в результат. 3. Сортировка: метод эффективен, если срезы большие и их можно изменять. ☑️ Сначала сортируем оба среза. ☑️ Затем используем два указателя, чтобы итерировать оба среза и находить совпадающие элементы. 📌 Простой пример:
package main

import (
    "fmt"
)

func Intersection(slice1, slice2 []int) []int {
    // Создание мапы для хранения элементов первого среза
    elements := make(map[int]bool)
    for _, item := range slice1 {
        elements[item] = true
    }

    // Поиск пересечений
    var intersection []int
    for _, item := range slice2 {
        if _, found := elements[item]; found {
            intersection = append(intersection, item)
            // Чтобы избежать повторений в результате
            delete(elements, item)
        }
    }

    return intersection
}

func main() {
    slice1 := []int{1, 3, 5, 7, 9}
    slice2 := []int{3, 4, 5, 6, 7}
    fmt.Println(Intersection(slice1, slice2)) // [3 5 7]
}

💬 Можно ли в Go цикл switch указать без условия? Да, в Go можно использовать оператор switch без явного условия. В таком случае, switch оценивает каждый case как логическое выражение. Это позволяет создавать более чистый и читаемый код, когда нужен простой способ написания длинных цепочек в духе if-then-else. Например:
x := 42

switch {
case x > 100:
    fmt.Println("x is very big")
case x > 10:
    fmt.Println("x is big")
default:
    fmt.Println("x is small")
}
Здесь switch последовательно проверяет каждое условие и выполняет код в блоке case, который соответствует первому истинному условию.

Пройди тест по Go: ответь на частые вопросы с собесов Привет! Это Эйч Навыки — команда гоферов из бигтеха. Мы давно менторим
Пройди тест по Go: ответь на частые вопросы с собесов Привет! Это Эйч Навыки — команда гоферов из бигтеха. Мы давно менторим 1:1 и проводим тестовые собеседования: уже провели 200+ мок-интервью по Go. Теперь собрали популярные вопросы с собесов по Concurrency, Базам Данных и указателям в один тест, чтобы ты узнал, какие навыки надо подтянуть для трудоустройства. Как пройдёшь тест, мы подарим тебе скидку на часовое мок-интервью. Senior Go-разработчик из большой компании проверит твои знания, подсветит сильные и слабые стороны, а ещё составит индивидуальный план занятий. Переходи в бота, чтобы пройти тест@skills_mentee_bot Реклама: ООО “Эйч Карьера” erid: LjN8JxJVj

💬 Для каких целей в Go предназначены теги сборки (build tags)? Теги сборки в Go предназначены для условной компиляции определенных частей кода в зависимости от заданных условий, таких как целевая ОС, архитектура или кастомные флаги. Это позволяет создавать более модульные и настраиваемые приложения, а также обеспечивает более гибкую организацию кода. 📌 Ключевые принципы: 1. Код, специфичный для платформы: например, наше приложение должно выполнять разные функции в зависимости от того, работает ли оно на Windows, Linux или macOS. В таком случае мы можем использовать теги сборки для включения соответствующего кода только для конкретной платформы. Например, файл config_windows.go может содержать конфигурации, специфичные для Windows, и будет иметь в первой строке комментарий // +build windows. 2. Экспериментальные фичи: теги сборки также могут использоваться для включения экспериментальных или еще не готовых фич. Например, если у нас есть экспериментальная функция, мы можем пометить её файл с помощью // +build experimental и компилировать приложение с этим тегом только тогда, когда захотим её включить. 3. Кастомные конфигурации: теги сборки позволяют создавать различные конфигурации нашего приложения. Например, мы можем использовать теги для сборки lite-версии приложения с ограниченным функционалом.

🧑‍💻 Статьи для IT: как объяснять и распространять значимые идеи Напоминаем, что у нас есть бесплатный курс для всех, кто хо
🧑‍💻 Статьи для IT: как объяснять и распространять значимые идеи Напоминаем, что у нас есть бесплатный курс для всех, кто хочет научиться интересно писать — о программировании и в целом. Что: семь модулей, посвященных написанию, редактированию, иллюстрированию и распространению публикаций. Для кого: для авторов, копирайтеров и просто программистов, которые хотят научиться интересно рассказывать о своих проектах. 👉Материалы регулярно дополняются, обновляются и корректируются. А еще мы отвечаем на все учебные вопросы в комментариях курса.