Топ 25 запитань на співбесіду з Go: повний посібник для розробника

Підкорюйте співбесіди з Go завдяки 25 найпоширенішим запитанням. Горутини, канали, інтерфейси та патерни конкурентності з прикладами коду.

Запитання на співбесіду з Go - Повний посібник з підготовки

Технічні співбесіди з Go перевіряють розуміння ключових концепцій мови: конкурентність, керування пам'яттю та ідіоматичні шаблони. Цей посібник охоплює 25 найпоширеніших запитань із розгорнутими відповідями та прикладами коду.

Порада на співбесіду

Go цінує простоту та читабельність. Інтерв'юери надають перевагу стислим відповідям, які демонструють глибоке розуміння, перед надмірно складними рішеннями.

Основи мови Go

1. Чим відрізняється var від :=?

Оголошення var дає змогу явно вказати тип і працює на рівні пакета. Оператор := автоматично виводить тип, але доступний лише всередині функцій.

declaration.gogo
package main

// Package level - var required
var globalConfig = "production"

func main() {
    // var with explicit type
    var count int = 10

    // var with type inference
    var name = "Alice"

    // Short declaration - functions only
    age := 25

    // Multiple declarations
    var (
        host = "localhost"
        port = 8080
    )
}

Усередині функцій перевагу віддають короткому оголошенню := за стислість, тоді як var залишається необхідним для змінних рівня пакета.

2. Як працює система типів у Go?

Go використовує статичну типізацію з виведенням типів. Мова відрізняє типи за значенням (копіюються при присвоєнні) від типів за посиланням (поділяють спільну структуру).

types.gogo
package main

import "fmt"

func main() {
    // Value types - full copy
    a := [3]int{1, 2, 3}
    b := a          // Copies the array
    b[0] = 100      // Doesn't modify a
    fmt.Println(a)  // [1 2 3]

    // Reference types - share data
    slice1 := []int{1, 2, 3}
    slice2 := slice1    // Same underlying array
    slice2[0] = 100     // Also modifies slice1
    fmt.Println(slice1) // [100 2 3]

    // Maps are also references
    m1 := map[string]int{"a": 1}
    m2 := m1
    m2["a"] = 100
    fmt.Println(m1["a"]) // 100
}

Масиви — це типи за значенням, а slice'и, map'и та канали — типи за посиланням.

3. Поясни різницю між масивами та slice'ами

Масиви мають фіксований розмір, що визначається на етапі компіляції. Slice'и — це динамічні представлення над базовим масивом, що складаються з трьох частин: вказівника, довжини й місткості.

arrays_slices.gogo
package main

import "fmt"

func main() {
    // Array - fixed size, value type
    arr := [5]int{1, 2, 3, 4, 5}

    // Slice - view over the array
    slice := arr[1:4]  // [2 3 4]
    fmt.Printf("len=%d, cap=%d\n", len(slice), cap(slice))
    // len=3, cap=4

    // Modifications affect original array
    slice[0] = 20
    fmt.Println(arr) // [1 20 3 4 5]

    // Direct creation with make
    dynamic := make([]int, 3, 10)
    // len=3, cap=10

    // Append may reallocate
    dynamic = append(dynamic, 1, 2, 3, 4, 5)
}

У Go slice'и — це найпоширеніший тип для динамічних колекцій.

4. Як працює інструкція defer?

defer планує виклик функції на завершення зовнішньої функції. Відкладені виклики накопичуються та виконуються у порядку LIFO (last in, first out).

defer.gogo
package main

import (
    "fmt"
    "os"
)

func main() {
    // LIFO order
    defer fmt.Println("1")
    defer fmt.Println("2")
    defer fmt.Println("3")
    // Prints: 3, 2, 1
}

// Typical use case: resource cleanup
func readFile(path string) ([]byte, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }
    defer file.Close() // Always executes

    // Read file...
    return os.ReadFile(path)
}

// Caution: arguments are evaluated immediately
func deferArgs() {
    x := 10
    defer fmt.Println(x) // Captures 10
    x = 20
    // Prints: 10
}

defer гарантує виконання навіть під час panic, тому ідеально підходить для звільнення ресурсів.

5. Що таке інтерфейс у Go?

Інтерфейс описує набір методів. Будь-який тип, що реалізує ці методи, неявно задовольняє інтерфейсу — без явного оголошення.

interfaces.gogo
package main

import "fmt"

// Interface definition
type Writer interface {
    Write([]byte) (int, error)
}

// Type that implicitly implements Writer
type FileLogger struct {
    path string
}

func (f *FileLogger) Write(data []byte) (int, error) {
    // Write to file
    fmt.Println("Writing to", f.path)
    return len(data), nil
}

// Empty interface - accepts any type
func printAny(v interface{}) {
    fmt.Printf("Type: %T, Value: %v\n", v, v)
}

// Type assertion
func process(w Writer) {
    // Type check
    if fl, ok := w.(*FileLogger); ok {
        fmt.Println("FileLogger with path:", fl.path)
    }
}

Неявна реалізація інтерфейсів забезпечує сильне розчеплення між пакетами.

Конкурентність та горутини

6. Що таке горутина і чим вона відрізняється від потоку?

Горутина — це легкий потік, яким керує рантайм Go. Її стек займає кілька КБ (на відміну від кількох МБ системного потоку), а планувальник Go мультиплексує тисячі горутин на кільканадцять системних потоків.

goroutines.gogo
package main

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

func main() {
    var wg sync.WaitGroup

    // Launch 1000 goroutines
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func(id int) {
            defer wg.Done()
            time.Sleep(100 * time.Millisecond)
            fmt.Printf("Goroutine %d finished\n", id)
        }(i) // Pass i by value
    }

    wg.Wait()
    fmt.Println("All goroutines completed")
}
Поширена пастка

Змінні циклу варто завжди передавати у горутини за значенням. Інакше всі горутини можуть зафіксувати однакове кінцеве значення.

7. Поясни, як працюють канали

Канали забезпечують комунікацію та синхронізацію між горутинами. Вони можуть бути буферизованими (з місткістю) або небуферизованими (синхронними).

channels.gogo
package main

import "fmt"

func main() {
    // Unbuffered channel - blocks until received
    ch := make(chan int)

    go func() {
        ch <- 42 // Blocks until read
    }()

    value := <-ch // Receives value
    fmt.Println(value)

    // Buffered channel - doesn't block until full
    buffered := make(chan string, 2)
    buffered <- "first"
    buffered <- "second"
    // buffered <- "third" // Would block

    fmt.Println(<-buffered) // "first"
    fmt.Println(<-buffered) // "second"
}

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

8. Як використовувати select із кількома каналами?

select одночасно очікує на кілька операцій із каналами. Виконується перша готова операція, а в разі рівноваги вибір випадковий.

select.gogo
package main

import (
    "fmt"
    "time"
)

func main() {
    ch1 := make(chan string)
    ch2 := make(chan string)

    go func() {
        time.Sleep(100 * time.Millisecond)
        ch1 <- "from ch1"
    }()

    go func() {
        time.Sleep(200 * time.Millisecond)
        ch2 <- "from ch2"
    }()

    // Wait with timeout
    for i := 0; i < 2; i++ {
        select {
        case msg := <-ch1:
            fmt.Println(msg)
        case msg := <-ch2:
            fmt.Println(msg)
        case <-time.After(500 * time.Millisecond):
            fmt.Println("Timeout")
        }
    }

    // Non-blocking select with default
    select {
    case msg := <-ch1:
        fmt.Println(msg)
    default:
        fmt.Println("No message available")
    }
}

select — основний інструмент для елегантного керування конкурентністю в Go.

9. Як уникати race conditions?

Race conditions виникають, коли кілька горутин одночасно звертаються до спільних даних без синхронізації. Go надає кілька механізмів захисту.

race_conditions.gogo
package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

// Solution 1: Mutex
type SafeCounter struct {
    mu    sync.Mutex
    count int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.count++
}

// Solution 2: RWMutex for read-heavy workloads
type Cache struct {
    mu   sync.RWMutex
    data map[string]string
}

func (c *Cache) Get(key string) string {
    c.mu.RLock()         // Multiple readers allowed
    defer c.mu.RUnlock()
    return c.data[key]
}

func (c *Cache) Set(key, value string) {
    c.mu.Lock()          // Single writer
    defer c.mu.Unlock()
    c.data[key] = value
}

// Solution 3: atomic for simple counters
var atomicCounter int64

func incrementAtomic() {
    atomic.AddInt64(&atomicCounter, 1)
}

func main() {
    // Detection: go run -race main.go
    counter := SafeCounter{}
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }

    wg.Wait()
    fmt.Println("Count:", counter.count)
}

Прапорець компілятора -race фіксує race conditions під час виконання.

10. Поясни патерн worker pool

Патерн worker pool обмежує конкурентність, створюючи фіксовану кількість горутин, що обробляють завдання з черги.

worker_pool.gogo
package main

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

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        fmt.Printf("Worker %d processing job %d\n", id, job)
        time.Sleep(100 * time.Millisecond) // Simulate work
        results <- job * 2
    }
}

func main() {
    const numJobs = 10
    const numWorkers = 3

    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)

    var wg sync.WaitGroup

    // Start workers
    for w := 1; w <= numWorkers; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }

    // Send jobs
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)

    // Wait and close results
    go func() {
        wg.Wait()
        close(results)
    }()

    // Collect results
    for result := range results {
        fmt.Println("Result:", result)
    }
}

Цей патерн запобігає надмірному використанню пам'яті та CPU через створення зайвих горутин.

Готовий до співбесід з Go?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Обробка помилок та panic/recover

11. Як обробляти помилки в Go?

Go використовує явні значення повернення для помилок без винятків. За домовленістю error — останній параметр повернення.

errors.gogo
package main

import (
    "errors"
    "fmt"
)

// Sentinel errors for comparison
var (
    ErrNotFound     = errors.New("resource not found")
    ErrUnauthorized = errors.New("access unauthorized")
)

// Custom error type
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation %s: %s", e.Field, e.Message)
}

func validateAge(age int) error {
    if age < 0 {
        return &ValidationError{
            Field:   "age",
            Message: "must be positive",
        }
    }
    return nil
}

func main() {
    // Basic check
    if err := validateAge(-5); err != nil {
        // Type assertion for custom error
        var valErr *ValidationError
        if errors.As(err, &valErr) {
            fmt.Printf("Field: %s\n", valErr.Field)
        }
    }

    // Sentinel error comparison
    err := findUser("unknown")
    if errors.Is(err, ErrNotFound) {
        fmt.Println("User not found")
    }
}

func findUser(id string) error {
    // Error wrapping with context
    return fmt.Errorf("findUser %s: %w", id, ErrNotFound)
}

Обгортання через %w ланцюжкує помилки, але дозволяє перевіряти оригінальну.

12. Коли використовувати panic та recover?

panic перериває нормальний потік і розгортає стек. recover перехоплює panic у defer і дозволяє продовжити виконання.

panic_recover.gogo
package main

import "fmt"

func safeOperation() (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered from panic: %v", r)
        }
    }()

    riskyOperation()
    return nil
}

func riskyOperation() {
    // Simulates an operation that can panic
    panic("something went wrong")
}

// Legitimate use case: initialization validation
func MustCompileRegex(pattern string) *Regexp {
    r, err := regexp.Compile(pattern)
    if err != nil {
        panic(err) // Programming error
    }
    return r
}

func main() {
    err := safeOperation()
    if err != nil {
        fmt.Println("Recovered error:", err)
    }
    fmt.Println("Program continues")
}
Золоте правило

Panic варто застосовувати лише до програмних помилок (порушені інваріанти). Для очікуваних помилок (відсутній файл, мережеві проблеми) краще завжди повертати error.

Структури, методи та embedding

13. У чому різниця між receiver'ами за значенням і за вказівником?

Receiver за значенням отримує копію структури, тоді як receiver за вказівником отримує посилання й може змінювати оригінал.

receivers.gogo
package main

import "fmt"

type Counter struct {
    value int
}

// Value receiver - works on copy
func (c Counter) GetValue() int {
    return c.value
}

// Pointer receiver - modifies original
func (c *Counter) Increment() {
    c.value++
}

// Pointer receiver for large structs (avoids copy)
type LargeStruct struct {
    data [1000]int
}

func (l *LargeStruct) Process() {
    // Avoids copying 8000 bytes
}

func main() {
    c := Counter{value: 0}
    c.Increment() // Go automatically converts
    fmt.Println(c.GetValue()) // 1

    // Careful with interfaces
    var _ fmt.Stringer = &c // OK if method on *Counter
}

Правило: якщо хоч один метод використовує receiver за вказівником, для узгодженості всі методи типу мають бути також за вказівником.

14. Як працює embedding у Go?

Embedding включає один тип у склад іншого, успадковуючи його методи й поля. Це не класичне успадкування, а композиція.

embedding.gogo
package main

import "fmt"

type Logger struct {
    prefix string
}

func (l *Logger) Log(msg string) {
    fmt.Printf("[%s] %s\n", l.prefix, msg)
}

// Embedding Logger
type Service struct {
    *Logger // Pointer embedding
    name    string
}

func NewService(name string) *Service {
    return &Service{
        Logger: &Logger{prefix: name},
        name:   name,
    }
}

func main() {
    svc := NewService("API")

    // Promoted method - direct access
    svc.Log("Starting")

    // Explicit access also works
    svc.Logger.Log("Explicit")

    // Promoted field
    fmt.Println(svc.prefix) // "API"
}

Embedding дозволяє гнучкі композиції, уникаючи жорсткості традиційного успадкування.

15. Як реалізувати патерн singleton у Go?

Пакет sync пропонує sync.Once, що гарантує одноразову ініціалізацію навіть при паралельних горутинах.

singleton.gogo
package main

import (
    "fmt"
    "sync"
)

type Database struct {
    connectionString string
}

var (
    instance *Database
    once     sync.Once
)

func GetDatabase() *Database {
    once.Do(func() {
        fmt.Println("Single initialization")
        instance = &Database{
            connectionString: "postgres://...",
        }
    })
    return instance
}

func main() {
    // Concurrent calls - single initialization
    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            db := GetDatabase()
            fmt.Printf("Instance: %p\n", db)
        }()
    }
    wg.Wait()
}

sync.Once потокобезпечний і елегантніший за mutex з double-check locking.

Context та скасування

16. Для чого потрібен пакет context?

Пакет context керує дедлайнами, сигналами скасування та прив'язаними до запиту значеннями уздовж усього дерева викликів.

context.gogo
package main

import (
    "context"
    "fmt"
    "time"
)

func main() {
    // Context with timeout
    ctx, cancel := context.WithTimeout(
        context.Background(),
        2*time.Second,
    )
    defer cancel() // Always call cancel

    result := make(chan string, 1)

    go func() {
        // Simulate long operation
        time.Sleep(3 * time.Second)
        result <- "completed"
    }()

    select {
    case res := <-result:
        fmt.Println(res)
    case <-ctx.Done():
        fmt.Println("Timeout:", ctx.Err())
    }
}

// Propagation through functions
func fetchData(ctx context.Context, url string) ([]byte, error) {
    // Early check
    if ctx.Err() != nil {
        return nil, ctx.Err()
    }

    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return nil, err
    }

    // HTTP client respects context
    resp, err := http.DefaultClient.Do(req)
    // ...
}

Кожна потенційно тривала функція має приймати context.Context як перший параметр.

17. Як забезпечити graceful shutdown програми?

Системні сигнали SIGINT та SIGTERM можна перехоплювати, аби виконати чистий вихід.

graceful_shutdown.gogo
package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    // Context cancelled on signal
    ctx, stop := signal.NotifyContext(
        context.Background(),
        syscall.SIGINT,
        syscall.SIGTERM,
    )
    defer stop()

    // Start server
    server := startServer()

    // Wait for signal
    <-ctx.Done()
    fmt.Println("\nShutting down...")

    // Timeout for graceful shutdown
    shutdownCtx, cancel := context.WithTimeout(
        context.Background(),
        5*time.Second,
    )
    defer cancel()

    if err := server.Shutdown(shutdownCtx); err != nil {
        fmt.Println("Shutdown error:", err)
    }

    fmt.Println("Shutdown complete")
}

Цей патерн дає активним з'єднанням завершитися коректно перед зупинкою.

Тести та бенчмарки

18. Як писати тести в Go?

Вбудований пакет testing забезпечує базовий функціонал. Тести розміщуються у файлах *_test.go.

calculator_test.gogo
package calculator

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}

// Table-driven tests
func TestAddTableDriven(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive", 2, 3, 5},
        {"negative", -1, -1, -2},
        {"mixed", -1, 5, 4},
        {"zero", 0, 0, 0},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("Add(%d, %d) = %d; want %d",
                    tt.a, tt.b, result, tt.expected)
            }
        })
    }
}

Таблично орієнтовані тести — це ідіоматичний підхід Go для перевірки кількох випадків.

19. Як писати бенчмарки?

Бенчмарки використовують testing.B і запускаються командою go test -bench.

benchmark_test.gogo
package main

import (
    "strings"
    "testing"
)

func BenchmarkStringConcat(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var s string
        for j := 0; j < 100; j++ {
            s += "a"
        }
    }
}

func BenchmarkStringBuilder(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var sb strings.Builder
        for j := 0; j < 100; j++ {
            sb.WriteString("a")
        }
        _ = sb.String()
    }
}

// Typical results:
// BenchmarkStringConcat-8      50000    28000 ns/op
// BenchmarkStringBuilder-8   1000000     1200 ns/op

Бенчмарки виявляють різницю в продуктивності між реалізаціями.

Generics (Go 1.18+)

20. Як використовувати generics у Go?

Go 1.18 представив параметри типу, які дозволяють писати узагальнений код, зберігаючи безпеку типів.

generics.gogo
package main

import "fmt"

// Generic function
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

// Custom type constraint
type Number interface {
    int | int64 | float64
}

func Sum[T Number](values []T) T {
    var sum T
    for _, v := range values {
        sum += v
    }
    return sum
}

// Generic type
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

func main() {
    // Usage
    doubled := Map([]int{1, 2, 3}, func(n int) int {
        return n * 2
    })
    fmt.Println(doubled) // [2 4 6]

    fmt.Println(Sum([]int{1, 2, 3, 4, 5})) // 15

    stack := &Stack[string]{}
    stack.Push("hello")
    stack.Push("world")
    val, _ := stack.Pop()
    fmt.Println(val) // "world"
}

Generics усувають потребу дублювати код або використовувати interface{}.

Модулі та залежності

21. Як працює система модулів Go?

Модулі Go керують залежностями із семантичним версіонуванням. Файл go.mod описує модуль і його залежності.

go.mod examplego
module github.com/user/myproject

go 1.21

require (
    github.com/gin-gonic/gin v1.9.1
    github.com/lib/pq v1.10.9
)

// Essential commands:
// go mod init github.com/user/project
// go mod tidy        - clean dependencies
// go get package@v1.2.3 - add/update
// go mod vendor      - copy locally
bash
# Updating dependencies
go get -u ./...           # All dependencies
go get -u=patch ./...     # Patches only

Файл go.sum містить криптографічні контрольні суми, які забезпечують цілісність залежностей.

22. Як структурувати Go-проєкт?

Стандартна структура спирається на конвенції спільноти й не нав'язує жорстких правил.

text
myproject/
├── cmd/
│   └── api/
│       └── main.go      # Entry point
├── internal/            # Private to module
│   ├── handler/
│   ├── service/
│   └── repository/
├── pkg/                 # Reusable external code
├── go.mod
├── go.sum
└── README.md

Папка internal особлива: її вміст не може бути імпортований іншими модулями.

Розширені запитання

23. Як працює garbage collector у Go?

Go використовує конкурентний триколірний mark-and-sweep garbage collector, оптимізований під низьку затримку.

gc_optimization.gogo
package main

import "runtime"

func main() {
    // GC configuration
    // GOGC=100 (default) - triggers GC when heap doubles

    // Force GC
    runtime.GC()

    // Memory statistics
    var stats runtime.MemStats
    runtime.ReadMemStats(&stats)

    println("Alloc:", stats.Alloc)
    println("NumGC:", stats.NumGC)
    println("PauseTotalNs:", stats.PauseTotalNs)
}

// Optimization techniques
// 1. Reuse allocations with sync.Pool
// 2. Pre-allocate slices with make([]T, 0, cap)
// 3. Avoid repeated string/[]byte conversions
// 4. Use pointers for large structs

Змінна оточення GODEBUG=gctrace=1 показує сліди GC.

24. Поясни планувальник Go

Планувальник Go використовує модель M:N, відображаючи N горутин на M системних потоків, із трьома сутностями: G (горутина), M (потік) і P (логічний процесор).

scheduler.gogo
package main

import (
    "fmt"
    "runtime"
)

func main() {
    // Number of logical processors (P)
    fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0))

    // Number of active goroutines
    fmt.Println("NumGoroutine:", runtime.NumGoroutine())

    // Yield processor to other goroutines
    runtime.Gosched()

    // M:P:G model
    // - G: goroutine (lightweight stack ~2KB)
    // - M: OS thread (machine)
    // - P: logical processor (execution context)
    //
    // Each P has a local queue of Gs
    // Work stealing when queue is empty
}

Із Go 1.14 планувальник є витискаючим, що не дозволяє одній горутині монополізувати P.

25. Як оптимізувати продуктивність у Go?

Оптимізація починається з профілювання, щоб виявити вузькі місця.

profiling.gogo
package main

import (
    "os"
    "runtime/pprof"
)

func main() {
    // CPU profiling
    f, _ := os.Create("cpu.prof")
    pprof.StartCPUProfile(f)
    defer pprof.StopCPUProfile()

    // Code to profile...

    // Memory profiling
    mf, _ := os.Create("mem.prof")
    defer mf.Close()
    pprof.WriteHeapProfile(mf)
}

// Analysis: go tool pprof cpu.prof

// Common optimization techniques:
// 1. Avoid allocations in hot loops
// 2. Use sync.Pool for reusable objects
// 3. Prefer []byte over string for mutations
// 4. Use bufio for I/O
// 5. Batch database operations
Правило оптимізації

Спочатку вимірюй, потім оптимізуй. Профілювання часто відкриває несподіванки про реальні вузькі місця.

Висновок

Ці 25 запитань охоплюють фундаментальні концепції, які перевіряють на співбесідах із Go:

Чек-лист підготовки:

  • ✅ Опанування горутин та каналів
  • ✅ Розуміння неявних інтерфейсів
  • ✅ Ідіоматична обробка помилок
  • ✅ Коректне використання context
  • ✅ Патерни конкурентності (mutex, worker pool)
  • ✅ Тести й бенчмарки
  • ✅ Знання generics із Go 1.18+

Ключ до успіху на співбесіді з Go: показати розуміння компромісів між простотою та продуктивністю й знати, коли застосовувати кожен патерн конкурентності.

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Теги

#go
#golang
#interview
#concurrency
#goroutines

Поділитися

Пов'язані статті