~ / posts / developer
smith@lab:~/posts$ cat developer/typescript-type-check-settings-performance-guide.md

타입스크립트 strict 옵션 고르기, 타입 주석 판단표와 tsc 검사 한계

tsconfig를 새로 만들거나 손볼 때마다 같은 질문이 나와요. strict 하나면 충분한지, 타입을 어디까지 적을지, 검사가 느려지면 어디부터 볼지요. 결론부터 말하면 2026년 10월 기준으로는 strict(TypeScript 6.0부터 기본값)에 noUncheckedIndexedAccess를 더하고, 내보내는 함수에만 반환 타입을 적고, 설정 객체는 satisfies로 검사하는 조합이 손이 가장 덜 가요.

이 글은 검사기 원리 대신 고를 때 쓰는 기준을 다뤄요. 예제는 모두 TypeScript 5.9.3, 6.0.3, 7.0.2 세 버전으로 직접 돌려 확인했어요.

광고

tsconfig strict 옵션 판단표 (6.0·7.0 기준)

TSConfig 레퍼런스에 따르면 strict는 아래 표의 앞 다섯 개를 포함한 9개 검사를 한 번에 켜요. 같은 문서는 앞으로 이 묶음에 검사가 더 들어올 수 있다고 적어요. 버전을 올렸더니 오류가 늘었다면 대개 이 때문이에요. TypeScript 6.0 발표 글부터는 strict 기본값이 true라서, tsconfig에 strict를 안 적었던 프로젝트는 6.0으로 올리는 순간 오류가 쏟아질 수 있어요.

옵션strict 포함잡아내는 것켤 때 손 가는 정도추천
strictNullChecks예null·undefined 처리 누락큼반드시
noImplicitAny예타입을 못 정해 any가 된 매개변수중간반드시
strictFunctionTypes예콜백 매개변수 역방향 대입작음반드시
useUnknownInCatchVariables예catch (e)의 e를 unknown으로작음반드시
strictPropertyInitialization예생성자에서 안 채운 클래스 필드중간반드시
noUncheckedIndexedAccess아니요arr[i]·map[key]가 없을 수 있음중간~큼새 프로젝트는 켬
exactOptionalPropertyTypes아니요a?: T에 undefined 직접 대입라이브러리와 충돌 잦음필요한 팀만
isolatedDeclarations아니요내보낸 값의 타입을 추론에 기댐큼모노레포·라이브러리

같은 파일에 두 옵션을 더해 돌리자 prices["banana"].toFixed()와 list[5].toFixed()에 TS2532가, nickname?: string 자리에 넣은 { nickname: undefined }에 TS2375가 붙었어요. 셋 다 strict만으로는 통과했고, 앞의 두 줄은 실행하면 실제로 TypeError를 내요.

타입 주석·satisfies·as const, 어느 것을 쓸까

타입을 붙이는 방법은 네 가지이고, 남기는 정보와 잡는 실수가 서로 달라요.

추론만, 타입 주석, satisfies, as const satisfies 네 방법이 각각 남기는 타입 정보와 검사 범위를 비교한 카드
같은 객체에 타입을 붙여도 남는 정보와 잡히는 실수가 달라요
자리추천이유
지역 변수, 함수 안 계산값추론만주석은 고칠 곳만 늘려요
모듈 밖으로 내보내는 함수반환 타입 주석선언 파일이 짧아지고 구현 실수가 호출부로 안 퍼져요
설정·옵션 객체satisfies 타입오타는 잡고 리터럴 정보는 남아요
라우트·권한 코드 같은 상수 표as const satisfies 타입값이 리터럴로 남아 다른 타입의 재료가 돼요
제네릭 함수에 넘기는 콜백매개변수 주석 생략주석이 타입 인자를 먼저 정해 엉뚱한 곳에 오류가 찍혀요
외부 API 응답, JSON.parse 결과주석 + 런타임 검증주석만으로는 실제 값이 맞는지 몰라요
type Route = { path: `/${string}`; auth: boolean };

const routes = {
  home:  { path: "/",      auth: false },
  admin: { path: "/admin", auth: true },
} as const satisfies Record<string, Route>;

const p: "/admin" = routes.admin.path; // 리터럴이 그대로 남아요

path에서 슬래시를 빼먹으면 satisfies 줄에서 오류가 나요. 같은 객체에 : Record<string, Route> 주석을 달면 routes.admin.path는 ` /${string} 으로 넓어지고, 없는 키 routes.admn도 통과해요. satisfies`는 TypeScript 4.9에서 들어왔어요.

외부 응답 타입은 실제 응답으로 초안을 뽑는 편이 빨라요. JSON → TypeScript 변환기에 응답 하나를 붙여 넣으면 interface 초안이 나와요. 이 타입은 컴파일할 때만 있으니, 실제 응답 확인 순서는 API 응답 디버깅 6단계를 참고하세요.

반환 타입은 모든 함수에 적을 필요는 없어요. TypeScript 위키 Performance 문서는 .d.ts 출력에 import("./some/path").SomeType 같은 표기나 소스에 없던 거대한 타입이 보일 때 적어 보라고 해요. TypeScript 5.5의 isolatedDeclarations는 이를 강제해서, 반환 타입 없는 export function total(xs: number[]) { return xs.reduce(...) }에 TS9013을 내요. export const rate = 0.1은 통과해요.

타입 검사가 예상과 다를 때 확인 순서

원인은 대개 몇 가지로 좁혀져요. 위에서부터 확인해요.

타입 검사가 예상과 다를 때 확인 순서. 왼쪽은 틀린 코드가 통과할 때 다섯 단계, 오른쪽은 맞는 코드가 막힐 때 다섯 단계
위에서부터 하나씩 확인하면 대부분 3단계 안에서 원인이 나와요

틀린 코드가 통과할 때

  1. any가 섞였나? JSON.parse, response.json(), 타입 없는 패키지가 흔한 출발점이에요. any는 닿는 값마다 검사를 끄고 퍼져요.
  2. 변수에 담았다가 넘겼나? 오타를 잡는 초과 속성 검사는 호출 자리에 바로 적은 객체에만 돌아요. const opts = { host: "db", timeoutMS: 500 }; connect(opts)는 통과했고, satisfies ConnectOptions를 붙이자 TS2561과 Did you mean to write 'timeoutMs'?가 나왔어요.
  3. 메서드 문법인가? strictFunctionTypes는 handle: (e: X) => void 속성 문법에만 적용되고, handle(e: X): void는 여전히 양방향으로 통과해요. typescript-eslint의 method-signature-style 규칙으로 속성 문법을 강제할 수 있어요.
  4. 같은 원시형의 별칭인가? type UserId = string은 다른 string과 구별되지 않아요. 섞이면 안 되는 값은 브랜드 타입이나 #private 필드를 가진 클래스로 바꿔요.
  5. 인덱스로 꺼낸 값인가? noUncheckedIndexedAccess가 꺼져 있으면 arr[999]도 그냥 number예요.

맞는 코드가 막힐 때

  1. 오류 줄이 진짜 원인인가? 제네릭 호출에서는 먼저 정해진 타입 인자 기준으로 다른 인자에 오류가 찍혀요. 콜백 주석을 지우거나 fn<number>(...)처럼 타입 인자를 직접 줘 봐요.
  2. 선택 속성뿐인 타입인가? 모든 속성이 ?인 타입에 겹치는 속성이 없는 값을 넣으면 TS2559가 나요. { dryrun: true }를 dryRun? 자리에 넣은 경우처럼 대개 진짜 오타예요.
  3. 리터럴이 넓혀졌나? const cfg = { method: "GET" }의 method는 string이라 "GET" | "POST" 자리에 못 들어가요.
  4. 6.0 새 기본값 때문인가? types 기본값이 []가 되어 process, describe 같은 전역 타입이 사라졌을 수 있어요. "types": ["node", "jest"]처럼 적어요.
  5. 검사기 한계인가? TS2589·TS2590·TS2321은 다음 절의 표를 봐요.

타입 검사 한계값: TS2589·TS2590이 나는 숫자

아래 값은 TypeScript 5.9.3 패키지의 lib/typescript.js에서 찾았고, 6.0.3과 7.0.2(Go 포트)에서도 같은 오류가 나는지 돌려 봤어요.

한계값오류실제로 걸리는 코드
타입 인스턴스화 깊이100단계TS2589꼬리 재귀가 아닌 재귀 제네릭
인스턴스화 횟수 (문장 하나)500만 번TS2589거대한 매핑·조건부 타입 조합
꼬리 재귀 조건부 타입1,000번TS2589Tuple<999> 통과, Tuple<1000> 오류
유니온 곱집합 크기10만 개TS2590템플릿 리터럴 ${D}${D}${D}${D}${D}
유니온 중복 제거 비교추정 100만 번TS2590모양이 다른 객체 1,002종 배열 리터럴
튜플 길이1만 개TS2799·TS2800펼치기로 만든 긴 튜플
타입 비교 재귀 깊이100단계TS2321서로 참조하는 깊은 타입 비교

숫자 열 개짜리 유니온 D로 만든 네 자리 PIN 타입은 1만 개라 통과하고, 다섯 자리는 10만 개에 닿아 세 버전 모두 TS2590을 냈어요. 모든 경우를 타입으로 나열하기보다 string에 브랜드를 붙여 검증 함수 한 곳에서만 만들게 하는 편이 나아요.

객체 배열 리터럴의 TS2590

코드 생성기로 만든 폼 필드 정의처럼 원소마다 키가 다른 큰 배열을 코드에 바로 적으면 다섯째 줄에 걸려요. 검사기는 원소 타입으로 유니온을 만들고, 겹치는 것을 지우려고 타입끼리 짝지어 비교해요. 비교 횟수가 타입 종류 수의 제곱으로 늘어요.

세는 단위는 원소 수가 아니라 모양의 종류예요. 배열 안의 리터럴은 string·number로 넓혀져서 키 구성이 같은 객체는 하나로 합쳐져요. 원소 3,000개를 선택 키 조합 32가지로 만들었더니 5.9.3에서 0.33초, 7.0.2에서 0.13초에 통과했어요. 최악의 경우를 보려고 키가 모두 다른 객체 N종을 넣고 Check time(세 번 중 최솟값)을 쟀어요.

모양이 다른 객체 N종 배열 리터럴의 tsc 검사 시간
TypeScript 5.9.3TypeScript 7.0.2
00.511.522004006008001000200 · TypeScript 5.9.3 0.18초400 · TypeScript 5.9.3 0.54초600 · TypeScript 5.9.3 0.59초800 · TypeScript 5.9.3 0.91초1000 · TypeScript 5.9.3 1.9초1.9200 · TypeScript 7.0.2 0.047초400 · TypeScript 7.0.2 0.152초600 · TypeScript 7.0.2 0.344초800 · TypeScript 7.0.2 0.651초1000 · TypeScript 7.0.2 0.818초0.818

1,001종까지 통과하고 1,002종부터 두 버전 모두 TS2590으로 멈춰요. 파일 하나라 7.0의 병렬 검사 이득이 작아요.

단위: 초 · 자료: Apple M1 8코어·메모리 8GB, Node 25.3, 파일 하나로 직접 측정 (2026-10-25)
표로 보기
구분TypeScript 5.9.3TypeScript 7.0.2
2000.180.047
4000.540.152
6000.590.344
8000.910.651
10001.90.818

경계는 두 버전이 똑같았어요. 예상과 달랐던 것은 고치는 방법이에요.

바꾼 방법 (모양이 다른 객체 3,000종)5.9.37.0.2
그대로 [...]TS2590TS2590
const rows: Row[] = [...]TS2590TS2590
[...] satisfies Row[]TS2590TS2590
[...] as const통과 (0.19초)통과 (0.18초)
data.json으로 옮겨 import통과 (0.63초)통과 (0.05초)

주석을 달아도 원소 유니온은 그대로 만들어져서 소용이 없었어요. as const는 원소마다 타입이 따로 정해진 튜플이 되어 중복 제거를 건너뛰어요. 큰 데이터는 JSON 파일로 빼거나, 원소마다 키 구성을 맞추고 없는 값을 null로 채우면 풀려요.

tsc가 느릴 때 확인 순서

  1. tsc --extendedDiagnostics 로 Files, Instantiations, Check time을 봐요. 파일 수가 예상보다 많으면 --explainFiles로 들어온 이유를 확인하고, Instantiations가 수백만이면 제네릭 타입을 의심해요.
  2. tsc --generateTrace <폴더> 로 추적 파일을 만들고 typescript-analyze-trace로 오래 걸린 위치를 뽑아요. 7.0.2의 tsc도 --generateTrace를 지원해요.
  3. 위키 처방은 찾은 위치에만 써요. Performance 문서는 교차 타입 대신 interface ... extends, 반환 타입 주석, 큰 유니온 대신 공통 부모 타입, 복잡한 조건부 타입에 이름 붙이기를 권해요. interface 사이 관계는 캐시되지만 교차 타입 전체는 캐시되지 않는다고 설명해요.
  4. 설정도 확인해요. types를 필요한 패키지로 좁히고 skipLibCheck, incremental을 켜요.

TypeScript 7 도입 전에 따질 것

TypeScript 7.0 발표 글(2026-07-08)은 Go로 옮긴 컴파일러가 보통 전체 빌드에서 8~12배 빠르다고 적어요. npm typescript의 최신 태그는 2026-10-25 현재 7.0.2예요.

TypeScript 6 vs 7 전체 빌드 시간 (공식 발표, --checkers 4)
TypeScript 6TypeScript 7
0255075100125150vscode · TypeScript 6 126초vscode · TypeScript 7 10.6초vscodesentry · TypeScript 6 140초sentry · TypeScript 7 15.7초sentrybluesky · TypeScript 6 24.3초bluesky · TypeScript 7 2.8초blueskyplaywright · TypeScript 6 12.8초playwright · TypeScript 7 1.5초playwrighttldraw · TypeScript 6 11.2초tldraw · TypeScript 7 1.5초tldraw

같은 표에서 메모리는 6~26% 줄었고, --checkers 8이면 vscode가 7.51초까지 내려갔어요.

단위: 초 · 자료: Microsoft TypeScript 블로그, Announcing TypeScript 7.0 (2026-07-08)
표로 보기
구분TypeScript 6TypeScript 7
vscode12610.6
sentry14015.7
bluesky24.32.8
playwright12.81.5
tldraw11.21.5

CI 시간으로 바꿔 보면 vscode 규모 저장소는 한 번에 115.1초(125.7 − 10.6)가 줄어요. 하루 40번, 한 달 22일이면 115.1 × 880 = 101,288초, 약 28시간이에요. 다만 앞의 측정처럼 파일 하나에 몰린 무거운 타입은 나눠 검사할 수 없어 이득이 2~4배에 그쳤어요. 내 저장소에서 먼저 재 보세요.

자주 묻는 질문

strict만 켜면 null 오류는 다 잡히나요?

아니요. arr[i]나 record[key]처럼 인덱스로 꺼낸 값은 strict만으로는 늘 있다고 가정해요. noUncheckedIndexedAccess를 따로 켜야 막혀요.

TS2589 Type instantiation is excessively deep 는 어떻게 고치나요?

재귀 제네릭이 100단계 깊이나 500만 번 인스턴스화에 닿은 거예요. 조건부 타입의 결과 자리에서 바로 자신을 부르는 꼬리 재귀로 바꾸면 한도가 1,000번이 돼요. 그래도 넘치면 런타임 검증으로 옮기는 편이 나아요.

TypeScript 7로 바로 올려도 되나요?

6.0에서 ignoreDeprecations 없이 --stableTypeOrdering을 켜고 깨끗하게 통과하면 7.0에서도 거의 같은 결과가 나온다고 공식 글이 적어요. 린터·테스트 도구가 컴파일러 API를 쓰는지 먼저 확인하세요.

큰 JSON 데이터의 타입은 어떻게 붙이나요?

.json 파일로 두고 resolveJsonModule로 가져오면 검사도 빠르고 TS2590도 피해요. JSON 문법이 깨졌다면 JSON 파싱 에러 원인 6가지부터 확인하세요.

정리

참고한 자료

#타입스크립트 strict 옵션#tsconfig 추천 설정#noUncheckedIndexedAccess#satisfies 언제 쓰나#TS2590 union too complex#TS2589 해결#TypeScript 7 마이그레이션#tsc 느릴 때
← 이전 글인증서 폐기 확인, CRL·OCSP·CRLite 차이와 openssl 점검법다음 글 →vLLM KV 캐시 용량 계산, 동시 요청 수와 GPU 비용으로 설정 고르기

더 둘러보기

💻 개발자 다른 도구

🔥 많이 찾는 도구

🔮 재미로 한 번 더

📝 읽어 볼 만한 글

글 전체 보기 →