TypeScript 시니어 프레임워크 기술면접
새 면접Q. TypeScript의 Excess Property Checking이 객체 리터럴에만 적용되는 이유와 동작 원리를 설명하고, 이로 인해 발생할 수 있는 타입 안정성 허점을 설명해주세요. 특히 변수에 할당 후 전달하는 경우와 직접 전달하는 경우의 차이, 그리고 Index Signature와의 상호작용을 포함해주세요.
구조적 타이핑과 freshness 개념, 그리고 타입 호환성 검사 시점의 차이를 고려해보세요.
Excess Property Checking은 객체 리터럴이 fresh object로 간주될 때만 적용되며, 이는 오타나 실수로 인한 잘못된 프로퍼티 전달을 방지하기 위한 안전장치입니다. 변수에 할당되면 객체는 더 이상 fresh하지 않아 구조적 타이핑 규칙만 적용되므로, 추가 프로퍼티가 있어도 에러가 발생하지 않습니다. Index Signature가 있는 타입의 경우 추가 프로퍼티를 허용하므로 Excess Property Checking이 적용되지 않습니다. 이러한 동작으로 인해 중간 변수를 거치면 타입 안정성이 약화될 수 있으므로, 실무에서는 타입 단언보다는 명시적 타입 정의와 직접 전달 패턴을 권장합니다. 또한 as const assertion을 활용하면 리터럴 타입을 보존하면서도 타입 안정성을 높일 수 있습니다.
- • Fresh object 개념과 Excess Property Checking의 적용 조건
- • 변수 할당 시 freshness 소실로 인한 구조적 타이핑 적용
- • Index Signature와의 상호작용 및 타입 안정성 허점
- • 실무에서 타입 안정성을 유지하기 위한 패턴
API 클라이언트 라이브러리에서 요청 옵션 객체를 전달할 때 오타로 인한 잘못된 설정을 컴파일 타임에 방지할 수 있습니다.
대규모 API 응답 객체를 다룰 때 Excess Property Checking의 한계를 극복하고 런타임 타입 안정성까지 보장하려면 어떤 설계 패턴을 사용하시겠습니까?
Q. TypeScript의 Type Widening과 Type Narrowing의 차이를 설명하고, const assertion, as const, satisfies 연산자가 각각 타입 추론에 미치는 영향을 비교해주세요. 특히 객체와 배열에서 각 방식이 생성하는 타입의 차이와 실무에서의 활용 시나리오를 포함해주세요.
리터럴 타입의 확장과 축소, 그리고 타입 체크와 타입 추론의 균형을 생각해보세요.
Type Widening은 let으로 선언된 변수의 리터럴 타입이 일반 타입으로 확장되는 현상이고, Type Narrowing은 조건문 등을 통해 타입이 구체화되는 과정입니다. const assertion은 변수를 readonly 리터럴 타입으로 추론하며, as const는 중첩된 객체와 배열까지 깊은 readonly로 만듭니다. satisfies 연산자는 TypeScript 4.9에서 도입되어 타입 체크는 수행하되 추론된 타입은 보존하므로, 타입 안정성과 정확한 타입 추론을 동시에 달성할 수 있습니다. 실무에서 설정 객체는 as const로 불변성을 보장하고, API 응답 스키마는 satisfies로 타입 체크와 자동완성을 모두 활용하며, 상수 배열은 const assertion으로 튜플 타입을 유지하는 패턴이 효과적입니다.
- • Type Widening과 Narrowing의 개념적 차이
- • const assertion, as const, satisfies의 타입 추론 방식 비교
- • 객체와 배열에서 각 방식이 생성하는 타입의 구조적 차이
- • 실무 시나리오별 최적 선택 기준
다국어 번역 키를 관리할 때 as const로 키 목록을 정의하면 자동완성과 타입 체크를 동시에 활용할 수 있습니다.
대규모 설정 관리 시스템에서 환경별 설정을 타입 안전하게 관리하면서도 런타임 오버헤드를 최소화하려면 어떤 조합을 사용하시겠습니까?
Q. TypeScript의 Higher-Kinded Types(HKT) 부재로 인한 제약사항을 설명하고, 이를 우회하여 Functor, Monad 같은 함수형 프로그래밍 패턴을 구현하는 방법을 설명해주세요. 특히 타입 레벨 프로그래밍과 branded types를 활용한 HKT 시뮬레이션 기법과 실무적 트레이드오프를 포함해주세요.
타입 생성자를 타입 파라미터로 전달할 수 없는 한계와, 이를 우회하는 인코딩 기법을 고려해보세요.
TypeScript는 타입 생성자 자체를 제네릭 파라미터로 받을 수 없어 Functor나 Monad 같은 추상화를 직접 표현할 수 없습니다. 이를 우회하기 위해 타입 레벨 맵핑을 사용하는 defunctionalization 기법이 있으며, URI를 통해 타입 생성자를 식별하고 lookup 타입으로 실제 타입을 가져오는 패턴입니다. fp-ts 같은 라이브러리는 이 방식으로 HKT를 시뮬레이션하지만, 타입 정의가 복잡해지고 타입 추론이 약화되는 단점이 있습니다. 실무에서는 완전한 HKT 구현보다는 구체적인 타입에 대한 제네릭 함수를 작성하거나, 필요한 경우에만 제한적으로 HKT 패턴을 적용하는 것이 유지보수성과 가독성 측면에서 유리합니다. 타입 안정성과 코드 복잡도 사이의 균형을 고려한 실용적 접근이 중요합니다.
- • Higher-Kinded Types의 개념과 TypeScript의 제약사항
- • Defunctionalization 기법을 통한 HKT 시뮬레이션
- • 타입 레벨 URI 맵핑과 lookup 타입 활용
- • 실무에서의 복잡도와 유지보수성 트레이드오프
비동기 작업 체이닝과 에러 핸들링을 함수형 스타일로 구현할 때 Monad 패턴의 타입 안정성이 필요합니다.
Effect 시스템이나 Railway Oriented Programming을 TypeScript로 구현할 때 HKT 없이도 타입 안정성을 유지하는 대안적 설계 방법은 무엇입니까?
Q. TypeScript의 namespace와 ES Module의 차이를 설명하고, 현대적인 TypeScript 프로젝트에서 namespace 사용을 지양하는 이유를 설명해주세요. 특히 트리 쉐이킹, 코드 분할, 순환 참조 문제 측면에서 각 방식의 장단점과, namespace가 여전히 유용한 예외적 상황을 포함해주세요.
번들링 도구의 최적화 능력과 런타임 동작의 차이를 생각해보세요.
namespace는 TypeScript 고유의 내부 모듈 시스템으로 컴파일 시 IIFE로 변환되며, ES Module은 표준 JavaScript 모듈 시스템입니다. ES Module은 정적 분석이 가능해 트리 쉐이킹과 코드 분할을 효과적으로 지원하지만, namespace는 런타임 객체로 번들링되어 사용하지 않는 코드도 포함될 수 있습니다. 순환 참조 문제에서도 ES Module은 명시적 import로 의존성을 파악하기 쉽지만, namespace는 암묵적 참조로 인해 디버깅이 어렵습니다. 다만 전역 타입 선언이나 ambient declaration을 그룹화할 때, 또는 레거시 라이브러리와의 호환성을 위해서는 namespace가 여전히 유용합니다. 현대 프로젝트에서는 ES Module을 기본으로 사용하고, declare namespace는 타입 정의 목적으로만 제한적으로 활용하는 것이 권장됩니다.
- • namespace와 ES Module의 구조적, 런타임 동작 차이
- • 트리 쉐이킹과 코드 분할 관점의 최적화 차이
- • 순환 참조와 의존성 관리의 명확성
- • 타입 정의와 레거시 호환성 측면의 예외적 활용
서드파티 라이브러리의 글로벌 타입을 확장하거나 Window 객체에 커스텀 프로퍼티를 추가할 때 declare namespace를 사용합니다.
대규모 모노레포에서 여러 패키지가 공통 타입을 공유할 때 namespace와 ES Module 중 어떤 방식으로 타입을 구조화하시겠습니까?
Q. TypeScript의 Distributive Conditional Types의 동작 원리를 설명하고, Union 타입이 Conditional Type과 만났을 때 분배 법칙이 적용되는 메커니즘을 설명해주세요. 특히 분배를 의도적으로 방지해야 하는 상황과 대괄호를 사용한 non-distributive 패턴, 그리고 실무에서의 활용 사례를 포함해주세요.
Union 타입의 각 멤버가 개별적으로 조건부 타입을 통과하는 과정과, 이를 막는 방법을 생각해보세요.
Distributive Conditional Types는 naked type parameter가 Union 타입일 때 각 멤버에 대해 조건부 타입을 개별적으로 적용한 후 결과를 Union으로 합치는 동작입니다. 예를 들어 T extends U ? X : Y에서 T가 A | B라면 (A extends U ? X : Y) | (B extends U ? X : Y)로 분배됩니다. 분배를 방지하려면 [T] extends [U] 패턴으로 타입 파라미터를 튜플로 감싸 naked type parameter가 아니게 만들어야 합니다. Union 타입 전체를 하나의 단위로 처리해야 할 때, 예를 들어 Union을 배열로 변환하거나 Union의 길이를 계산할 때 non-distributive 패턴이 필요합니다. 실무에서는 Exclude, Extract 같은 유틸리티 타입이 분배 법칙을 활용하며, 복잡한 타입 변환 로직에서 분배 제어가 타입 정확성에 중요한 역할을 합니다.
- • Naked type parameter와 분배 법칙의 동작 원리
- • Union 타입 멤버별 개별 적용과 결과 합치기
- • 대괄호 패턴을 통한 non-distributive 구현
- • 실무에서 분배 제어가 필요한 시나리오
Redux 액션 타입에서 특정 액션만 필터링하거나, GraphQL 스키마에서 nullable 필드만 추출할 때 분배 법칙을 활용합니다.
State Machine의 상태 전이를 타입으로 표현할 때 Distributive Conditional Types를 어떻게 활용하여 불가능한 상태 전이를 컴파일 타임에 방지할 수 있습니까?
Q. TypeScript의 esModuleInterop과 allowSyntheticDefaultImports 옵션의 차이를 설명하고, CommonJS 모듈을 ES Module 방식으로 import할 때 발생하는 문제를 각 옵션이 어떻게 해결하는지 설명해주세요. 특히 런타임 동작의 차이와 번들러 사용 시 고려사항을 포함해주세요.
타입 체크와 실제 코드 변환의 차이, 그리고 default export의 합성 여부를 생각해보세요.
allowSyntheticDefaultImports는 타입 체크 단계에서만 작동하여 default import를 허용하지만 실제 코드는 변환하지 않으며, esModuleInterop은 타입 체크와 함께 런타임 헬퍼 코드를 생성하여 실제 동작을 보장합니다. CommonJS 모듈은 module.exports로 내보내지만 ES Module의 default import와 구조가 달라, esModuleInterop 없이는 import React from 'react' 같은 코드가 런타임 에러를 발생시킬 수 있습니다. esModuleInterop은 __importStar와 __importDefault 헬퍼를 생성하여 이를 해결하며, allowSyntheticDefaultImports만 활성화하면 타입 체크는 통과하지만 번들러가 없는 환경에서는 런타임 에러가 발생합니다. Webpack이나 Rollup 같은 번들러를 사용하면 모듈 변환을 번들러가 처리하므로 allowSyntheticDefaultImports만으로 충분할 수 있지만, Node.js 직접 실행 환경에서는 esModuleInterop이 필수입니다.
- • 타입 체크 vs 런타임 코드 변환의 차이
- • CommonJS와 ES Module의 구조적 불일치
- • 헬퍼 함수 생성과 런타임 동작 보장
- • 번들러 환경과 Node.js 환경의 요구사항 차이
React 프로젝트에서 import React from 'react' 구문이 정상 작동하려면 esModuleInterop 옵션이 필요합니다.
TypeScript 라이브러리를 작성할 때 사용자가 다양한 환경에서 사용할 수 있도록 하려면 어떤 모듈 시스템과 컴파일러 옵션 조합을 권장하시겠습니까?
Q. TypeScript의 Tuple Types에서 Rest Elements, Optional Elements, Named Tuple Elements의 활용 방법을 설명하고, 이를 조합하여 가변 인자 함수의 타입 안정성을 보장하는 방법을 설명해주세요. 특히 TypeScript 4.0+의 Variadic Tuple Types를 활용한 고급 패턴과 제약사항을 포함해주세요.
튜플의 구조적 특성과 제네릭을 조합하여 함수 오버로딩을 타입으로 표현하는 방법을 생각해보세요.
Tuple Types는 고정 길이 배열에 각 위치별 타입을 지정할 수 있으며, Rest Elements(...T[])로 가변 길이를, Optional Elements(T?)로 선택적 요소를 표현합니다. Named Tuple Elements는 [name: string, age: number] 같은 형태로 가독성을 높이며, 함수 매개변수 시그니처를 튜플 타입으로 표현할 때 유용합니다. Variadic Tuple Types는 제네릭과 spread 연산자를 조합하여 함수 합성이나 커링을 타입 안전하게 구현할 수 있게 합니다. 예를 들어 concat 함수의 타입을 [...T, ...U]로 표현하여 두 튜플의 결합을 정확히 추론할 수 있습니다. 다만 튜플 길이 계산이나 재귀적 타입 조작은 TypeScript의 재귀 깊이 제한과 복잡도 제약을 받으므로, 실무에서는 합리적인 수준의 복잡도를 유지해야 합니다.
- • Rest, Optional, Named Elements의 각 역할과 문법
- • Variadic Tuple Types를 통한 가변 인자 함수 타입화
- • 함수 합성과 커링의 타입 안전성 보장
- • 재귀 깊이와 복잡도 제약사항
함수형 프로그래밍 라이브러리에서 compose나 pipe 함수의 타입을 정의할 때 Variadic Tuple Types가 필수적입니다.
이벤트 핸들러 체인에서 각 핸들러의 반환값을 다음 핸들러의 입력으로 전달하는 파이프라인을 Variadic Tuple Types로 어떻게 타입 안전하게 구현하시겠습니까?
Q. TypeScript의 Abstract Class와 Interface의 차이를 설명하고, 각각을 선택해야 하는 상황을 설명해주세요. 특히 다중 상속, 구현 로직 포함 여부, 접근 제어자, 런타임 존재 여부 측면에서 비교하고, 실무에서 두 가지를 조합하여 사용하는 패턴을 포함해주세요.
컴파일 후 JavaScript 코드에 남는 것과 사라지는 것, 그리고 OOP 설계 원칙을 고려해보세요.
Abstract Class는 일부 구현을 포함할 수 있고 런타임에 존재하며 단일 상속만 가능한 반면, Interface는 순수 타입 정의만 가능하고 컴파일 후 제거되며 다중 구현이 가능합니다. Abstract Class는 공통 로직을 상위 클래스에서 구현하고 일부만 하위 클래스가 오버라이드하는 Template Method 패턴에 적합하며, private/protected 같은 접근 제어자를 사용할 수 있습니다. Interface는 계약만 정의하고 구현은 클래스에 맡기는 Strategy 패턴이나 Dependency Injection에 적합하며, 여러 인터페이스를 조합하여 유연한 타입 시스템을 구축할 수 있습니다. 실무에서는 Abstract Class로 기본 구현과 템플릿을 제공하고, Interface로 외부 계약을 정의하는 조합 패턴이 효과적이며, 이를 통해 SOLID 원칙 중 Dependency Inversion과 Interface Segregation을 달성할 수 있습니다.
- • 구현 포함 여부와 런타임 존재 여부의 차이
- • 단일 상속 vs 다중 구현의 유연성
- • 접근 제어자와 캡슐화 가능성
- • 설계 패턴과 SOLID 원칙 적용
ORM 엔티티에서 공통 필드와 메서드는 Abstract Base Entity로 구현하고, 특정 동작 계약은 Interface로 정의하여 유연성을 확보합니다.
Plugin 시스템을 설계할 때 확장성과 타입 안정성을 모두 보장하려면 Abstract Class와 Interface를 어떻게 조합하시겠습니까?
Q. TypeScript의 Assertion Functions(asserts 키워드)의 동작 원리를 설명하고, 일반 타입 가드와의 차이점을 설명해주세요. 특히 제어 흐름 분석에 미치는 영향과, 런타임 검증 로직과 타입 시스템을 연결하는 방법, 그리고 실무에서 유효성 검사 라이브러리와 통합하는 패턴을 포함해주세요.
함수 반환 후 타입 상태 변화와 예외 발생을 통한 타입 보장을 생각해보세요.
Assertion Functions는 asserts 키워드를 사용하여 함수가 정상 반환되면 특정 조건이 참임을 타입 시스템에 보장하며, 조건이 거짓이면 예외를 던져야 합니다. 일반 타입 가드가 boolean을 반환하여 if문 내부에서만 타입을 좁히는 것과 달리, Assertion Functions는 함수 호출 이후 코드 전체에서 타입이 좁혀진 상태를 유지합니다. 제어 흐름 분석에서 assertion function 호출 이후 코드는 조건이 참인 경로로만 간주되어 타입 체크가 단순화됩니다. 런타임 검증 라이브러리와 통합할 때는 검증 함수에 asserts 시그니처를 추가하여 런타임 안전성과 타입 안전성을 동시에 달성할 수 있습니다. 실무에서는 API 응답 검증, 환경 변수 검증, 불변 조건 확인 등에서 assertion functions를 활용하여 방어적 프로그래밍과 타입 안정성을 결합합니다.
- • asserts 키워드와 예외 기반 타입 보장 메커니즘
- • 일반 타입 가드 대비 지속적인 타입 좁히기 효과
- • 제어 흐름 분석과 타입 상태 전파
- • 런타임 검증과 타입 시스템의 통합
환경 변수가 필수로 존재해야 하는 경우 assertion function으로 검증하면 이후 코드에서 undefined 체크 없이 안전하게 사용할 수 있습니다.
마이크로서비스 간 통신에서 메시지 스키마 검증을 assertion functions로 구현할 때 성능과 타입 안정성을 어떻게 균형있게 유지하시겠습니까?
Q. TypeScript의 Recursive Type Aliases를 활용하여 깊이 중첩된 데이터 구조를 처리하는 방법을 설명하고, TypeScript 4.1+의 재귀 제한 완화 이전과 이후의 차이를 설명해주세요. 특히 JSON 타입 정의, 트리 구조 순회, 깊은 객체 변환에서의 활용과, 무한 재귀를 방지하기 위한 설계 패턴을 포함해주세요.
타입 레벨에서의 재귀 호출과 종료 조건, 그리고 컴파일러의 깊이 제한을 생각해보세요.
Recursive Type Aliases는 타입 정의 내부에서 자기 자신을 참조하여 재귀적 구조를 표현하며, TypeScript 4.1 이전에는 재귀 깊이가 매우 제한적이었지만 이후 크게 완화되었습니다. JSON 타입을 정의할 때 type Json = string | number | boolean | null | Json[] | { [key: string]: Json } 같은 재귀 구조로 모든 유효한 JSON을 표현할 수 있습니다. 트리 구조 순회나 깊은 객체 변환에서는 Conditional Types와 조합하여 각 레벨을 재귀적으로 처리하되, 종료 조건을 명확히 정의해야 무한 재귀를 방지할 수 있습니다. 실무에서는 재귀 깊이를 제한하는 카운터 타입을 사용하거나, 특정 깊이 이후 any로 대체하는 안전 장치를 두는 패턴이 효과적입니다. 또한 재귀 타입의 복잡도가 컴파일 성능에 영향을 미칠 수 있으므로, 필요한 경우에만 제한적으로 사용하고 타입 단순화를 고려해야 합니다.
- • Recursive Type Aliases의 문법과 자기 참조 메커니즘
- • TypeScript 4.1 이전/이후의 재귀 제한 차이
- • JSON, 트리 구조 등 실용적 활용 사례
- • 무한 재귀 방지와 성능 고려사항
설정 객체가 임의 깊이로 중첩될 수 있을 때 재귀 타입으로 전체 구조의 타입 안정성을 보장할 수 있습니다.
파일 시스템 트리를 타입으로 표현하여 경로 문자열의 유효성을 컴파일 타임에 검증하는 시스템을 재귀 타입으로 어떻게 구현하시겠습니까?
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!