백엔드 개발자 (Go) 면접 질문: 완벽 가이드

Milad Bonakdar
작성자
동시성, 인터페이스, 오류 처리 및 시스템 설계 등 필수 면접 질문으로 Go 백엔드 개발을 마스터하세요. Go 개발자 면접을 위한 완벽한 준비입니다.
소개
Go (Golang)는 확장 가능한 백엔드 시스템, 마이크로서비스 및 클라우드 네이티브 애플리케이션을 구축하는 데 있어 주요 언어로 자리 잡았습니다. Go의 단순성, 강력한 동시성 모델 및 성능은 현대 엔지니어링 팀에게 최고의 선택이 되게 합니다.
이 가이드는 Go를 전문으로 하는 백엔드 개발자를 위한 필수 면접 질문을 다룹니다. 핵심 언어 개념, 동시성 패턴, 오류 처리 및 시스템 설계를 탐구하여 다음 면접에서 성공할 수 있도록 돕습니다.
핵심 Go 개념
1. Go는 Java 또는 Python과 같은 다른 언어와 어떤 점에서 다른가요?
답변: Go는 Google에서 대규모 소프트웨어 개발의 과제를 해결하기 위해 설계되었습니다. 주요 차이점은 다음과 같습니다.
- 단순성: Go는 작은 키워드 세트를 가지고 있으며 상속이나 메서드 오버로딩과 같은 복잡한 기능이 없어 가독성을 우선시합니다.
- 동시성: Goroutine 및 Channel을 통한 동시성에 대한 기본 지원을 통해 확장 가능한 동시성 프로그램을 더 쉽게 작성할 수 있습니다.
- 컴파일: Go는 가상 머신(JVM) 없이 빠른 시작 및 실행 속도를 제공하는 머신 코드(정적으로 연결된 바이너리)로 직접 컴파일됩니다.
- 가비지 컬렉션: 낮은 지연 시간에 최적화된 효율적인 가비지 컬렉션.
빈도: 일반적 난이도: 쉬움
2. 배열과 슬라이스의 차이점을 설명하세요.
답변:
- 배열: 동일한 유형의 요소로 구성된 고정 크기 시퀀스입니다. 크기는 유형의 일부입니다(예:
[5]int는[10]int와 다름). 배열은 값 유형입니다. 배열을 다른 배열에 할당하면 모든 요소가 복사됩니다. - 슬라이스: 기본 배열에 대한 동적이고 유연한 뷰입니다. 배열에 대한 포인터, 길이 및 용량으로 구성됩니다. 슬라이스는 참조와 같습니다. 슬라이스를 함수에 전달하면 전체 데이터를 복사하지 않고도 기본 요소를 수정할 수 있습니다.
빈도: 일반적 난이도: 쉬움
3. Go에서 인터페이스는 어떻게 작동하나요? 암시적 구현이란 무엇인가요?
답변: Go의 인터페이스는 메서드 시그니처의 모음입니다.
- 암시적 구현: Java 또는 C#과 달리 유형은 인터페이스를 구현한다고 명시적으로 선언하지 않습니다(
implements키워드 없음). 유형이 인터페이스에 선언된 모든 메서드를 정의하면 자동으로 해당 인터페이스를 구현합니다. - 덕 타이핑: "오리처럼 걷고 오리처럼 꽥꽥거린다면 오리다." 이는 정의와 구현을 분리하여 코드를 더 유연하게 만들고 테스트를 위해 더 쉽게 모의할 수 있도록 합니다.
빈도: 일반적 난이도: 중간
4. defer 키워드는 무엇이며 어떻게 작동하나요?
답변:
defer는 함수가 반환되기 직전에 실행되도록 함수 호출을 예약합니다. 파일 닫기, 뮤텍스 잠금 해제 또는 데이터베이스 연결 닫기와 같은 리소스 정리에 일반적으로 사용됩니다.
- LIFO 순서: 지연된 호출은 Last-In-First-Out 순서로 실행됩니다.
- 인수 평가: 지연된 함수에 대한 인수는 호출이 실행될 때가 아니라
defer문이 실행될 때 평가됩니다.
예시:
빈도: 일반적 난이도: 쉬움
동시성
5. Goroutine을 설명하고 OS 스레드와 어떻게 다른지 설명하세요.
답변:
- Goroutine: Go 런타임에서 관리하는 경량 스레드입니다. 동적으로 확장 및 축소되는 작은 스택(예: 2KB)으로 시작합니다. 수천 개의 Goroutine이 단일 OS 스레드에서 실행될 수 있습니다.
- OS 스레드: 커널에서 관리하며 고정된 큰 스택(예: 1MB)을 가지고 있으며 컨텍스트 전환이 비쌉니다.
- M:N 스케줄링: Go 런타임은 M개의 Goroutine을 N개의 OS 스레드로 멀티플렉싱하여 사용자 공간에서 효율적으로 스케줄링을 처리합니다.
빈도: 매우 일반적 난이도: 중간
6. 채널이란 무엇인가요? 버퍼링됨 vs. 버퍼링되지 않음?
답변: 채널은 Goroutine이 실행을 통신하고 동기화할 수 있도록 하는 유형화된 파이프입니다.
- 버퍼링되지 않은 채널: 용량이 없습니다. 송신 작업은 수신자가 준비될 때까지 차단되고 그 반대도 마찬가지입니다. 강력한 동기화를 제공합니다.
- 버퍼링된 채널: 용량이 있습니다. 송신 작업은 버퍼가 가득 찬 경우에만 차단됩니다. 수신 작업은 버퍼가 비어 있는 경우에만 차단됩니다. 송신자와 수신자를 어느 정도 분리합니다.
빈도: 일반적 난이도: 중간
7. Go에서 경쟁 조건을 어떻게 처리하나요?
답변: 경쟁 조건은 여러 Goroutine이 동시에 공유 메모리에 액세스하고 적어도 하나의 액세스가 쓰기일 때 발생합니다.
- 감지: 내장된 경쟁 감지기를 사용합니다:
go run -race또는go test -race. - 예방:
- 채널: "메모리를 공유하여 통신하지 말고, 통신하여 메모리를 공유하십시오."
- Sync 패키지:
sync.Mutex또는sync.RWMutex를 사용하여 중요한 섹션을 잠급니다. - 원자적 작업: 간단한 카운터 또는 플래그에는
sync/atomic을 사용합니다.
빈도: 일반적 난이도: 어려움
8. select 문은 무엇에 사용되나요?
답변:
select 문을 사용하면 Goroutine이 여러 통신 작업을 기다릴 수 있습니다. 사례 중 하나가 실행될 수 있을 때까지 차단한 다음 해당 사례를 실행합니다. 여러 사례가 준비되면 하나를 무작위로 선택합니다.
- 시간 초과:
time.After를 사용하여 구현할 수 있습니다. - 비차단 작업:
default사례는 다른 사례가 준비되지 않은 경우 select를 비차단으로 만듭니다.
예시:
빈도: 중간 난이도: 중간
오류 처리 및 견고성
9. Go에서 오류 처리는 어떻게 작동하나요?
답변:
Go는 오류를 값으로 취급합니다. 함수는 예외를 throw하는 대신 error 유형(일반적으로 마지막 반환 값으로)을 반환합니다.
- 오류 확인: 호출자는 오류가
nil인지 명시적으로 확인해야 합니다. - 사용자 지정 오류:
error인터페이스(Error() string메서드가 하나 있음)를 구현하여 사용자 지정 오류 유형을 만들 수 있습니다. - 래핑: Go 1.13은 원래 오류를 검사할 수 있도록 유지하면서 컨텍스트를 추가하기 위해 오류 래핑(
fmt.Errorf("%w", err))을 도입했습니다(errors.Is및errors.As사용).
빈도: 일반적 난이도: 쉬움
10. Panic과 Recover는 무엇인가요? 언제 사용해야 하나요?
답변:
- Panic: 일반적인 제어 흐름을 중단하고 패닉을 시작합니다. 예외와 유사하지만 복구할 수 없는 오류(예: nil 포인터 역참조, 인덱스 범위를 벗어남)에 예약해야 합니다.
- Recover: 패닉 상태인 Goroutine의 제어를 다시 얻는 내장 함수입니다.
defer함수 내에서만 유용합니다. - 사용법: 일반적으로 정상적인 제어 흐름에는 권장되지 않습니다. 예상되는 오류 조건에는
error값을 사용합니다. Panic/recover는 주로 예외적인 상황이나 라이브러리/프레임워크 내에서 충돌로 인해 전체 서버가 다운되는 것을 방지하는 데 사용됩니다.
빈도: 중간 난이도: 중간
시스템 설계 및 백엔드
11. Go 웹 애플리케이션을 어떻게 구성하겠습니까?
답변: Go는 구조를 강제하지 않지만 일반적인 표준은 "표준 Go 프로젝트 레이아웃"입니다.
/cmd: 기본 애플리케이션(진입점)./pkg: 외부 애플리케이션에서 사용해도 괜찮은 라이브러리 코드./internal: 개인 애플리케이션 및 라이브러리 코드(Go 컴파일러에 의해 적용됨)./api: OpenAPI/Swagger 사양, 프로토콜 정의./configs: 구성 파일.- 클린 아키텍처: 애플리케이션을 테스트 가능하고 유지 관리 가능하게 만들기 위해 우려 사항을 계층(Delivery/Handler, Usecase/Service, Repository/Data)으로 분리합니다.
빈도: 일반적 난이도: 중간
12. context 패키지는 어떻게 작동하며 왜 중요한가요?
답변:
context 패키지는 API 경계 및 Goroutine에서 요청 범위 값, 취소 신호 및 마감일을 관리하는 데 필수적입니다.
- 취소: 사용자가 요청을 취소하면 컨텍스트가 취소되고 해당 요청에 대한 작업을 수행하는 모든 생성된 Goroutine은 리소스를 절약하기 위해 중지해야 합니다.
- 시간 초과:
context.WithTimeout은 데이터베이스 쿼리 또는 외부 API 호출이 영원히 중단되지 않도록 합니다. - 값: 사용자 ID 또는 인증 토큰과 같은 요청별 데이터를 전달할 수 있습니다(드물게 사용).
빈도: 매우 일반적 난이도: 어려움
13. 의존성 주입이란 무엇이며 Go에서 어떻게 수행되나요?
답변: 의존성 주입(DI)은 객체가 의존하는 다른 객체를 수신하는 디자인 패턴입니다.
- Go에서: 일반적으로 인터페이스를 통해 데이터베이스 연결 또는 로거와 같은 종속성을 구조체의 생성자 또는 팩토리 함수에 전달하여 구현됩니다.
- 이점: 코드를 더 모듈화하고 테스트 가능하게 만듭니다(실제 DB를 모의 DB로 쉽게 교체할 수 있음).
- 프레임워크: 수동 DI가 단순성을 위해 선호되지만 복잡한 그래프에는
google/wire또는uber-go/dig와 같은 라이브러리가 있습니다.
빈도: 중간 난이도: 중간
데이터베이스 및 도구
14. Go에서 JSON을 어떻게 처리하나요?
답변:
Go는 encoding/json 패키지를 사용합니다.
- 구조체 태그:
`json:"field_name"`와 같은 태그를 사용하여 구조체 필드를 JSON 키에 매핑합니다. - Marshal: Go 구조체를 JSON 문자열(바이트 슬라이스)로 변환합니다.
- Unmarshal: JSON 데이터를 Go 구조체로 구문 분석합니다.
- 스트리밍:
json.Decoder와json.Encoder는 데이터 스트림을 처리하므로 큰 페이로드에 더 좋습니다.
빈도: 일반적 난이도: 쉬움
15. 일반적으로 사용하는 Go 도구는 무엇인가요?
답변:
go mod: 의존성 관리.go fmt: 코드를 표준 스타일로 포맷합니다.go vet: 의심스러운 구조에 대한 코드를 검사합니다.go test: 테스트 및 벤치마크를 실행합니다.pprof: CPU 및 메모리 사용량을 분석하기 위한 프로파일링 도구입니다.delve: Go용 디버거입니다.
빈도: 일반적 난이도: 쉬움
고급 주제 및 모범 사례
16. Go의 제네릭이란 무엇이며 언제 사용해야 하나요?
답변: 제네릭(Go 1.18에 도입됨)을 사용하면 특정 유형이 아닌 유형 집합에서 작동하는 함수와 데이터 구조를 작성할 수 있습니다.
- 유형 매개변수: 대괄호
[]를 사용하여 정의됩니다. 예:func Map[K comparable, V any](m map[K]V) ... - 제약 조건: 허용 가능한 유형 집합을 정의하는 인터페이스(예:
any,comparable). - 사용법: 여러 유형에 적용되는 알고리즘(예: 정렬, 필터링 또는 Set/Tree와 같은 데이터 구조)에 대한 코드 중복을 줄이는 데 사용합니다. 과도한 사용을 피하십시오. 인터페이스로 충분하다면 인터페이스를 사용하십시오.
빈도: 일반적 난이도: 중간
17. Go에서 테이블 기반 테스트를 설명하세요.
답변: 테이블 기반 테스트는 테스트 케이스가 구조체 슬라이스("테이블")로 정의되는 Go에서 선호되는 패턴입니다. 각 구조체에는 입력 인수와 예상 출력이 포함되어 있습니다.
- 이점:
- 테스트 논리와 테스트 데이터의 깔끔한 분리.
- 새로운 테스트 케이스를 쉽게 추가할 수 있습니다(테이블에 행을 추가하기만 하면 됨).
- 정확히 어떤 입력이 실패했는지 보여주는 명확한 실패 메시지.
- 예시:
빈도: 일반적 난이도: 쉬움
18. Go HTTP 서버에서 미들웨어 패턴이란 무엇인가요?
답변:
미들웨어는 다음 핸들러에 제어를 전달하기 전에 사전 또는 사후 처리 논리를 수행하기 위해 http.Handler를 래핑하는 함수입니다.
- 서명:
func(next http.Handler) http.Handler - 사용 사례: 로깅, 인증, 패닉 복구, 속도 제한, CORS.
- 체이닝: 미들웨어를 함께 체이닝할 수 있습니다(예:
Log(Auth(Handler))).
예시:
빈도: 매우 일반적 난이도: 중간
19. Go 서버에서 Graceful Shutdown을 어떻게 구현하나요?
답변: Graceful Shutdown은 서버가 새 요청 수락을 중지하지만 종료하기 전에 활성 요청 처리를 완료하도록 보장합니다.
- 메커니즘:
os/signal을 사용하여 OS 신호(SIGINT, SIGTERM)를 수신합니다.- 정리 기간(예: 5-10초)을 허용하기 위해
context.WithTimeout을 만듭니다. http.Server에서server.Shutdown(ctx)을 호출합니다.- DB 연결 및 기타 리소스를 닫습니다.
- 중요성: 배포 중 데이터 손실 및 클라이언트 오류를 방지합니다.
빈도: 일반적 난이도: 어려움
20. 일반 맵과 뮤텍스 대신 sync.Map을 언제 사용해야 하나요?
답변:
sync.Map은 표준 라이브러리의 동시적으로 안전한 맵 구현입니다.
- 사용 사례:
- 캐시 경합: 주어진 키에 대한 항목이 한 번만 작성되고 여러 번 읽혀질 때(예: 지연 로딩 캐시).
- 분리된 키: 여러 Goroutine이 분리된 키 집합에 대한 항목을 읽고, 쓰고, 덮어쓸 때.
- 트레이드오프: 일반적인 사용 사례(빈번한 읽기/쓰기 업데이트)의 경우
sync.RWMutex로 보호되는 일반map이 더 빠르고 더 나은 유형 안전성을 갖습니다(sync.Map은any를 사용하기 때문).
빈도: 드묾 난이도: 어려움



