Go 면접 핵심 25문항: 개발자를 위한 완전 가이드
Go 면접에 가장 많이 등장하는 25개 질문으로 합격을 노리세요. 고루틴, 채널, 인터페이스, 동시성 패턴을 코드 예제로 정리했습니다.

Go 기술 면접에서는 언어의 핵심 개념인 동시성, 메모리 관리, 관용적 패턴에 대한 이해가 평가됩니다. 이 가이드는 가장 자주 등장하는 25개 질문을 상세한 답변과 코드 예제와 함께 정리합니다.
Go는 단순함과 가독성을 중시합니다. 면접관은 지나치게 복잡한 풀이보다 깊은 이해를 보여주는 간결한 답변을 선호합니다.
Go 언어의 기초
1. var와 :=의 차이는 무엇입니까
var 선언은 타입을 명시할 수 있고 패키지 수준에서도 사용할 수 있습니다. := 연산자는 타입을 자동으로 추론하지만 함수 내부에서만 사용할 수 있습니다.
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는 타입 추론을 갖춘 정적 타이핑을 사용합니다. 할당 시 복사되는 값 타입과 내부 구조를 공유하는 참조 타입을 구분합니다.
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, channel은 참조 타입입니다.
3. 배열과 slice의 차이를 설명해 주세요
배열은 컴파일 시점에 크기가 고정됩니다. slice는 기반 배열에 대한 동적 뷰로, 포인터·길이·용량의 세 요소로 구성됩니다.
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(나중에 들어간 것이 먼저 나가는) 순서로 실행됩니다.
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에서 인터페이스는 무엇입니까
인터페이스는 일련의 메서드를 정의합니다. 해당 메서드를 구현하는 모든 타입은 명시적인 선언 없이 인터페이스를 암묵적으로 만족합니다.
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 수준(시스템 스레드의 수 MB와 대비)이며, Go 스케줄러는 수천 개의 고루틴을 소수의 시스템 스레드에 다중화합니다.
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. 채널의 동작 방식을 설명해 주세요
채널은 고루틴 사이의 통신과 동기화를 가능하게 합니다. 버퍼가 있는(용량을 가지는) 채널과 버퍼가 없는(동기) 채널이 있습니다.
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는 여러 채널 작업을 동시에 기다립니다. 가장 먼저 준비된 작업이 실행되며, 동시에 준비된 경우에는 무작위로 선택됩니다.
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 condition을 어떻게 방지합니까
race condition은 여러 고루틴이 동기화 없이 공유 데이터에 접근할 때 발생합니다. Go는 여러 보호 메커니즘을 제공합니다.
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 condition을 감지합니다.
10. worker pool 패턴을 설명해 주세요
worker pool 패턴은 고정된 수의 고루틴이 큐에서 작업을 가져와 처리하도록 하여 동시성을 제한합니다.
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는 마지막 반환 매개변수입니다.
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는 defer 안에서 panic을 잡아 실행을 계속할 수 있게 합니다.
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. 값 receiver와 포인터 receiver의 차이는 무엇입니까
값 receiver는 구조체의 사본을 받고, 포인터 receiver는 참조를 받아 원본을 수정할 수 있습니다.
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를 사용한다면 일관성을 위해 해당 타입의 모든 메서드도 포인터 receiver를 사용하는 것이 바람직합니다.
14. Go에서 임베딩은 어떻게 동작합니까
임베딩은 한 타입을 다른 타입에 포함시켜 메서드와 필드를 물려받게 합니다. 이는 고전적 상속이 아니라 컴포지션입니다.
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는 동시 고루틴 환경에서도 초기화가 단 한 번만 실행되도록 보장합니다.
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는 스레드 안전하며 더블 체크 잠금을 사용하는 뮤텍스보다 우아합니다.
context와 취소
16. context 패키지는 어디에 사용됩니까
context 패키지는 데드라인, 취소 시그널, 요청 단위 값을 호출 트리 전체에 걸쳐 관리합니다.
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 같은 시스템 시그널을 잡아 깨끗하게 종료할 수 있습니다.
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 파일에 위치합니다.
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로 실행합니다.
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은 타입 매개변수를 도입해 타입 안전성을 유지하면서도 제네릭 코드를 작성할 수 있게 만들었습니다.
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 파일이 모듈과 의존성을 정의합니다.
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# Updating dependencies
go get -u ./... # All dependencies
go get -u=patch ./... # Patches onlygo.sum 파일에는 의존성의 무결성을 보장하는 암호학적 체크섬이 들어 있습니다.
22. Go 프로젝트는 어떻게 구성해야 합니까
표준 구성은 엄격한 규칙을 강요하지 않고 커뮤니티 관행을 따릅니다.
myproject/
├── cmd/
│ └── api/
│ └── main.go # Entry point
├── internal/ # Private to module
│ ├── handler/
│ ├── service/
│ └── repository/
├── pkg/ # Reusable external code
├── go.mod
├── go.sum
└── README.mdinternal 폴더는 특별합니다. 그 내용은 다른 모듈이 import할 수 없습니다.
심화 질문
23. Go의 가비지 컬렉터는 어떻게 동작합니까
Go는 동시성을 활용한 트라이컬러 mark-and-sweep 가비지 컬렉터를 사용하며, 낮은 지연 시간에 최적화되어 있습니다.
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 스케줄러는 N개의 고루틴을 M개의 시스템 스레드에 매핑하는 M:N 모델을 사용하며, G(고루틴), M(스레드), P(논리 프로세서)라는 세 가지 엔터티로 구성됩니다.
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에서 성능을 어떻게 최적화합니까
최적화는 병목 구간을 찾기 위한 프로파일링부터 시작합니다.
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 면접에서 평가되는 기본 개념을 모두 다룹니다.
준비 체크리스트:
- ✅ 고루틴과 채널의 숙달
- ✅ 암묵적 인터페이스에 대한 이해
- ✅ Go다운 오류 처리
- ✅ context의 적절한 사용
- ✅ 동시성 패턴(뮤텍스, worker pool)
- ✅ 테스트와 벤치마크
- ✅ Go 1.18 이상의 제네릭에 대한 지식
Go 면접에서 성공의 열쇠는 단순함과 성능 사이의 트레이드오프를 이해하고, 어떤 동시성 패턴을 언제 적용해야 하는지 분명히 아는 것입니다.
연습을 시작하세요!
면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.
태그
공유
관련 기사

Go 기술 면접: Goroutine, Channel, 동시성 패턴 완벽 가이드
Go 기술 면접에서 자주 출제되는 goroutine, channel, 동시성 관련 질문을 다룹니다. 프로덕션 수준의 코드 예제와 각 답변의 설계 근거를 2026년 면접 대비용으로 상세히 설명합니다.

Go 동시성: 고루틴과 채널 - 완벽 가이드
고루틴과 채널을 사용해 Go 동시성을 마스터하세요. 고급 패턴, 동기화, select 문, 모범 사례를 자세한 코드 예제와 함께 다룹니다.

Go: Java/Python 개발자를 위한 기초 2026
Java나 Python 경험을 활용하여 Go를 빠르게 배우세요. 고루틴, 채널, 인터페이스 및 원활한 전환을 위한 핵심 패턴을 설명합니다.