TypeScript 주니어 코딩 면접
새 면접Q. 숫자 배열을 받아서 짝수만 필터링한 후 각 숫자를 제곱하여 반환하는 함수를 작성해야 합니다. map, filter 등의 배열 메서드를 활용하여 어떻게 구현할 수 있는지 설명하고, 각 단계의 타입이 어떻게 변환되는지 설명해주세요.
먼저 filter 메서드로 짝수만 선택합니다(num % 2 === 0 조건 사용). 그 다음 map 메서드로 각 요소를 제곱합니다(num => num * num). 입력 타입은 number[]이고, filter 후에도 number[], map 후 최종 반환값도 number[]입니다. 메서드 체이닝을 사용하면 가독성 좋게 한 줄로 작성할 수 있습니다. 함수 시그니처는 function processNumbers(arr: number[]): number[] 형태가 됩니다.
- • filter로 조건에 맞는 요소만 선택
- • map으로 각 요소를 변환
- • 메서드 체이닝으로 연속 처리 가능
- • 각 단계마다 타입이 유지되거나 변환됨
만약 배열이 매우 크다면 성능 최적화를 위해 어떤 방법을 고려할 수 있을까요? for 루프를 사용하는 것과 메서드 체이닝의 성능 차이는 무엇인가요?
Q. 사용자 정보 객체의 특정 속성만 업데이트하되, 원본 객체는 변경하지 않고 새로운 객체를 반환해야 합니다. TypeScript에서 스프레드 연산자를 활용한 불변 업데이트 방법과, Readonly 타입을 사용하여 실수로 원본을 수정하는 것을 방지하는 방법을 설명해주세요.
스프레드 연산자를 사용하여 { ...originalUser, name: newName } 형태로 새 객체를 생성합니다. 이렇게 하면 원본은 그대로 유지되고 변경된 속성만 덮어쓴 새 객체가 반환됩니다. 함수 파라미터에 Readonly<User> 타입을 지정하면 컴파일 타임에 원본 수정을 방지할 수 있습니다. 중첩된 객체의 경우 각 레벨마다 스프레드를 적용해야 깊은 복사가 됩니다. 이는 React 같은 프레임워크에서 상태 관리 시 중요한 패턴입니다.
- • 스프레드 연산자로 얕은 복사 수행
- • Readonly 타입으로 컴파일 타임 보호
- • 중첩 객체는 각 레벨마다 스프레드 필요
- • 불변성은 예측 가능한 코드 작성에 필수
깊은 중첩 구조를 가진 객체를 불변 방식으로 업데이트할 때 코드가 복잡해지는데, 이를 해결하기 위한 라이브러리나 패턴에는 어떤 것들이 있나요?
Q. 외부 API를 호출하는 비동기 함수에서 발생할 수 있는 다양한 에러(네트워크 에러, 타임아웃, 4xx/5xx 응답)를 처리해야 합니다. try-catch를 사용한 에러 처리 방법과, 커스텀 에러 클래스를 만들어 에러 타입을 구분하는 방법을 설명해주세요.
async 함수 내에서 try-catch 블록으로 에러를 잡습니다. catch 블록에서 error의 타입을 체크(instanceof 사용)하여 각기 다른 처리를 합니다. 커스텀 에러는 Error 클래스를 상속받아 NetworkError, TimeoutError 등으로 만들고, 각 에러마다 고유한 속성(statusCode 등)을 추가합니다. 에러 발생 지점에서 적절한 커스텀 에러를 throw하면 호출부에서 타입 안전하게 처리할 수 있습니다. unknown 타입으로 catch한 후 타입 가드로 좁히는 것이 안전합니다.
- • try-catch로 비동기 에러 포착
- • 커스텀 에러 클래스로 에러 타입 구분
- • instanceof와 타입 가드로 에러 타입 체크
- • catch의 에러는 unknown으로 처리하는 것이 안전
에러 처리를 중앙화하여 반복 코드를 줄이려면 어떤 패턴을 사용할 수 있을까요? 예를 들어 axios의 interceptor 같은 개념을 직접 구현한다면 어떻게 할 수 있을까요?
Q. 학생 객체 배열이 있고 각 학생은 학년(grade) 속성을 가지고 있습니다. 이 배열을 학년별로 그룹핑하여 객체로 변환하는 로직을 작성해야 합니다. reduce 메서드를 활용한 구현 방법과, 결과 객체의 타입을 Record나 Map으로 정의하는 방법을 설명해주세요.
reduce 메서드를 사용하여 빈 객체를 초기값으로 시작합니다. 각 학생을 순회하며 acc[student.grade]가 없으면 빈 배열로 초기화하고, 해당 학년 배열에 학생을 push합니다. 반환 타입은 Record<number, Student[]> 또는 { [grade: number]: Student[] }로 정의할 수 있습니다. Map을 사용하면 get/set 메서드로 더 명시적으로 처리할 수 있고, has로 존재 여부를 체크합니다. reduce는 누적값을 만드는 강력한 도구입니다.
- • reduce로 배열을 객체로 변환
- • 누적 객체에 조건부로 속성 추가
- • Record 타입으로 키-값 구조 표현
- • Map 사용 시 더 명시적인 API 제공
같은 작업을 Object.groupBy 메서드를 사용하면 어떻게 구현할 수 있을까요? 또는 lodash의 groupBy 같은 유틸리티 함수를 직접 만든다면 제네릭을 어떻게 활용할 수 있을까요?
Q. 입력 타입에 따라 다른 타입을 반환하는 함수를 만들어야 합니다. 예를 들어 문자열을 주면 문자열 배열을, 숫자를 주면 숫자 배열을 반환하는 함수입니다. TypeScript의 함수 오버로딩을 사용하여 이를 타입 안전하게 구현하는 방법과, 제네릭을 사용한 대안을 설명해주세요.
함수 오버로딩은 같은 함수명에 여러 시그니처를 선언합니다. function toArray(value: string): string[]과 function toArray(value: number): number[]를 먼저 선언하고, 실제 구현부는 function toArray(value: string | number): (string | number)[]로 작성합니다. 제네릭을 사용하면 function toArray<T>(value: T): T[]로 더 간결하게 표현할 수 있습니다. 오버로딩은 완전히 다른 동작을 할 때 유용하고, 제네릭은 동일한 로직에 다양한 타입을 적용할 때 적합합니다. 오버로딩 시 구현부는 모든 경우를 처리할 수 있어야 합니다.
- • 함수 오버로딩으로 입력별 반환 타입 명시
- • 구현 시그니처는 모든 오버로드를 포괄
- • 제네릭으로 더 유연한 타입 처리 가능
- • 오버로딩은 완전히 다른 동작, 제네릭은 동일 로직에 적합
함수 오버로딩의 시그니처 순서가 중요한 이유는 무엇이며, 잘못된 순서로 인해 발생할 수 있는 문제는 무엇인가요? 또한 조건부 타입을 사용하여 오버로딩을 대체할 수 있나요?
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!