2026년 Go 인터페이스 심화: 합성, 타입 어설션, 면접 대비 완벽 가이드
Go 1.26의 자기참조 제네릭을 포함한 인터페이스 합성과 타입 어설션을 심층 분석합니다. 실전 코드 예제와 기술 면접 질문까지 모두 다룹니다.

Go의 인터페이스는 구현을 규정하지 않고 동작을 정의하여 유연하고 테스트하기 쉬운 코드의 핵심이 됩니다. Go 1.26에서는 자기참조 제네릭, 새로운 리플렉션 이터레이터, errors.AsType을 통한 타입 안전 에러 처리가 추가되었습니다. 이 글에서는 인터페이스 합성, 타입 어설션, 임베딩 패턴, 그리고 기술 면접에서 자주 나오는 질문들을 다룹니다.
제네릭 타입이 이제 타입 파라미터 목록에서 자기 자신을 참조할 수 있습니다: type Adder[A Adder[A]] interface { Add(A) A }. 이 F-bounded 다형성 패턴을 통해 반환 타입을 구현 타입 자체로 제약하는 인터페이스가 가능해졌습니다.
인터페이스 합성과 임베딩 패턴
인터페이스 임베딩은 여러 인터페이스를 단일 계약으로 결합합니다. 결과적으로 임베딩된 모든 인터페이스의 메서드를 요구하는 새로운 인터페이스가 생성됩니다. 이 패턴은 중복을 피하고 명확한 추상화 경계를 만듭니다.
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// Composed interface embedding three interfaces
type ReadWriteCloser interface {
Reader
Writer
Closer
}Read, Write, Close 메서드를 구현하는 모든 타입은 자동으로 ReadWriteCloser를 만족합니다. Go 표준 라이브러리의 io 패키지에서는 이 패턴이 광범위하게 사용됩니다.
면접에서 자주 나오는 질문 중 하나는 "왜 Go는 작고 집중된 인터페이스를 선호하는가"입니다. 답은 합성 가능성에 있습니다: io.Reader는 단 하나의 메서드만 요구하기 때문에 수백 개의 함수에서 사용됩니다. 큰 인터페이스는 결합도를 높이고 재사용성을 낮춥니다.
타입 어설션: 문법과 안전성
타입 어설션은 인터페이스 값에서 구체적인 타입을 추출합니다. 2값 형식을 사용하면 성공 여부를 나타내는 불리언을 반환하여 패닉을 방지합니다.
func processValue(v interface{}) {
// Two-value assertion: safe, no panic
if str, ok := v.(string); ok {
fmt.Printf("String value: %s\n", str)
return
}
// Type switch for multiple types
switch val := v.(type) {
case int:
fmt.Printf("Integer: %d\n", val)
case float64:
fmt.Printf("Float: %.2f\n", val)
case []byte:
fmt.Printf("Bytes: %x\n", val)
default:
fmt.Printf("Unknown type: %T\n", val)
}
}타입 스위치는 여러 가능한 타입을 깔끔하게 처리합니다. 각 케이스에서 val은 해당 블록 내에서 어설션된 타입에 바인딩되므로 별도의 어설션이 필요하지 않습니다.
str := v.(string)과 같은 단일 값 어설션은 어설션이 실패하면 패닉을 발생시킵니다. 프로덕션 코드에서는 항상 2값 형식이나 타입 스위치를 사용해야 합니다.
면접관들은 종종 타입 어설션의 성능에 대해 질문합니다. 런타임은 타입 디스크립터의 단일 비교를 수행하므로 어설션은 비용이 낮습니다. 큰 구조체에 대한 포인터를 래핑하는 인터페이스 값에서는 간접 참조로 인해 비용이 증가하지만, 어설션 자체는 O(1)을 유지합니다.
Go 1.26의 자기참조 제네릭
Go 1.26에서는 자기참조 타입 파라미터가 도입되어 메서드가 구현 타입을 반환해야 하는 인터페이스가 가능해졌습니다. F-bounded 다형성이라고도 불리는 이 패턴은 오랜 제한을 해결합니다.
// Self-referential interface: methods return the same type
type Builder[B Builder[B]] interface {
WithName(name string) B
WithAge(age int) B
Build() string
}
type PersonBuilder struct {
name string
age int
}
func (p PersonBuilder) WithName(name string) PersonBuilder {
p.name = name
return p
}
func (p PersonBuilder) WithAge(age int) PersonBuilder {
p.age = age
return p
}
func (p PersonBuilder) Build() string {
return fmt.Sprintf("%s, %d years old", p.name, p.age)
}
// Generic function using the self-referential constraint
func configure[B Builder[B]](b B, name string, age int) string {
return b.WithName(name).WithAge(age).Build()
}제약 조건 Builder[B]는 WithName과 WithAge가 제네릭 Builder가 아닌 B를 반환하도록 보장합니다. 이것이 없으면 반환 타입은 인터페이스가 되어 구체적인 타입 정보가 손실되고 메서드 체이닝이 깨집니다.
수학 타입도 이 패턴의 혜택을 받습니다. Addable[A Addable[A]] 인터페이스는 Add(A) A가 동일한 숫자 타입을 반환하도록 보장하여 BigInt와 Decimal 값의 실수로 인한 혼합을 방지합니다.
errors.AsType: 타입 안전 에러 언래핑
Go 1.26에서는 errors.AsType이 추가되어 errors.As의 포인터 기반 패턴을 대체했습니다. 제네릭 버전은 언래핑된 에러를 직접 반환합니다.
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
func handleError(err error) {
// Go 1.26: generic type-safe unwrapping
if valErr, ok := errors.AsType[*ValidationError](err); ok {
log.Printf("Validation error on field %s\n", valErr.Field)
return
}
// Before Go 1.26: pointer-based unwrapping
// var valErr *ValidationError
// if errors.As(err, &valErr) { ... }
log.Printf("Unexpected error: %v\n", err)
}새 API는 별도의 변수 선언을 제거하고 함수 호출에서 대상 타입을 명시적으로 만듭니다. 에러 체인은 이전과 동일한 방식으로 검색됩니다. 변경된 것은 인터페이스뿐입니다.
Go 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
인터페이스 검사를 위한 리플렉션 이터레이터
Go 1.26에서는 reflect 패키지에 이터레이터 메서드가 추가되었습니다. Type.Methods()와 Value.Methods()는 메서드를 순회하기 위한 이터레이터를 반환하며, 인덱스 기반 루프를 대체합니다.
import "reflect"
func inspectInterface(v interface{}) {
t := reflect.TypeOf(v)
// Go 1.26: iterator-based method inspection
fmt.Printf("Type %s methods:\n", t.Name())
for method := range t.Methods() {
fmt.Printf(" %s: %s\n", method.Name, method.Type)
}
// For struct fields (also new in 1.26)
if t.Kind() == reflect.Struct {
for field := range t.Fields() {
fmt.Printf(" Field: %s (%s)\n", field.Name, field.Type)
}
}
}이터레이터 패턴은 Go 1.23의 range-over-function 기능과 일치합니다. 코드는 더 읽기 쉬워지고 성능 페널티는 없습니다: 이터레이터는 지연 평가로 값을 생성합니다.
컴파일 타임 인터페이스 만족 검사
Go는 구체적인 값을 인터페이스 변수에 할당할 때 컴파일 타임에 인터페이스 만족을 검사합니다. 빈 식별자 할당을 사용한 명시적 검사로 대규모 코드베이스에서 에러를 조기에 발견할 수 있습니다.
type Storage interface {
Save(key string, data []byte) error
Load(key string) ([]byte, error)
Delete(key string) error
}
type FileStorage struct {
basePath string
}
// Compile-time check: fails if FileStorage misses a method
var _ Storage = (*FileStorage)(nil)
func (f *FileStorage) Save(key string, data []byte) error {
path := filepath.Join(f.basePath, key)
return os.WriteFile(path, data, 0644)
}
func (f *FileStorage) Load(key string) ([]byte, error) {
path := filepath.Join(f.basePath, key)
return os.ReadFile(path)
}
func (f *FileStorage) Delete(key string) error {
return os.Remove(filepath.Join(f.basePath, key))
}var _ Storage = (*FileStorage)(nil) 라인은 *FileStorage가 Storage를 만족할 때만 컴파일됩니다. 이 패턴은 값이 할당되는 런타임이 아니라 누락된 메서드를 즉시 감지합니다.
빈 인터페이스와 타입 제약
빈 인터페이스 interface{}는 모든 값을 받아들이며, any는 Go 1.18 이후 그 별칭입니다. 제네릭 제약은 런타임 어설션 없이 컴파일 타임 타입 안전성을 제공합니다.
import "golang.org/x/exp/constraints"
// Generic function with numeric constraint
func Sum[T constraints.Integer | constraints.Float](values []T) T {
var total T
for _, v := range values {
total += v
}
return total
}
// Comparable constraint for map keys
func Contains[K comparable, V any](m map[K]V, key K) bool {
_, exists := m[key]
return exists
}interface{} 대신 제네릭을 선호하면 타입 어설션이 불필요해지고 컴파일 타임에 타입 불일치를 감지할 수 있습니다. 트레이드오프는 함수 시그니처의 복잡성 증가이므로, 제네릭은 함수가 실제로 여러 타입을 처리할 때 가장 적합합니다.
Go 인터페이스에 관한 기술 면접 질문
면접관들은 여러 수준에서 인터페이스 지식을 테스트합니다. 다음은 일반적인 질문과 간결한 답변입니다.
Q: nil 인터페이스 값에서 메서드를 호출하는 것과 인터페이스 내부의 nil 구체 값에서 메서드를 호출하는 것은 어떻게 다릅니까?
nil 인터페이스는 타입도 값도 없습니다. 어떤 메서드를 호출해도 패닉이 발생합니다. nil 포인터를 보유한 비-nil 인터페이스는 타입이 있으므로 메서드는 nil 리시버로 실행됩니다. 이 동작 덕분에 (*bytes.Buffer)(nil).String()이 빈 문자열을 반환하는 패턴이 가능합니다.
Q: 인터페이스 비교는 어떻게 작동합니까?
두 인터페이스 값은 동일한 동적 타입과 동일한 동적 값을 가질 때 같습니다. 비교 불가능한 타입(슬라이스, 맵, 함수)을 가진 인터페이스를 비교하면 런타임에 패닉이 발생합니다.
Q: 메서드는 포인터 리시버와 값 리시버 중 언제 어떤 것을 사용해야 합니까?
포인터 리시버는 변경을 허용하고 큰 구조체의 복사를 피합니다. 값 리시버는 동시 사용에 안전하며 값과 포인터 모두에서 작동합니다. 어떤 메서드가 포인터 리시버를 필요로 하면 일관성을 위해 해당 타입의 모든 메서드가 포인터 리시버를 사용해야 합니다. 포인터 리시버 메서드를 요구하는 인터페이스를 만족하는 것은 *T뿐이기 때문입니다.
type Counter struct {
count int
}
// Pointer receiver: modifies the struct
func (c *Counter) Increment() {
c.count++
}
// Value receiver: read-only operation
func (c Counter) Value() int {
return c.count
}
type Incrementer interface {
Increment()
}
func main() {
var c Counter
// var i Incrementer = c // Compile error: Counter lacks Increment
var i Incrementer = &c // OK: *Counter has Increment
i.Increment()
}Q: "인터페이스를 받고, 구조체를 반환하라" 원칙을 설명해 주세요.
인터페이스를 받는 함수는 구체적인 구현에서 분리되어 모의 객체를 사용한 테스트가 가능합니다. 구체적인 타입을 반환하면 호출자가 타입 어설션 없이 해당 타입의 모든 메서드에 접근할 수 있습니다. 인터페이스를 반환하면 향후 메서드 추가가 타입 어설션 뒤에 숨겨집니다.
Go 면접 준비에 대한 더 자세한 내용은 동시성 패턴 면접 질문 모듈과 테스팅 모듈을 참조하세요.
인터페이스를 설계하라는 요청을 받으면 단일 메서드 케이스부터 시작하세요. 여러 메서드가 항상 함께 호출되는 경우에만 확장합니다. 표준 라이브러리의 Stringer, Reader, Handler 인터페이스는 각각 하나의 메서드만 정의합니다.
시니어 Go 개발자가 인터페이스에 대해 알아야 할 것들
- 임베딩을 통한 인터페이스 합성은 상속 계층 없이 유연한 계약을 만듭니다. 인터페이스는 작게 유지하세요: 1-3개의 메서드가 대부분의 사용 사례를 커버합니다.
- 타입 어설션과 타입 스위치는 구체적인 타입을 안전하게 추출합니다. 패닉을 피하기 위해 프로덕션 코드에서는 항상 2값 형식이나 스위치를 사용하세요.
- Go 1.26의 자기참조 제네릭은 메서드가 구현 타입을 반환해야 하는 빌더 패턴과 수학 타입을 가능하게 합니다.
errors.AsType은 제네릭하고 타입 안전한 API로 에러 언래핑을 단순화합니다. Go 1.26 이상을 대상으로 하는 새 코드에서 사용하세요.var _ Interface = (*Type)(nil)을 통한 컴파일 타임 인터페이스 검사는 테스트 실행 전에 누락된 메서드를 감지합니다.- 컴파일 타임 타입 안전성이 시그니처의 복잡성보다 중요할 때
interface{}대신 제네릭을 선호하세요. - "인터페이스를 받고, 구조체를 반환하라" 가이드라인은 호출자에게 유연성을 유지하면서 전체 기능을 노출합니다.
연습을 시작하세요!
면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.
Go 코드의 버그를 찾을 수 있나요
실제 코드 한 조각, 숨은 버그 하나, 하루 한 번. 계정 없이 바로 도전할 수 있습니다.

작성자
Anthony Fillion-MailletSharpSkill 창업자
10년 이상 풀스택 개발을 해왔습니다. SharpSkill을 운영하며 이곳에 게시되는 모든 내용에 책임을 집니다.
2026년 9월 10일 업데이트
태그
공유
관련 기사

Go 1.26 면접 대비: Green Tea GC, go fix 도구, 스택 최적화 완벽 정리
Go 1.26 면접에서 자주 출제되는 Green Tea 가비지 컬렉터, 새로워진 go fix 도구, 슬라이스 스택 할당 최적화 등 핵심 변경사항을 상세히 정리합니다.

Go 에러 핸들링 2026: 패턴, 래핑, 기술 면접 핵심 질문
Go 에러 핸들링 패턴 완벽 가이드. 센티넬 에러, 커스텀 타입, errors.Is와 errors.As, %w를 활용한 에러 래핑, 기술 면접 빈출 질문까지 체계적으로 다룬다.

Go 디자인 패턴: Go 개발자를 위한 필수 패턴과 면접 대비 가이드
Go의 설계 철학에 기반한 디자인 패턴을 체계적으로 해설합니다. Functional Options, Strategy, Observer, Middleware 등 면접에서 자주 출제되는 실전 패턴과 답변 전략을 다룹니다.