Menu

TypeScript 면접 질문과 답변 (코드 포함 25개)

짧고 정확한 답과 코드로 정리한 TypeScript 면접 질문 25개를 초급부터 고급까지 묶었습니다: any와 unknown, interface와 type, 제네릭, 타입 좁히기, 유틸리티 타입, 매핑된 타입과 조건부 타입, 구조적 타이핑, tsconfig, TypeScript 7.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

면접관이 가장 많이 묻는 TypeScript 질문을, 여러분이 답할 수 있어야 하는 짧은 답과 작은 예제와 함께 정리했습니다. 수준별로 묶었으며, 주니어 포지션은 대개 첫 번째 묶음에서 끝나고 시니어 포지션은 마지막 묶음의 타입을 외워서 작성하기를 기대합니다.

초급 질문

1. TypeScript란 무엇인가요?

TypeScript는 Microsoft가 만든, 정적 타입이 있는 JavaScript입니다. 값에 타입을 표기하면 컴파일러가 이를 검사한 뒤 타입을 제거하고, JavaScript가 실행되는 곳이라면 어디서든 실행되는 평범한 JavaScript를 출력합니다.

@ts-expect-error 주석이 없으면 두 번째 호출은 컴파일 오류이므로 버그가 배포되지 않습니다.

2. TypeScript는 런타임에 타입을 검사하나요?

아니요. 타입은 컴파일 중에 지워지며 출력에는 흔적이 남지 않습니다. 위 실행이 23을 출력하는 것은 실행 시점에 a가 숫자여야 한다는 것을 아는 것이 아무것도 없기 때문입니다. 프로그램 외부에서 온 데이터(JSON, 사용자 입력, API 응답)를 검사하려면 타입 가드나 스키마 검증기 같은 실제 검사를 작성해야 합니다.

3. 기본 타입에는 무엇이 있나요?

string, number(정수와 실수를 아우르는 하나의 타입이며 int는 없습니다), boolean, bigint, symbol, null, undefined, 그리고 배열(number[]), 튜플([string, number]), 객체 타입, any, unknown, never, void입니다. 소문자 이름을 쓰세요. String과 Number는 래퍼 객체의 타입입니다.

4. any와 unknown의 차이는 무엇인가요?

둘 다 어떤 값이든 받습니다. any는 검사도 끄므로 어떤 연산이든 컴파일됩니다. unknown은 좁히기 전까지 아무것도 허용하지 않으므로, 아직 검사하지 않은 값에 쓰기 안전한 타입입니다.

5. 타입 추론이란 무엇인가요?

작성하지 않은 타입을 컴파일러가 알아내는 것입니다. let count = 0은 number이고, const mode = "dark"는 리터럴 타입 "dark"이며, 함수의 반환 타입은 return 문에서 옵니다. 일반적인 규칙은 함수 매개변수와 공개 반환 타입에는 타입을 표기하고 지역 변수는 추론에 맡기는 것입니다.

6. interface와 type의 차이는 무엇인가요?

둘 다 객체 형태를 기술하며, 클래스는 어느 쪽이든 implements할 수 있습니다. interface는 다시 열어 병합할 수 있고(선언 병합), 다른 인터페이스를 extends합니다. type 별칭은 유니언, 튜플, 원시 타입, 매핑된 타입, 조건부 타입 등 무엇에든 이름을 붙일 수 있습니다.

interface User { name: string }
interface User { age: number }       // merged: User has name and age

type Id = string | number;           // only a type alias can be a union
type Pair = [string, number];        // or a tuple
type Admin = User & { role: "admin" };

흔한 관례는 객체 형태에는 interface를, 그 밖의 모든 것에는 type을 쓰는 것입니다. 자세한 비교는 interface와 type 비교 페이지에 있습니다.

7. 유니언 타입과 인터섹션 타입이란 무엇인가요?

유니언 A | B는 여러 타입 중 하나인 값입니다. 좁히기 전까지는 모든 타입에 공통인 멤버만 쓸 수 있습니다. 인터섹션 A & B는 동시에 두 타입 모두인 값으로, 각 타입의 모든 멤버를 가집니다.

type Id = string | number;                 // either
type Timestamped = { createdAt: Date };
type Post = { title: string } & Timestamped; // both: title and createdAt

8. void와 never의 차이는 무엇인가요?

void는 쓸모 있는 값 없이 정상적으로 반환하는 함수의 반환 타입입니다. never는 일어날 수 없는 것의 타입입니다. 항상 예외를 던지거나 영원히 반복하는 함수, 또는 모든 경우가 제거된 유니언이 그렇습니다. never는 모든 타입에 대입할 수 있고, 어떤 값도 never에 대입할 수 없습니다.

function log(msg: string): void { console.log(msg); }
function fail(msg: string): never { throw new Error(msg); }
type Impossible = string & number; // never

중급 질문

9. 타입 좁히기(narrowing)란 무엇인가요?

컴파일러가 여러분의 검사를 따라가며 각 분기 안에서 타입을 구체화하는 것입니다. typeof, instanceof, in, 동등 비교, truthy 검사, 사용자 정의 타입 가드를 이해합니다.

10. 사용자 정의 타입 가드란 무엇인가요?

반환 타입이 value is T인 함수입니다. 이 함수가 true를 반환하면 호출하는 쪽의 변수가 T로 좁혀집니다. 컴파일러는 함수를 믿으므로 본문에서 값을 실제로 검사해야 합니다.

interface Cat { meow(): void }

function isCat(value: unknown): value is Cat {
    return (
        typeof value === "object" &&
        value !== null &&
        "meow" in value &&
        typeof value.meow === "function"
    );
}

단언 함수 function assertCat(v: unknown): asserts v is Cat는 false를 반환하는 대신 예외를 던지는 방식으로 좁힙니다.

11. 제네릭이란 무엇인가요?

하나의 함수, 클래스, 타입이 입력과 출력의 연결을 유지하면서 여러 타입으로 동작하게 해 주는 타입 매개변수입니다. T는 인수에서 추론되며, extends로 무엇이 될 수 있는지 제약합니다.

pluck(users, "email")은 "email"이 사용자 타입의 keyof가 아니므로 컴파일 오류입니다.

12. 타입에서 keyof와 typeof는 무엇을 하나요?

keyof T는 T의 프로퍼티 이름들의 유니언입니다. 타입 위치에서 typeof x는 변수의 타입을 줍니다. 둘을 합친 keyof typeof obj는 객체의 키를 유니언으로 바꿉니다.

const colors = { red: "#f00", green: "#0f0" };
type Colors = typeof colors;        // { red: string; green: string }
type ColorName = keyof typeof colors; // "red" | "green"

13. 유틸리티 타입이란 무엇인가요? 몇 가지 말해 보세요.

다른 타입을 변환하는 내장 제네릭 타입입니다. 가장 자주 묻는 것들은 다음과 같습니다.

유틸리티결과
Partial<T>모든 프로퍼티를 선택적으로
Required<T>모든 프로퍼티를 필수로
Readonly<T>모든 프로퍼티를 readonly로
Pick<T, "a" | "b">나열한 프로퍼티만
Omit<T, "a">나열한 프로퍼티를 제외한 전부
Record<K, V>키가 K이고 값이 V인 객체
Exclude<U, X>, Extract<U, X>유니언 멤버를 제거하거나 남김
NonNullable<T>null과 undefined를 뺀 T
ReturnType<F>, Parameters<F>함수의 반환 타입 또는 매개변수 타입
Awaited<T>Promise가 resolve되는 값

전체 목록은 유틸리티 타입 페이지에 있습니다.

14. 판별 유니언(discriminated union)이란 무엇인가요?

리터럴 "태그" 프로퍼티를 공유하는 객체 타입들의 유니언입니다. 태그를 검사하면 값이 멤버 하나로 좁혀지고, default 분기의 never 검사를 두면 빠뜨린 경우를 컴파일러가 보고합니다.

15. as와 satisfies의 차이는 무엇인가요?

value as T는 타입 단언입니다. 컴파일러에게 값을 T로 취급하라고 말하며 대부분의 검사를 건너뜁니다. value satisfies T는 값을 T에 대해 검사하되, 값 자신의 더 정확한 타입을 유지합니다.

type Theme = { primary: string; secondary: string };

const a = { primary: "#07f" } as Theme;        // compiles: the missing key is not reported
const b = { primary: "#07f" } satisfies Theme; // error TS2741: Property 'secondary' is missing
const c = {} as { name: string };              // compiles; c.name is undefined at run time

객체가 완전하다면 satisfies는 자신의 추론된 타입도 유지합니다. Record<string, string>에 대해 검사해도 변수는 자신이 어떤 키를 가졌는지 정확히 알지만, 타입 표기를 쓰면 모든 문자열 키로 넓어집니다.

16. 변수 뒤의 ! 연산자는 무엇을 하나요?

non-null 단언입니다. el!은 타입에서 null과 undefined를 제거합니다. 아무 검사도 생성하지 않으므로, 값이 실제로 null이라면 그 값을 쓰는 곳에서 프로그램이 멈춥니다. ?., ??, 명시적인 if를 우선하세요.

17. private과 #private의 차이는 무엇인가요?

private은 컴파일러만 강제하며, JavaScript 출력에서는 평범한 프로퍼티입니다. #field는 런타임이 강제하는 JavaScript private 필드입니다.

protected는 private처럼 동작하되 하위 클래스에서의 접근도 허용하며, readonly는 생성 후 다시 대입하는 것을 금지합니다.

18. 추상 클래스와 인터페이스의 차이는 무엇인가요?

인터페이스는 타입일 뿐입니다. 형태를 기술하고 출력에서 사라집니다. 추상 클래스는 인스턴스를 만들 수 없는 실제 클래스로, 하위 클래스가 구현해야 하는 abstract 멤버와 함께 구현된 메서드, 필드, 생성자를 가질 수 있습니다. 클래스는 인터페이스를 여러 개 구현할 수 있지만 클래스는 하나만 상속할 수 있습니다.

abstract class Repository<T> {
    protected items: T[] = [];
    abstract validate(item: T): boolean; // subclasses must implement
    add(item: T): void {                 // shared implementation
        if (this.validate(item)) this.items.push(item);
    }
}

19. TypeScript의 함수 오버로딩이란 무엇인가요?

여러 호출 시그니처 뒤에, 그 모두와 호환되는 시그니처를 가진 구현 하나를 두는 것입니다. 호출하는 쪽은 오버로드만 보므로 호출마다 정확한 반환 타입을 받습니다.

function parse(value: string): number;
function parse(value: string[]): number[];
function parse(value: string | string[]): number | number[] {
    return Array.isArray(value) ? value.map(Number) : Number(value);
}

const one = parse("4");          // number
const many = parse(["1", "2"]);  // number[]

반환 타입이 인수 타입에 따라 달라지지 않는다면 유니언 매개변수를 가진 시그니처 하나가 더 간단합니다.

고급 질문

20. 구조적 타이핑(structural typing)이란 무엇인가요?

TypeScript는 타입을 이름이 아니라 형태로 비교합니다. 필요한 프로퍼티를 가진 값이라면 그 타입으로 선언된 적이 없어도 받아들입니다.

pixel은 변수이므로 여기서 추가 프로퍼티 color는 문제가 없습니다. 객체 리터럴을 직접 넘기면(show({ x: 3, y: 4, color: "red" })) 초과 프로퍼티 오류가 나는데, 이 검사는 새로 만든 리터럴에만 적용됩니다. 형태가 같은 두 타입이 섞이면 안 될 때(둘 다 문자열인 UserId와 OrderId)는 브랜드 타입을 쓰세요: type UserId = string & { readonly __brand: "UserId" }.

21. Readonly와 Partial을 직접 구현하려면 어떻게 하나요?

매핑된 타입으로 구현합니다. keyof T를 순회하며 각 프로퍼티에 수식어를 붙입니다.

type MyReadonly<T> = { readonly [K in keyof T]: T[K] };
type MyPartial<T> = { [K in keyof T]?: T[K] };
type Mutable<T> = { -readonly [K in keyof T]: T[K] }; // "-" removes a modifier

type User = { name: string; age: number };
type Draft = MyPartial<User>; // { name?: string; age?: number }

22. 조건부 타입과 infer란 무엇인가요?

T extends U ? X : Y는 조건에 따라 타입을 고릅니다. 조건 안에서 infer는 일치한 타입의 일부를 붙잡는 타입 변수를 선언합니다. 조건부 타입은 유니언에 분배됩니다.

type MyReturnType<F> = F extends (...args: any[]) => infer R ? R : never;
type ElementOf<T> = T extends (infer E)[] ? E : T;

type A = MyReturnType<() => Promise<number>>; // Promise<number>
type B = ElementOf<string[]>;                 // string
type C = ElementOf<number | boolean[]>;       // number | boolean (distributed)

더 많은 패턴은 조건부 타입 페이지에 있습니다.

23. .d.ts 파일이란 무엇이고, declare는 무엇을 하나요?

선언 파일은 JavaScript 라이브러리나 브라우저 API처럼 다른 곳에 있는 코드의 타입만 담습니다. declare는 값을 만들지 않고 그 값이 존재한다고 밝힙니다. declare const VERSION: string;은 아무것도 출력하지 않습니다. 라이브러리 타입은 패키지에 번들되어 있거나 @types/{name} 패키지에서 오며, 자신의 코드에 대해서는 declaration: true로 tsc가 .d.ts 파일을 생성합니다.

24. strict는 무엇을 하고, 가장 중요한 tsconfig 옵션은 무엇인가요?

"strict": true는 noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, noImplicitThis, useUnknownInCatchVariables, strictBuiltinIteratorReturn을 켭니다. TypeScript 7에서는 기본으로 켜져 있습니다. 면접관이 묻는 다른 옵션은 다음과 같습니다.

옵션한 줄 답변
target출력이 사용하는 JavaScript 버전
module출력 모듈 형식: nodenext는 Node의 규칙을 따르고, esnext/preserve는 번들러를 위해 import를 유지
moduleResolutionimport를 찾는 방식: nodenext는 ES 모듈 파일에서 ./file.js를 요구하고, bundler는 ./file을 허용
noUncheckedIndexedAccessarr[i]의 타입에 undefined를 포함
paths타입 검사기만을 위한 import 별칭. 출력에 별칭이 남으므로 번들러나 Node의 imports 필드가 해석해야 함
skipLibCheck속도를 위해 .d.ts 파일 검사를 건너뜀
verbatimModuleSyntax타입으로만 쓰는 import에는 type을 표시해야 하며, 다른 import는 작성한 그대로 유지

25. TypeScript 7이란 무엇인가요?

Go로 다시 작성한 네이티브 프로그램 형태의 TypeScript 컴파일러로, typescript npm 패키지의 버전 7로 출시되었습니다. 명령은 여전히 tsc이고 언어도 같지만, JavaScript 기반의 6.x 컴파일러보다 훨씬 빠릅니다. TypeScript 팀은 네이티브 코드와 공유 메모리 멀티스레딩 덕분에 일반적인 전체 빌드에서 8배에서 12배의 속도 향상을 보고합니다. 또한 6.0에서 사용 중단(deprecated)되었던 기능의 제거를 강제합니다. 예를 들어 baseUrl, outFile, moduleResolution: "node10", module: "amd", alwaysStrict: false는 이제 제거하라고 알려 주는 오류(TS5102, TS5108)입니다.

자주 묻는 질문

가장 흔한 TypeScript 면접 질문은 무엇인가요?

가장 자주 나오는 질문은 any와 unknown의 차이, interface와 type 비교, 제네릭의 동작 방식, 타입 좁히기와 타입 가드의 동작 방식, Partial, Pick, Omit 같은 유틸리티 타입이 하는 일, 그리고 TypeScript가 런타임에 타입을 검사하는지(검사하지 않습니다)입니다.

시니어 개발자에게는 어떤 TypeScript 질문을 하나요?

타입을 읽기만 하는 것이 아니라 직접 작성하기를 기대하세요. 매핑된 타입과 조건부 타입으로 Readonly나 ReturnType 구현하기, 구조적 타이핑과 브랜드 타입을 쓸 때 설명하기, 전수 검사가 있는 판별 유니언 설계하기, strict, noUncheckedIndexedAccess, 모듈 해석 같은 tsconfig 선택에 대해 논의하기 등입니다.

TypeScript 면접은 어떻게 준비하나요?

strict를 켜고 작은 프로그램을 작성하면서, 컴파일러 오류를 예측할 수 있을 때까지 읽어 보세요. 타입 좁히기, 제약이 있는 제네릭, 주요 유틸리티 타입, 타입 단언이 안전하지 않은 이유를 각각 두 줄짜리 예제와 함께 설명할 수 있어야 합니다.

React와 Angular 면접에서도 TypeScript를 묻나요?

대개 묻습니다. Angular 자체가 TypeScript로 작성되었고 Angular 앱도 TypeScript로 작성하므로, Angular 면접은 @Component 같은 데코레이터와 접근 제한자를 포함해 TypeScript를 전제로 하는 경우가 많습니다. TypeScript 코드베이스의 React 면접에서는 props, state, 이벤트 핸들러에 타입을 지정해 보라고 자주 요구하는데, 여기서 보여 주는 인터페이스, 유니언, 제네릭을 그대로 씁니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기