Go面接の頻出25問: 開発者向け完全ガイド

頻出25問でGoの面接を制する。ゴルーチン、チャネル、インターフェース、並行処理パターンをコード例とともに解説。

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
}

配列は値型ですが、スライス、マップ、チャネルは参照型です。

3. 配列とスライスの違いを説明してください

配列はコンパイル時に決まる固定長の型です。スライスは下層の配列に対する動的なビューで、ポインタ・長さ・容量という3つの要素から構成されます。

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では動的なコレクションにはスライスを使うのが一般的です。

4. defer文はどのように動作しますか

deferは関数の終了時に呼び出される処理を予約します。遅延された呼び出しはスタックに積まれ、LIFO(後入れ先出し)の順で実行されます。

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ランタイムが管理する軽量スレッドです。スタックは数KB(OSスレッドの数MBに対して)にとどまり、Goのスケジューラが数千のゴルーチンをわずかなOSスレッドに多重化します。

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. チャネルの仕組みを説明してください

チャネルはゴルーチン間の通信と同期を可能にします。バッファあり(容量を持つ)とバッファなし(同期)の2種類があります。

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. レースコンディションをどう防ぎますか

レースコンディションは複数のゴルーチンが同期なしで共有データへアクセスしたときに発生します。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は実行時にレースコンディションを検出します。

10. ワーカープールパターンを説明してください

ワーカープールパターンは、固定数のゴルーチンがキューからタスクを処理することで並行処理を制限します。

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は通常の流れを中断してスタックを巻き戻します。recoverdefer内でpanicを捕捉し、実行を継続させます。

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を返すのが望ましいです。

構造体・メソッド・埋め込み

13. 値レシーバとポインタレシーバの違いは何ですか

値レシーバは構造体のコピーを受け取り、ポインタレシーバは参照を受け取って元の構造体を変更できます。

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
}

ルール: いずれかのメソッドがポインタレシーバを使う場合は、一貫性のためその型の全メソッドをポインタレシーバに揃えるのが望ましいです。

14. Goにおける埋め込みはどう動きますか

埋め込みはある型を別の型の中に取り込み、メソッドとフィールドを継承します。これは古典的継承ではなくコンポジションです。

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"
}

埋め込みは継承の硬直さを避けつつ柔軟なコンポジションを実現します。

15. 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はスレッドセーフで、ダブルチェックロックを伴うミューテックスより簡潔です。

コンテキストとキャンセル

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. プログラムのグレースフルシャットダウンをどう実現しますか

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

ベンチマークは実装間の性能差を浮き彫りにします。

ジェネリクス(Go 1.18以降)

20. 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"
}

ジェネリクスにより、コードの重複や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ディレクトリは特別で、その内容は他のモジュールからimportできません。

上級トピック

23. Goのガベージコレクタはどう動きますか

Goは並行・トリカラーマーク&スイープ方式のガベージコレクタを採用し、低レイテンシに最適化されています。

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(論理プロセッサ)の3要素から成り立ちます。

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、ワーカープール)
  • ✅ テストとベンチマーク
  • ✅ Go 1.18以降のジェネリクス

Goの面接で成功する鍵は、簡潔さと性能のトレードオフを理解し、各並行処理パターンを使い分けるべきタイミングを判断できることです。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

タグ

#go
#golang
#interview
#concurrency
#goroutines

共有

関連記事