-
go 언어 관련 인터뷰 질문들Interview 2025. 3. 24. 07:57728x90
✅ 기초 문법 및 언어 특성
Go 언어의 주요 특징은 무엇인가요?
Go는 단순하고 읽기 쉬운 문법을 갖고 있어서 협업과 유지보수에 유리한 언어입니다.
내장된 병행성 지원(Goroutine, Channel)이 굉장히 강력해서, 고성능 네트워크 서버나 마이크로서비스 구축에 적합하고요.
정적 컴파일 언어라 실행 속도가 빠르고, 단일 바이너리로 배포가 가능해 DevOps 측면에서도 효율적입니다.
또, 표준 라이브러리가 잘 구성돼 있어서 외부 의존성 없이도 많은 기능을 바로 구현할 수 있습니다.Go에서 defer 키워드의 동작 방식은?
defer는 현재 함수가 종료되기 직전에 실행할 코드를 등록할 때 사용합니다. 주로 파일 닫기, 뮤텍스 해제, 커넥션 종료 같은 리소스 정리 작업에 쓰입니다.
defer된 함수들은 LIFO (Last In, First Out) 순서로 실행되며, 함수가 리턴될 때 실행되기 때문에, 함수 로직 초반에 적어도 안전하게 정리 작업을 예약할 수 있는 점이 큰 장점입니다.
(추가질문)
defer 인자 평가 시점이 언제인가요?
defer에 넘기는 인자는 defer가 호출될 때 평가됩니다. 그래서 변경되는 변수의 값을 defer 시점에 캡처해두는 것도 가능하죠.
반복문 안에서 defer를 사용하면 무슨 문제가 생길 수 있나요?
루프마다 defer를 호출하면, 함수가 끝날 때까지 모든 호출이 쌓이기 때문에 메모리나 성능에 악영향을 줄 수 있습니다. 꼭 필요한 경우가 아니라면 루프 밖에서 처리하거나 별도로 리팩토링하는 게 좋습니다Go의 포인터 개념과 사용법에 대해 설명해주세요.
Go에서 포인터는
값의 메모리 주소를 참조할 수 있는 기능입니다.
기본적으로 Go는 값 전달(pass-by-value) 방식이기 때문에, 함수에 변수를 인자로 넘기면 복사된 값이 전달됩니다.
하지만 포인터를 사용하면 원본 데이터를 직접 수정하거나, 큰 구조체를 복사하지 않고 효율적으로 처리할 수 있습니다.
Go에서 포인터의 사용 예
함수에서 값 변경이 필요할 때
- 예: 슬라이스 요소를 직접 수정하거나 카운터 증가
큰 구조체를 복사하지 않고 전달할 때
- 성능 최적화: 메모리 절약 및 GC pressure 감소
리턴 값 외에 부수적인 값을 같이 반환하고 싶을 때
- 에러 외 다른 값 갱신 등
저는 실무에서 구조체 포인터를 많이 사용합니다.
예를 들어 사용자 데이터를 다룰 때 User 객체를 복사하지 않고
포인터로 넘겨서 필드 값을 직접 업데이트하거나, DB 작업 시 같은 객체를 재사용하는 식이죠.
단, 포인터를 너무 많이 사용하면 코드의 추적이 어려워질 수 있어서 가독성과 유지보수성을 고려해 선택하고 있습니다.Go에서 배열과 슬라이스(slice)의 차이점은 무엇인가요?
배열은 고정 크기의 값 타입이라 복사되고, 슬라이스는 가변 크기의 참조 타입이라 원본 데이터를 공유합니다.
실무에서는 거의 슬라이스만 쓰며, 성능과 유연성 측면에서 더 적합합니다.
코드 예제:
func modifyArray(arr [3]int) {
arr[0] = 100
}
func modifySlice(s []int) {
s[0] = 100
}
func main() {
a := [3]int{1, 2, 3}
s := []int{1, 2, 3}
modifyArray(a)
modifySlice(s)
fmt.Println(a) // [1 2 3] → 배열은 복사됨
fmt.Println(s) // [100 2 3] → 슬라이스는 참조됨
}Go의 구조체(struct)와 인터페이스(interface)의 차이는?
Go에서 구조체(struct)는 데이터와 상태를 표현하는 구체적인 타입, 인터페이스(interface)는 행동(behavior)의 추상화를 위한 타입입니다.
구조체는 실제 필드 값을 가지고 있으며, 메서드도 정의할 수 있습니다. 반면 인터페이스는 메서드 시그니처만 정의하고, 이를 만족하는 타입은 자동으로 인터페이스를 구현하게 됩니다 — 즉, **암묵적인 인터페이스 구현(implicit implementation)**이 특징이죠.
실무에서는 인터페이스를 통해 유연한 의존성 주입이나 테스트 목(mock) 구현, 플러그인 아키텍처 등을 구현하고, 구조체는 실제 기능을 담는 단위로 사용합니다.
코드 예:
type User struct {
Name string
}
func (u User) Greet() string {
return "Hello, " + u.Name
}
type Greeter interface {
Greet() string
}
func PrintGreeting(g Greeter) {
fmt.Println(g.Greet())
}
func main() {
u := User{Name: "Alice"}
PrintGreeting(u) // User가 Greet() 메서드를 구현하므로 Greeter 인터페이스 만족
}
✅ 병행성(Concurrency)
Go에서 병행성을 어떻게 구현하나요?
Go는 병행성을 경량 고루틴(goroutine)과 채널(channel)이라는 기본 개념으로 구현합니다.
goroutine은 매우 가벼운 스레드로, go 키워드 하나로 함수나 메서드를 동시에 실행할 수 있습니다. Go 런타임 스케줄러가 이들을 효율적으로 관리하죠.
channel은 고루틴 간에 데이터를 안전하게 주고받을 수 있게 해주는 구조로,
공유 메모리 없이 통신 기반 병행성(communicating sequential processes, CSP)을 지향합니다.
channel 예제 코드:
func worker(ch chan string) {
msg := <-ch
fmt.Println("received:", msg)
}
func main() {
ch := make(chan string)
go worker(ch)
ch <- "Hello, Channel"
}고루틴(goroutine)과 채널(channel)에 대해 설명해주세요.
Go에서 goroutine은 경량 스레드로, go 키워드를 붙여 함수나 메서드를 비동기적으로 실행할 수 있습니다. 운영체제 스레드보다 훨씬 가볍고, Go 런타임 스케줄러가 수천 개의 goroutine을 효율적으로 관리해줍니다.
channel은 고루틴 간 데이터를 안전하게 주고받을 수 있도록 설계된 통신 수단입니다.
공유 메모리에 락을 거는 대신, "통신으로 메모리를 공유하라"는 CSP(Communicating Sequential Processes) 철학을 따릅니다. goroutine과 channel을 함께 사용하면 병렬 작업을 수행하면서도 동기화가 필요한 부분은 안전하게 처리할 수 있습니다.
goroutine 누수 방지 (goroutine leak) 주의 channel 닫기 시점 (close(chan)) 정확히 관리 select 문을 통해 여러 채널을 동시에 다룰 수 있음 무한히 쌓이는 버퍼 채널 사용은 피해야 합니다.select 구문의 사용 사례는?
select는 여러 채널 중 먼저 준비된 하나를 선택해서 처리할 수 있게 해주는 구문입니다.
주로 다중 채널 처리, 타임아웃, 취소 감지, graceful shutdown 등에 사용하며,
병행 처리를 안정적으로 구성할 수 있는 핵심 도구라고 생각합니다.sync.WaitGroup은 언제, 어떻게 사용하나요?
sync.WaitGroup은 여러 고루틴의 작업이 모두 끝날 때까지 기다리기 위한 동기화 도구입니다. 예를 들어, 여러 개의 병렬 작업을 수행하고 그 결과를 모아야 하는 경우, 고루틴마다 WaitGroup.Add(1)로 작업 수를 등록하고, 고루틴 내부에서 Done()을 호출하며, 최종적으로 Wait()를 호출해 모든 작업이 끝날 때까지 블로킹할 수 있습니다.
mutex와 channel을 사용할 때 각각의 장단점은?
Mutex 와 Channel은 모두 Go에서 병행성 문제를 다루는 도구지만,
철학과 사용 목적이 다릅니다
Mutex는 공유 자원 보호를 위한 전통적인 락 기반 동기화 방식이고,
Channel은 고루틴 간에 데이터를 직접 주고받는 통신 기반 방식입니다.
Mutex는 공유 자원 보호에 적합하고, 성능이 뛰어나지만 데드락에 주의해야 합니다.
Channel은 고루틴 간 통신에 적합하고 코드 흐름이 명확해지는 장점이 있지만, 과하게 쓰면 성능 이슈가 생길 수 있습니다.
상황에 따라 적절하게 선택하는 것이 중요합니다.
✅ 에러 처리
Go에서 에러는 어떻게 처리하나요?
Go에서는 전통적인 try-catch 방식이 아닌, 명시적으로 error 값을 반환하고 검사하는 방식으로 에러를 처리합니다. 함수에서 에러가 발생할 수 있으면 반드시 error 타입을 리턴하고, 호출하는 쪽에서 if err != nil로 명시적으로 처리해야 합니다.
이런 방식은 명확한 에러 흐름을 만들고, 예외 상황을 의식적으로 다루도록 유도하는 Go의 철학을 반영하고 있다고 생각합니다.
코드 예제:
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a / b, nil
}
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)errors.New()와 fmt.Errorf()의 차이점은?
핵심 차이점
항목 errors.New() fmt.Errorf() 용도 고정된 에러 메시지 생성 포맷 포함 메시지, 에러 wrapping 포맷 지원 ❌ 없음 ✅ Printf 스타일 포맷 지원 에러 체인 지원 (%w) ❌ 불가 ✅ errors.Is, errors.As로 탐색 가능 err := errors.New("something went wrong") name := "DB" err := fmt.Errorf("failed to connect to %s", name) // 문자열을 동적으로 구성할 수 있음 // %w를 사용하면 기존 에러를 감싸서 원인 추적 가능 // errors.Is() 또는 errors.As()로 에러 체인을 따라가며 검사할 수 있음 baseErr := errors.New("connection refused") err := fmt.Errorf("database error: %w", baseErr) if errors.Is(err, baseErr) { fmt.Println("matched base error") }Go 1.13 이후 도입된 error wrapping 기능에 대해 설명해주세요.
Go 1.13에서는 fmt.Errorf() 에서 %w 포맷을 사용해 기존 에러를 wrapping
할 수 있는 기능이 도입됐습니다.
이를 통해 에러의 컨텍스트를 추가하면서도 원래의 에러 정보를 보존할 수 있고,
errors.Is()와 errors.As()함수를 통해 에러 체인을 따라가며 원인을 비교하거나 특정 타입으로 변환
할 수 있게 됐습니다.
✅ 메모리 및 성능
make()와 new() 함수의 차이점은?
new()와 make()는 모두 Go에서 메모리를 할당하는 함수지만, 용도와 동작 방식이 다릅니다.
new(T)는 타입 T에 대해 zero value로 초기화된 메모리를 할당하고, 해당 타입의 포인터를 반환합니다. make()는 슬라이스, 맵, 채널과 같은 내장 참조 타입(builtin reference types)의 초기화된 값을 생성합니다. 즉, make()는 포인터가 아닌 초기화된 값을 리턴하며, 이 타입들에 대해서만 사용할 수 있습니다.항목 new(T) make(T, ...) 사용 대상 모든 타입 슬라이스, 맵, 채널만 가능 반환값 *T (포인터) T (초기화된 값) 초기화 수준 zero value로만 초기화 내부 구조까지 초기화 (길이, 용량 등) 예시 용도 단순 구조체, 정수 포인터 등 생성 슬라이스/맵/채널을 실제로 사용 가능하게 만듦 Go에서 GC(Garbage Collector)는 어떻게 동작하나요?
Go의 GC는 스톱 더 월드(STW)를 최소화한, 병행(Concurrent) 마크-스위프(Mark-and-Sweep) 방식의 가비지 컬렉터입니다. 주 목적은 짧은 지연 시간(Low Latency)을 유지하면서도, 메모리를 효율적으로 회수하는 것입니다. GC는 크게 mark → sweep → sweep termination 단계를 거치며, 대부분의 단계는 애플리케이션과 동시에(concurrently) 실행됩니다. 단, mark phase의 시작과 종료 시점에만 짧은 STW(Stop The World)가 발생합니다.
escape analysis란 무엇인가요? Stack과 Heap 할당의 차이를 예로 설명해보세요.
Escape Analysis는 Go 컴파일러가 변수나 객체가 함수 스코프를 벗어나는지(escape)를 분석해서, 해당 값을 스택에 할당할지, 힙에 할당할지를 결정하는 과정입니다. 기본적으로 Go는 가능하면 스택에 할당하려고 시도하지만, 어떤 값이 함수 밖에서도 참조될 수 있는 경우, 컴파일러는 해당 값을 Heap에 할당합니다. 이 과정은 성능과 GC(가비지 컬렉션) 비용에 큰 영향을 주기 때문에, 실무에서 중요한 최적화 요소입니다.
✅ 패키지 및 모듈 시스템
- Go module(go.mod)의 역할은 무엇인가요?
- Go에서 third-party 라이브러리를 관리할 때 주의할 점은?
- 여러 Go 패키지를 관리할 때 workspace나 monorepo 형태에 대한 경험이 있으신가요?
✅ 테스트
- Go에서 유닛 테스트는 어떻게 작성하나요?
- go test 명령어에서 자주 사용하는 플래그는?
- Table-driven 테스트란 무엇인가요?
✅ 실무 상황 기반 질문
- Go로 작성된 웹 서버를 만든 경험이 있으신가요? 어떤 프레임워크를 사용했나요?
- Go 언어를 선택한 이유는 무엇이었나요?
- 병목 현상을 디버깅하거나 성능을 최적화한 경험이 있나요?
✅ 심화/시니어 수준
- Go 인터페이스는 "암묵적 구현(implicit implementation)"을 사용합니다. 이 디자인의 장단점은?
- 빈 인터페이스(interface{})는 언제, 왜 사용하는지 설명해주세요.
- reflect 패키지는 어떤 경우에 사용하며, 주의할 점은 무엇인가요?
- Go에서 제네릭(generics)이 도입된 이유와 활용 사례는?
✅ Go 경력 개발자 인터뷰 질문
🔹 언어 특성과 철학
- Go 언어를 선택한 이유는 무엇인가요?
→ 다른 언어들과 비교해서 Go의 어떤 점이 실무에 도움이 된다고 느끼셨나요? - Go는 OOP를 명시적으로 지원하지 않습니다. 이런 설계 철학에 대해 어떻게 생각하시나요?
- 인터페이스는 Go에서 암묵적으로 구현되는데, 이 접근법이 프로젝트에 어떤 영향을 미쳤나요?
🔹 병행성 (Concurrency)
- Goroutine을 사용하면서 발생했던 문제점과 해결 방법을 공유해주실 수 있나요?
- Channel, WaitGroup, Mutex를 각각 어떤 상황에서 사용하셨나요?
- select 구문과 channel close를 조합해서 timeout이나 graceful shutdown을 구현한 경험이 있나요?
🔹 메모리 / 성능 최적화
- Go에서 성능 이슈가 발생했을 때 어떤 도구나 기법으로 진단하고 해결하셨나요?
→ 예: pprof, trace, go tool, custom logging 등 - Escape Analysis에 대해 알고 계신가요? stack vs heap 할당을 줄이기 위한 경험이 있으신가요?
- GC 튜닝이나 메모리 사용량 줄이기 위해 시도해본 작업이 있다면 설명해주세요.
🔹 실무/아키텍처 관련
- Go로 작성한 백엔드 서비스에서 구조를 어떻게 나누고 패키지를 관리하셨나요?
- Go 모듈과 버전 관리를 팀원들과 협업하는 상황에서 어떻게 처리하셨나요?
- REST API 또는 gRPC 서버를 Go로 구현하신 경험이 있으신가요? 인증/권한 관리는 어떻게 하셨나요?
- Go 기반 시스템에서 마이크로서비스 간 통신 문제를 경험한 적 있으신가요? 어떻게 대응하셨나요?
🔹 테스트 및 CI/CD
- Go에서의 테스트 전략은 어떻게 구성하셨나요?
→ 유닛 테스트 / 통합 테스트 / mock 처리 등 - Table-driven 테스트를 직접 작성해보신 경험이 있으신가요?
- Go 프로젝트에서 CI 파이프라인 구성 경험이 있으신가요? 어떤 도구를 사용하셨나요?
🔹 심화 주제
- Go에서 제네릭을 사용해본 경험이 있으신가요? 어떤 상황에서 유용했나요?
- Reflect 패키지를 사용한 사례가 있다면 설명해주세요. 사용 시 주의할 점은 무엇인가요?
- interface{}를 사용하는 코드를 타입 세이프하게 바꾸기 위해 어떤 방식으로 개선하셨나요?
💬 Follow-up 가능한 상황 질문 예시
- “그 상황에서 Go 말고 다른 언어를 썼다면 어떤 차이가 있었을까요?”
- “이 문제를 해결하면서 팀원들과 어떤 논의가 있었나요?”
- “해결 이후 어떤 개선이나 교훈이 있었나요?”
728x90'Interview' 카테고리의 다른 글
쉽게 설명하는 Forward Index vs Inverted Index (0) 2025.03.25 Elasticsearch/OpenSearch 관련 인터뷰 질문 (0) 2025.03.25 concurrency(동시성)와 parallelism(병렬성)의 차이 (0) 2025.03.24 Error Budget의 개념과 소진 시 대응 방안 (0) 2025.03.18 MTTR(Mean Time to Recovery) 단축 방법 (0) 2025.03.18