TypeScript Type Aliases
Type aliases create a new name for an existing type, making your TypeScript code more readable, maintainable, and reusable. They don't create new types but instead provide a way to refer to types with more descriptive names and encapsulate complex type definitions.
Basic Type Alias Syntax
Type aliases use the type
keyword followed by a name, equals sign, and the type definition. The syntax is straightforward: you declare the alias name and assign it any valid TypeScript type. Type aliases can be simple one-liners for basic types or complex multi-line definitions for intricate object structures.
type UserId = number;
// Using the type alias
const id: UserId = 123456;
This simple example might not seem very useful, but type aliases become powerful when you work with more complex types:
More Useful Type Alias
type Coordinates = {
x: number;
y: number;
};
// Using the type alias
function calculateDistance(point1: Coordinates, point2: Coordinates): number {
return Math.sqrt(Math.pow(point2.x - point1.x, 2) + Math.pow(point2.y - point1.y, 2));
}
const start: Coordinates = { x: 0, y: 0 };
const end: Coordinates = { x: 3, y: 4 };
console.log(calculateDistance(start, end)); // 5
Union Types
Union types within aliases use the pipe symbol |
to separate possible types, creating a type that accepts any of the specified options. This is perfect for variables that can legitimately hold different types of values, like API responses that return either data or error objects, or configuration values that accept multiple formats.
type ID = string | number;
function printID(id: ID) {
console.log(`ID: ${id}`);
// Type narrowing
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id.toFixed(0));
}
}
printID(101); // Valid
printID("202"); // Also valid
// printID(true); // Error: Type 'boolean' is not assignable to type 'ID'
Intersection Types
Intersection types combine multiple types using the ampersand symbol &
, creating a new type that must satisfy all constituent types simultaneously. They're useful for mixing object types together, adding properties to existing types, or creating composite types that merge different interfaces or type definitions.
type Person = {
name: string;
age: number;
};
type Employee = {
employeeId: string;
department: string;
};
type EmployeeRecord = Person & Employee;
const employee: EmployeeRecord = {
name: "John Smith",
age: 35,
employeeId: "E123",
department: "Engineering",
};
Literal Types
Literal type aliases restrict values to specific constants rather than general types. String literals create types that only accept exact string values, number literals constrain to specific numbers, and boolean literals limit to true or false. They're essential for creating precise constraints and enum-like behavior without actual enums.
type Direction = "North" | "East" | "South" | "West";
function move(direction: Direction, steps: number) {
console.log(`Moving ${steps} steps to the ${direction}`);
}
move("North", 3); // Valid
// move("Up", 3); // Error: Argument of type '"Up"' is not assignable to parameter of type 'Direction'
Type Aliases for Functions
Function type aliases define the shape of functions including parameter types, return types, and optional parameters. They create reusable function signatures that can be applied to variables, parameters, or properties. These aliases support overloads, generic parameters, and rest parameters, making them powerful for defining consistent API contracts and callback interfaces throughout your application.
type MathOperation = (a: number, b: number) => number;
const add: MathOperation = (a, b) => a + b;
const subtract: MathOperation = (a, b) => a - b;
const multiply: MathOperation = (a, b) => a * b;
const divide: MathOperation = (a, b) => a / b;
function performOperation(a: number, b: number, operation: MathOperation): number {
return operation(a, b);
}
console.log(performOperation(10, 5, add)); // 15
console.log(performOperation(10, 5, subtract)); // 5
Type Aliases vs. Interfaces
While type aliases and interfaces can be used in similar ways for defining object shapes, they have some key differences.
When to Use Type Aliases
When working with union types or literal types:
Interfaces cannot represent union types directly. Type aliases are the only way to create types that accept multiple different structural possibilities, making them essential for modeling data that can have different shapes based on context or state.
type Result = Success | Error;
type HttpStatus = 200 | 201 | 400 | 404 | 500;
When you need a new name for an existing type:
Interfaces cannot constrain values to specific constants. Type aliases enable creating types that only accept exact string, number, or boolean values, which is crucial for creating precise value constraints and enum-like behavior.
type UserId = string;
When working with tuples:
Interfaces cannot represent fixed-length arrays with specific types at each position. Type aliases are required for modeling structured data like coordinates, database rows, or any scenario requiring exact array shapes with known element types.
type Point = [number, number];
When you're creating function signatures:
While interfaces can define function shapes through call signatures, type aliases provide cleaner, more direct syntax for standalone function type definitions. They're preferred for callback types, event handlers, and reusable function contracts.
type Callback = (data: string) => void;
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
When using primitive types:
Interfaces only work with object structures. Type aliases are necessary for creating semantic names for strings, numbers, booleans, or any non-object type, enabling better code documentation and type safety for simple values.
type Age = number;
type Email = string;
General Rule
Use interfaces for object shapes and extensible APIs. Use type aliases for everything else: unions, intersections, primitives, tuples, functions, and complex type computations that interfaces cannot express.
Real-World Examples
API Response Types
Type aliases are excellent for modeling API responses and ensuring type safety:
type User = {
id: number;
username: string;
email: string;
};
type ApiResponse = {
data: User;
status: number;
message: string;
timestamp: string;
};
// Using the types
async function fetchUser(id: number): Promise<ApiResponse> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// Usage
fetchUser(123).then((response) => {
if (response.status === 200) {
const user = response.data;
console.log(`Found user: ${user.username}`);
}
});
State Management
Type aliases help manage state in applications:
// Define the structure of our state
type User = {
id: string;
name: string;
email: string;
};
type AuthState = {
isAuthenticated: boolean;
user: User | null;
token: string | null;
error: string | null;
};
// Initial state
const initialState: AuthState = {
isAuthenticated: false,
user: null,
token: null,
error: null,
};
// Function to update state
function login(state: AuthState, user: User, token: string): AuthState {
return {
...state,
isAuthenticated: true,
user: user,
token: token,
error: null,
};
}
// Usage
const updatedState = login(
initialState,
{
id: "123",
name: "John Doe",
email: "john@example.com",
},
"auth-token-123"
);
Best Practices for Type Aliases
Use PascalCase for naming type aliases
PascalCase maintains consistency with TypeScript conventions for types, classes, and interfaces. This naming pattern immediately identifies type constructs versus variables or functions, creating visual clarity in your codebase and aligning with community standards and popular style guides.
type UserProfile = { ... }; // Good
type userProfile = { ... }; // Not ideal
Be specific with your type names
Descriptive names prevent confusion and make code self-documenting. Vague names like "Data" or "Info" provide no context about what the type represents or how it should be used. Specific names like "UserProfile" or "ApiResponse" immediately communicate purpose and domain, making code easier to understand and maintain.
// Too vague
type Data = { ... };
// More specific and clearer
type UserData = { ... };
Use type aliases for complex or reused types
Type aliases eliminate repetition and create single sources of truth for complex type definitions. When the same type structure appears multiple times, aliases ensure consistency, make updates easier, and improve code maintainability. They're particularly valuable for intricate object shapes, union types, or computed types that would be unwieldy to repeat.
// Define once, use many times
type Coordinates = {
x: number;
y: number;
};
function calculateDistance(point1: Coordinates, point2: Coordinates): number { ... }
function movePoint(point: Coordinates, direction: string, distance: number): Coordinates { ... }
Choose Types vs Interfaces Strategically
Interfaces excel for object shapes that might be extended, implemented by classes, or need declaration merging. Type aliases are superior for unions, intersections, primitives, tuples, and any scenario where interfaces cannot express the required type structure. The choice should reflect the type's intended usage and extensibility needs.
// Better as an interface if it might be extended
interface ApiClient {
get(url: string): Promise<unknown>;
post(url: string, data: unknown): Promise<unknown>;
}
// Better as a type alias
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
Document your types with JSDoc
JSDoc comments provide essential context that type signatures alone cannot convey. They explain business rules, document valid value ranges, provide usage examples, and clarify intent for complex types. Good documentation makes types self-explanatory and reduces cognitive load when working with unfamiliar code.
/**
* Represents a response from the API
*/
type ApiResponse = {
/** The returned data */
data: unknown;
/** HTTP status code */
status: number;
/** Success or error message */
message: string;
};
Exercises
Exercise 1: Basic Type Aliases
Create a few basic type aliases:
A type alias for a string ID
A type alias for a coordinate with x and y properties
A type alias for a configuration object with properties
apiUrl
(string),timeout
(number), anddebug
(boolean)
Exercise 2: Union and Literal Types
Create a type alias for a Status
that can only be one of these string literals: "pending", "processing", "completed", "failed".
Then, create a function called updateStatus
that takes an ID and a Status
as parameters, and returns a message confirming the update.
Exercise 3: Intersection Types
Create two separate type aliases:
Person
with propertiesname
(string),age
(number)Worker
with propertiesjobTitle
(string),employeeId
(string)
Then, create a third type alias Employee
that is an intersection of Person
and Worker
. Create a sample Employee
object.