---
id: wiki-2026-0508-브랜디드-타입-branded-types
title: 브랜디드 타입 (Branded Types)
category: 10_Wiki/Topics
status: verified
canonical_id: self
aliases: [Nominal Types in TS, Opaque Types, Tagged Types, Branded Primitives]
duplicate_of: none
source_trust_level: A
confidence_score: 0.95
verification_status: applied
tags: [typescript, type-system, nominal-typing, type-safety]
raw_sources: []
last_reinforced: 2026-05-10
github_commit: pending
tech_stack:
  language: TypeScript
  framework: TypeScript 5.7+
---

# 브랜디드 타입 (Branded Types)

## 매 한 줄
> **"매 structural type system 에 nominal flavor 을 주입"**. TypeScript 의 structural typing 이 매 `UserId` 와 `OrderId` 를 동일하게 취급하는 문제를 매 phantom type tag 로 분리, 매 compile-time 에 매 ID mix-up 을 차단. 2026 년 zod/effect/fp-ts 모두 매 branded primitive 를 first-class 지원.

## 매 핵심

### 매 정의
- 매 `type Branded<T, B> = T & { readonly __brand: B }`.
- 매 runtime 매 cost 0 — 매 type-only construct.
- 매 `__brand` field 매 실제 X — 매 phantom marker.

### 매 variant
- **Unique symbol brand**: 매 `unique symbol` 로 매 collision-proof.
- **String literal brand**: 매 simple, 매 collision 가능.
- **Multi-brand**: 매 `T & Brand1 & Brand2` — 매 trait-like.
- **Effect-style branded**: 매 `Brand<"UserId">` helper.

### 매 응용
1. ID type segregation (UserId vs OrderId).
2. Validated input (Email, URL, NonEmptyString).
3. Unit-of-measure (Meters, Seconds, USD).
4. Capability tokens (AdminToken, ReadOnlyToken).

## 💻 패턴

### Basic branded ID
```ts
declare const __brand: unique symbol;
type Brand<T, B> = T & { readonly [__brand]: B };

type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;

// 매 constructor (smart constructor pattern)
function UserId(s: string): UserId {
  if (!/^u_[0-9a-f]{16}$/.test(s)) throw new Error("invalid UserId");
  return s as UserId;
}

// 매 usage
function getUser(id: UserId) { /* ... */ }

const u = UserId("u_0123456789abcdef");
const o = "o_xxx" as OrderId;
getUser(u);   // ok
getUser(o);   // 매 Type error — 매 brand mismatch
getUser("u_0123456789abcdef"); // 매 Type error — 매 raw string
```

### Validated string types
```ts
type Email = Brand<string, "Email">;
type URL = Brand<string, "URL">;
type NonEmpty = Brand<string, "NonEmpty">;

function parseEmail(s: string): Email {
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s))
    throw new TypeError(`not an email: ${s}`);
  return s as Email;
}

function sendMail(to: Email, body: NonEmpty) { /* ... */ }

// 매 caller 매 validate 강제
sendMail(parseEmail(input), parseNonEmpty(body));
```

### Zod branded (idiomatic 2026)
```ts
import { z } from "zod";

const UserIdSchema = z.string().uuid().brand<"UserId">();
const OrderIdSchema = z.string().uuid().brand<"OrderId">();

type UserId = z.infer<typeof UserIdSchema>;
type OrderId = z.infer<typeof OrderIdSchema>;

const id = UserIdSchema.parse(req.params.id);
// 매 id: UserId — 매 typesafe + runtime validated
```

### Unit-of-measure
```ts
type Meters = Brand<number, "Meters">;
type Seconds = Brand<number, "Seconds">;
type MetersPerSec = Brand<number, "MetersPerSec">;

function meters(n: number): Meters { return n as Meters; }
function seconds(n: number): Seconds { return n as Seconds; }

function speed(d: Meters, t: Seconds): MetersPerSec {
  return (d / t) as MetersPerSec;
}

speed(meters(100), seconds(10));   // ok
speed(seconds(10), meters(100));   // 매 type error — 매 unit mismatch
```

### Effect-TS Brand module
```ts
import { Brand } from "effect";

type Int = number & Brand.Brand<"Int">;
const Int = Brand.refined<Int>(
  (n) => Number.isInteger(n),
  (n) => Brand.error(`${n} is not an integer`),
);

const x = Int(42);   // 매 Int
const y = Int(3.14); // 매 throws BrandErrors

// 매 multi-brand combine
type PositiveInt = Int & Brand.Brand<"Positive">;
const PositiveInt = Brand.all(
  Int,
  Brand.refined<number & Brand.Brand<"Positive">>(
    (n) => n > 0,
    (n) => Brand.error(`${n} is not positive`),
  ),
);
```

### Capability tokens
```ts
type ReadToken = Brand<string, "ReadToken">;
type WriteToken = Brand<string, "WriteToken">;
type AdminToken = ReadToken & WriteToken & Brand<string, "Admin">;

function readBlob(token: ReadToken, key: string) { /* ... */ }
function writeBlob(token: WriteToken, key: string, val: Buffer) { /* ... */ }
function deleteBucket(token: AdminToken) { /* ... */ }

// 매 read-only client 매 deleteBucket 호출 X
```

## 매 결정 기준
| 상황 | Brand 사용 |
|---|---|
| 동종 primitive 의 의미 차이 (UserId vs OrderId) | **Yes** |
| 매 validated string (Email, URL) | **Yes** (smart constructor) |
| 매 unit-of-measure | **Yes** |
| 매 단순 internal helper, 매 1 callsite | No (overhead 큼) |
| 매 cross-team API boundary | **Yes** |
| 매 runtime parsing 필요 | Brand + Zod/Effect |

**기본값**: ID + validated input 매 brand. 매 internal scratch 매 X.

## 🔗 Graph
- 부모: [[TypeScript Type System]] · [[Nominal-Typing-in-TypeScript|Nominal Typing]]
- 변형: [[Opaque Types (TypeScript)]]
- Adjacent: [[Parse Don't Validate]]

## 🤖 LLM 활용
**언제**: ID confusion 우려, 매 validated input 강제, 매 unit safety, 매 capability-based security.
**언제 X**: 매 prototyping, 매 single-file script, 매 brand 가 매 ergonomic cost > safety gain.

## ❌ 안티패턴
- **`as Brand` 직접 cast 남발**: 매 smart constructor 우회 — 매 brand 무의미.
- **String literal brand collision**: 매 `Brand<string, "Id">` 여러 module 에서 매 동일 이름 — 매 unique symbol 사용.
- **Branded-everything**: 매 모든 string 매 brand — 매 codebase ergonomic 파괴.
- **Runtime brand check**: 매 `__brand` 가 runtime 에 존재한다고 가정 — 매 phantom 매 X.

## 🧪 검증 / 중복
- Verified (TypeScript Handbook nominal typing, Effect Brand docs, Zod v3 brand API).
- 신뢰도 A.

## 🕓 Changelog
| 날짜 | 변경 |
|---|---|
| 2026-05-08 | Phase 1 |
| 2026-05-10 | Manual cleanup — Effect/Zod modern patterns + 6 examples |
