---
id: wiki-2026-0508-타입-좁히기-type-narrowing
title: 타입 좁히기 (Type Narrowing)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Type Narrowing, Control Flow Analysis, CFA]
duplicate_of: none
source_trust_level: A
confidence_score: 0.9
verification_status: applied
tags: [typescript, type-system, narrowing, control-flow]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
  language: typescript
  framework: typescript-5.5+
---

# 타입 좁히기 (Type Narrowing)

## 매 한 줄
> **"매 control flow 의 type 의 refine"**. Type narrowing 은 TypeScript compiler 가 conditional branch, equality check, predicate call 등을 추적하여 union type 을 점점 더 specific 한 type 으로 축소하는 process. 매 control flow analysis (CFA) 의 결과로, 매 runtime check 의 동일 코드가 type level 에서도 evidence 의 carry — 매 type system 의 ergonomic core.

## 매 핵심

### 매 narrowing trigger
1. **`typeof`**: primitive 7종.
2. **`instanceof`**: class.
3. **`in`** operator: property existence.
4. **Equality** (`===`, `!==`): literal narrowing.
5. **Truthiness**: `if (x)` — null/undefined/0/'' 의 strip.
6. **User-defined predicate**: `x is T`.
7. **`asserts`**: throw-or-narrow.
8. **Discriminated union**: `kind` field 의 switch.
9. **Array.length**: `if (arr.length > 0)` 의 tuple narrowing (제한적).
10. **Aliased condition** (TS 4.4+): `const isStr = typeof x === 'string'; if (isStr) ...`.

### 매 narrowing 의 scope
- `if` block 안에서만 유효.
- Closure 의 capture 의 후 narrowing 의 lost (callback 안의 `x` 의 다시 union).
- `let` reassign 의 후 narrowing 의 reset.

### 매 응용
1. Optional handling — `if (user)` 후 `user.name`.
2. Discriminated union 의 exhaustive switch.
3. Error type 의 narrowing (`if (err instanceof HttpError)`).
4. Array filter null strip.

## 💻 패턴

### typeof narrowing
```typescript
function pad(x: string | number) {
  if (typeof x === 'number') return ' '.repeat(x)  // x: number
  return x.padStart(10)                             // x: string
}
```

### Discriminated union (가장 idiomatic)
```typescript
type Shape =
  | { kind: 'circle'; r: number }
  | { kind: 'square'; side: number }
  | { kind: 'rect'; w: number; h: number }

function area(s: Shape): number {
  switch (s.kind) {
    case 'circle': return Math.PI * s.r ** 2
    case 'square': return s.side ** 2
    case 'rect':   return s.w * s.h
    default: {
      const _exhaustive: never = s  // compile error if Shape extended
      throw new Error('unreachable')
    }
  }
}
```

### Truthiness narrowing
```typescript
function greet(name: string | null | undefined) {
  if (name) {
    name.toUpperCase()  // name: string
  }
}
```

### `in` operator
```typescript
type Fish = { swim: () => void }
type Bird = { fly: () => void }

function move(p: Fish | Bird) {
  if ('swim' in p) p.swim()
  else p.fly()
}
```

### instanceof
```typescript
class HttpError extends Error { status: number = 500 }

try {
  await fetch(url)
} catch (e) {
  if (e instanceof HttpError) console.error(e.status)  // narrowed
  else if (e instanceof Error) console.error(e.message)
  else console.error('unknown', e)
}
```

### Aliased condition (TS 4.4+)
```typescript
function process(x: string | number[]) {
  const isArray = Array.isArray(x)
  if (isArray) {
    x.length     // x: number[]
  } else {
    x.charAt(0)  // x: string
  }
}
```

### TS 5.5 inferred predicate (자동 narrowing in filter)
```typescript
const mixed: (string | null)[] = ['a', null, 'b']
const strings = mixed.filter(x => x !== null)
//    ^? string[]  (TS 5.5+ — previously (string | null)[])
```

### Closure 의 narrowing 의 loss
```typescript
function example(x: string | null) {
  if (x === null) return
  x.toUpperCase()  // OK

  setTimeout(() => {
    x.toUpperCase()  // ERROR: x might be null again (closure)
  })

  // Fix:
  const fixed = x  // const captured — narrowing preserved
  setTimeout(() => fixed.toUpperCase())
}
```

### never 의 exhaustiveness
```typescript
type Action = { t: 'load' } | { t: 'save' }

function handle(a: Action) {
  switch (a.t) {
    case 'load': return load()
    case 'save': return save()
    default: {
      const _: never = a  // compile error if Action grows
      throw new Error()
    }
  }
}
```

### Optional chaining + narrowing
```typescript
type User = { profile?: { email?: string } }

function send(u: User) {
  if (u.profile?.email) {
    u.profile.email  // string (not undefined)
  }
}
```

## 매 결정 기준
| 상황 | Narrowing approach |
|---|---|
| Union of primitives | `typeof` |
| Class hierarchy | `instanceof` |
| Object shape variants | Discriminated union (`kind` field) |
| Property existence check | `in` |
| Custom validation | User-defined predicate |
| Throw-or-narrow | `asserts` |
| Filter null/undefined | TS 5.5 의 auto, 또는 explicit predicate |
| Exhaustiveness | `never` 의 default branch |

**기본값**: Discriminated union 의 design — 매 narrowing 의 free. 매 `never` exhaustiveness check 의 매 switch 에 추가.

## 🔗 Graph
- 부모: [[TypeScript Type System]]
- 변형: [[Type Predicates]]
- 응용: [[Discriminated_Unions|Discriminated Unions]] · [[Error Handling]]
- Adjacent: [[Control Flow Analysis]] · [[Exhaustiveness Checking]]

## 🤖 LLM 활용
**언제**: Union type 의 specific branch handling. 매 LLM 의 generate 한 switch 의 `never` exhaustiveness 의 추가 의 verify.
**언제 X**: 매 단순 single-type 의 over-narrowing 의 unnecessary.

## ❌ 안티패턴
- **`as` cast 의 abuse**: narrowing 의 가능한 곳에서 `x as User` — unsafe.
- **`!` non-null assertion overuse**: `x!.foo` — narrowing 의 알려주는 게 좋음.
- **Closure 의 narrowing 의 expectation**: `let` mutable 의 narrowing 의 reset.
- **No exhaustive check**: discriminated union 의 grow 시 silent miss.

## 🧪 검증 / 중복
- Verified (TypeScript handbook — Narrowing chapter 2026).
- 신뢰도 A.

## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — narrowing patterns + TS 5.5 inferred predicates + exhaustiveness |
