TypeScript Interfaces | Complete Guide
이 글의 핵심
Interfaces in TypeScript: optional and readonly props, call signatures, index signatures, extends, declaration merging, implements, and when to prefer interface vs type alias.
Introduction
An interface describes the shape of objects in TypeScript.
1. Interface basics
Declaration
An interface lists the properties an object must have and their types:
// User interface — defines the object "contract"
interface User {
id: string;
name: string;
age: number;
email: string;
}
// ✅ Valid: all required properties provided
const user: User = {
id: "U001",
name: "Alice",
age: 25,
email: "[email protected]"
};
// ❌ Missing properties
// const user2: User = { id: "U002", name: "Bob" };
// ❌ Wrong types
// const user3: User = { ..., age: "25", ....};
// ❌ Unknown extra properties (in object literals with explicit type)
// const user4: User = { ..., phone: "..." };
What interfaces give you:
- Type checking: validate object structure at compile time
- Autocomplete: IDE suggests property names
- Documentation: the shape is explicit
- Refactoring: rename or change types with confidence
Optional properties
Use ? for optional properties:
interface User {
id: string;
name: string;
age?: number;
email?: string;
}
// ✅ OK without optional fields
const user1: User = {
id: "U001",
name: "Alice"
};
// ✅ OK with optional fields
const user2: User = {
id: "U002",
name: "Bob",
age: 30,
email: "[email protected]"
};
function printAge(user: User) {
if (user.age !== undefined) {
console.log(user.age.toFixed(0));
}
console.log(user.age?.toFixed(0));
const age = user.age ?? 0;
console.log(age);
}
printAge(user1);
printAge(user2);
Why optional properties help:
- Flexible shapes
- Partial updates
- APIs where some fields may be absent
Readonly properties
readonly marks properties that must not be reassigned after initialization:
// 타입 정의
interface User {
readonly id: string;
name: string;
age: number;
}
const user: User = {
id: "U001",
name: "Alice",
age: 25
};
user.name = "Bob"; // ✅
user.age = 30; // ✅
// user.id = "U002"; // ❌ error
interface Post {
readonly id: string;
readonly createdAt: Date;
title: string;
content: string;
updatedAt: Date;
}
const post: Post = {
id: "POST001",
createdAt: new Date(),
title: "First post",
content: "Body",
updatedAt: new Date()
};
post.title = "Updated title";
post.content = "Updated body";
post.updatedAt = new Date();
readonly vs const:
readonly: property on an objectconst: binding for a variable
const user: User = { id: "U001", name: "Alice", age: 25 };
// user cannot be reassigned
// user.id cannot be reassigned
2. Function types in interfaces
Methods
interface Calculator {
add(a: number, b: number): number;
subtract(a: number, b: number): number;
multiply?(a: number, b: number): number;
}
const calc: Calculator = {
add(a, b) {
return a + b;
},
subtract(a, b) {
return a - b;
}
};
console.log(calc.add(10, 5));
console.log(calc.subtract(10, 5));
Call signatures
interface MathOperation {
(a: number, b: number): number;
}
const add: MathOperation = (a, b) => a + b;
const multiply: MathOperation = (a, b) => a * b;
console.log(add(10, 5));
console.log(multiply(10, 5));
Constructor types
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick(): void;
}
class DigitalClock implements ClockInterface {
constructor(h: number, m: number) {}
tick() {
console.log("beep beep");
}
}
function createClock(
ctor: ClockConstructor,
hour: number,
minute: number
): ClockInterface {
return new ctor(hour, minute);
}
const clock = createClock(DigitalClock, 12, 17);
clock.tick();
3. Index signatures
String index
interface StringMap {
[key: string]: string;
}
const colors: StringMap = {
red: "#FF0000",
green: "#00FF00",
blue: "#0000FF"
};
console.log(colors[red]);
console.log(colors.green);
Numeric index
interface NumberArray {
[index: number]: string;
}
const fruits: NumberArray = ["apple", "banana", "orange"];
console.log(fruits[0]);
console.log(fruits[1]);
Mixed constraints
interface Dictionary {
[key: string]: string | number;
length: number;
}
const dict: Dictionary = {
name: "Alice",
age: 25,
length: 2
};
4. Extending interfaces
extends
interface Person {
name: string;
age: number;
}
interface Employee extends Person {
employeeId: string;
department: string;
}
const employee: Employee = {
name: "Alice",
age: 30,
employeeId: "E001",
department: "Engineering"
};
Multiple inheritance
interface Timestamped {
createdAt: Date;
updatedAt: Date;
}
interface Identifiable {
id: string;
}
interface User extends Identifiable, Timestamped {
name: string;
email: string;
}
const user: User = {
id: "U001",
name: "Alice",
email: "[email protected]",
createdAt: new Date(),
updatedAt: new Date()
};
5. Interface merging
Declaration merging
interface User {
name: string;
}
interface User {
age: number;
}
const user: User = {
name: "Alice",
age: 25
};
Augmenting globals
interface Window {
myCustomProperty: string;
}
window.myCustomProperty = "Hello!";
console.log(window.myCustomProperty);
6. Classes and interfaces
implements
interface Animal {
name: string;
makeSound(): void;
}
class Dog implements Animal {
name: string;
constructor(name: string) {
this.name = name;
}
makeSound() {
console.log("Woof!");
}
}
const dog = new Dog("Buddy");
dog.makeSound();
Multiple interfaces
interface Flyable {
fly(): void;
}
interface Swimmable {
swim(): void;
}
class Duck implements Flyable, Swimmable {
fly() {
console.log("Flying!");
}
swim() {
console.log("Swimming!");
}
}
const duck = new Duck();
duck.fly();
duck.swim();
7. Interface vs type alias
Comparison
| Feature | Interface | Type alias |
|---|---|---|
| Object shapes | ✅ | ✅ |
| Union / intersection | ❌ (use type) | ✅ |
| Extension | extends | & |
| Declaration merging | ✅ | ❌ |
| Primitive aliases | ❌ | ✅ |
Examples
interface User {
name: string;
}
interface User {
age: number;
}
type Person = {
name: string;
};
type ID = string | number;
type Status = "active" | "inactive";
type Employee = Person & {
employeeId: string;
};
8. Hands-on examples
Example 1: API response
interface ApiResponse<T> {
success: boolean;
data: T;
error?: string;
timestamp: Date;
}
interface User {
id: string;
name: string;
email: string;
}
async function fetchUser(id: string): Promise<ApiResponse<User>> {
try {
const response = await fetch(`/api/users/${id}`);
const data = await response.json();
return {
success: true,
data,
timestamp: new Date()
};
} catch (error) {
return {
success: false,
data: null as any,
error: "Request failed",
timestamp: new Date()
};
}
}
Example 2: Form validation
interface FormField {
value: string;
error: string | null;
touched: boolean;
}
interface LoginForm {
email: FormField;
password: FormField;
}
const form: LoginForm = {
email: {
value: "",
error: null,
touched: false
},
password: {
value: "",
error: null,
touched: false
}
};
function validateEmail(email: string): string | null {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email) ? null : "Invalid email format";
}
form.email.value = "[email protected]";
form.email.error = validateEmail(form.email.value);
form.email.touched = true;
Example 3: Event handlers
interface ClickEvent {
x: number;
y: number;
button: "left" | "right";
}
interface EventHandler<T> {
(event: T): void;
}
const handleClick: EventHandler<ClickEvent> = (event) => {
console.log(`Click: (${event.x}, ${event.y}), button: ${event.button}`);
};
handleClick({ x: 100, y: 200, button: "left" });
Summary
Takeaways
- Interface: describe object structure
- Optional:
? - Readonly:
readonly - Extension:
extends(multiple allowed) - Implementation:
implements(multiple allowed) - Merging: declaration merging
When to use interfaces
- Object models
- Class contracts
- Augmenting libraries
- API response types
Next steps
- [TypeScript generics](/en/blog/typescript-series-04-generics/
- TypeScript utility types
- TypeScript decorators
Related posts
- [Advanced TypeScript types | Union, intersection, literals](/en/blog/typescript-series-02-types/
- [TypeScript generics | Complete guide](/en/blog/typescript-series-04-generics/
- C++ Adapter pattern | Interface bridging
- C++ numeric_limits
- C++ interfaces and PIMPL | ABI and compile-time boundaries
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Interfaces in TypeScript: optional and readonly props, call signatures, index signatures, extends, declaration merging, … 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- [Advanced TypeScript Types | Union· Intersection](/en/blog/typescript-series-02-types/
- [TypeScript Generics | Complete Guide](/en/blog/typescript-series-04-generics/
- [TypeScript Utility Types | Partial· Pick](/en/blog/typescript-series-05-utility-types/
이 글에서 다루는 키워드 (관련 검색어)
TypeScript, Interface, extends, implements, Declaration Merging 등으로 검색하시면 이 글이 도움이 됩니다.