Prerequisites
- Basic understanding of TypeScript types ๐
- Familiarity with arrays and objects ๐
- Knowledge of literal types ๐ก
What you'll learn
- Understand const assertions and readonly types ๐ฏ
- Use const assertions to prevent mutations ๐ก๏ธ
- Create immutable data structures ๐๏ธ
- Apply const assertions in real-world scenarios โจ
๐ฏ Introduction
Welcome to the fascinating world of const assertions! ๐ This powerful TypeScript feature lets you create readonly types that prevent unwanted mutations and make your code safer.
Think of const assertions as a way to tell TypeScript: โHey, this data should never change!โ ๐ Itโs like putting a protective shield around your values, ensuring they remain exactly as you defined them.
By the end of this tutorial, youโll be confidently using const assertions to write more robust, type-safe code! Letโs unlock this superpower! ๐
๐ Understanding Const Assertions
๐ค What are Const Assertions?
Const assertions are like a magic spell โจ that transforms mutable types into their readonly equivalents. Think of them as TypeScriptโs way of saying โfreeze this data!โ
In simple terms, const assertions:
- ๐ Make arrays readonly
- ๐ก๏ธ Convert objects to readonly
- ๐ Create literal types instead of wider types
- โก Happen at compile-time (no runtime cost!)
๐ก Why Use Const Assertions?
Hereโs why developers love const assertions:
- Type Safety ๐ก๏ธ: Prevent accidental mutations
- Better IntelliSense ๐ป: More precise autocomplete
- Literal Types ๐: Exact values instead of general types
- Zero Runtime Cost โก: Pure compile-time feature
- Immutable Patterns ๐๏ธ: Functional programming support
Real-world example: Imagine defining a gameโs difficulty levels ๐ฎ. With const assertions, you ensure they never accidentally change!
๐ง Basic Syntax and Usage
๐ The Magic Syntax
The const assertion syntax is beautifully simple:
// โจ The const assertion magic!
const colors = ['red', 'green', 'blue'] as const;
// ๐ฏ Alternative syntax (same result)
const sizes = <const>['small', 'medium', 'large'];
๐ก Pro Tip: Use as const
- itโs more readable and widely adopted!
๐จ Before and After Comparison
Letโs see the transformation:
// โ Without const assertion
const fruits = ['apple', 'banana', 'orange'];
// Type: string[] (mutable array)
// โ
With const assertion
const fruitsReadonly = ['apple', 'banana', 'orange'] as const;
// Type: readonly ["apple", "banana", "orange"] (readonly tuple)
// ๐ฏ See the difference!
fruits.push('grape'); // โ
Allowed
fruitsReadonly.push('grape'); // โ TypeScript error!
๐ก Practical Examples
๐ฎ Example 1: Game Configuration
Letโs build a game config that canโt be accidentally modified:
// ๐ฎ Game difficulty settings
const DIFFICULTY_LEVELS = {
easy: {
name: 'Easy Mode',
multiplier: 1,
emoji: '๐'
},
normal: {
name: 'Normal Mode',
multiplier: 1.5,
emoji: '๐'
},
hard: {
name: 'Hard Mode',
multiplier: 2,
emoji: '๐ฅ'
},
nightmare: {
name: 'Nightmare Mode',
multiplier: 3,
emoji: '๐'
}
} as const;
// ๐ก๏ธ Now it's completely readonly!
type DifficultyLevel = keyof typeof DIFFICULTY_LEVELS;
// Type: "easy" | "normal" | "hard" | "nightmare"
// ๐ฏ Type-safe game class
class Game {
private difficulty: DifficultyLevel = 'normal';
setDifficulty(level: DifficultyLevel): void {
this.difficulty = level;
const config = DIFFICULTY_LEVELS[level];
console.log(`๐ฎ Switched to ${config.name} ${config.emoji}`);
console.log(`๐ Score multiplier: ${config.multiplier}x`);
}
getScoreMultiplier(): number {
return DIFFICULTY_LEVELS[this.difficulty].multiplier;
}
}
// ๐ Let's play!
const game = new Game();
game.setDifficulty('hard'); // โ
Type-safe!
// game.setDifficulty('impossible'); // โ TypeScript error!
๐ Example 2: E-commerce Status System
Letโs create an order status system:
// ๐ฆ Order status configuration
const ORDER_STATUSES = [
{ id: 'pending', label: 'Pending Payment', emoji: 'โณ', color: '#fbbf24' },
{ id: 'paid', label: 'Payment Confirmed', emoji: '๐ณ', color: '#10b981' },
{ id: 'processing', label: 'Processing Order', emoji: '๐', color: '#3b82f6' },
{ id: 'shipped', label: 'Order Shipped', emoji: '๐', color: '#8b5cf6' },
{ id: 'delivered', label: 'Delivered', emoji: '๐ฆ', color: '#06b6d4' },
{ id: 'cancelled', label: 'Cancelled', emoji: 'โ', color: '#ef4444' }
] as const;
// ๐ฏ Extract the status IDs as literal types
type OrderStatusId = typeof ORDER_STATUSES[number]['id'];
// Type: "pending" | "paid" | "processing" | "shipped" | "delivered" | "cancelled"
// ๐๏ธ Order management class
class OrderManager {
private orders: Map<string, OrderStatusId> = new Map();
// ๐ Create new order
createOrder(orderId: string): void {
this.orders.set(orderId, 'pending');
console.log(`๐ Order ${orderId} created with status: โณ Pending`);
}
// ๐ Update order status
updateStatus(orderId: string, newStatus: OrderStatusId): void {
if (!this.orders.has(orderId)) {
console.log(`โ Order ${orderId} not found!`);
return;
}
const statusConfig = ORDER_STATUSES.find(s => s.id === newStatus)!;
this.orders.set(orderId, newStatus);
console.log(`โ
Order ${orderId} updated to: ${statusConfig.emoji} ${statusConfig.label}`);
}
// ๐ Get status info
getStatusInfo(statusId: OrderStatusId) {
return ORDER_STATUSES.find(s => s.id === statusId)!;
}
// ๐ Get all possible statuses
getAllStatuses() {
return ORDER_STATUSES.map(status => ({
id: status.id,
display: `${status.emoji} ${status.label}`
}));
}
}
// ๐ Let's manage some orders!
const orderManager = new OrderManager();
orderManager.createOrder('ORD-001');
orderManager.updateStatus('ORD-001', 'paid'); // โ
Type-safe!
orderManager.updateStatus('ORD-001', 'shipped'); // โ
Perfect!
// orderManager.updateStatus('ORD-001', 'unknown'); // โ TypeScript error!
๐ Advanced Concepts
๐งโโ๏ธ Nested Const Assertions
When you have nested objects, const assertions go deep:
// ๐จ Theme configuration with nested readonly
const THEME_CONFIG = {
colors: {
primary: {
light: '#3b82f6',
dark: '#1e40af',
emoji: '๐ต'
},
secondary: {
light: '#10b981',
dark: '#047857',
emoji: '๐ข'
}
},
fonts: {
heading: 'Inter',
body: 'system-ui',
code: 'Fira Code'
},
spacing: [0, 4, 8, 16, 24, 32, 48, 64] as const
} as const;
// ๐ฏ Everything is deeply readonly!
type ThemeColor = keyof typeof THEME_CONFIG.colors;
type SpacingValue = typeof THEME_CONFIG.spacing[number];
// SpacingValue: 0 | 4 | 8 | 16 | 24 | 32 | 48 | 64
๐๏ธ Const Assertions with Functions
Create readonly return types:
// ๐ Data fetching with const assertions
function createApiEndpoints() {
return {
users: '/api/users',
posts: '/api/posts',
comments: '/api/comments',
auth: {
login: '/api/auth/login',
logout: '/api/auth/logout',
refresh: '/api/auth/refresh'
}
} as const;
}
// ๐ Usage in API client
const ENDPOINTS = createApiEndpoints();
type EndpointPath = typeof ENDPOINTS.users; // "/api/users"
class ApiClient {
async fetchUsers() {
return fetch(ENDPOINTS.users); // โ
Type-safe!
}
async login(credentials: LoginData) {
return fetch(ENDPOINTS.auth.login, {
method: 'POST',
body: JSON.stringify(credentials)
});
}
}
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Forgetting const assertion
// โ Without const assertion - loses specificity
const directions = ['north', 'south', 'east', 'west'];
// Type: string[] (too general!)
function move(direction: 'north' | 'south' | 'east' | 'west') {
console.log(`Moving ${direction}!`);
}
// move(directions[0]); // โ Error: string is not assignable!
// โ
With const assertion - maintains literal types
const directionsConst = ['north', 'south', 'east', 'west'] as const;
// Type: readonly ["north", "south", "east", "west"]
move(directionsConst[0]); // โ
Works perfectly!
๐คฏ Pitfall 2: Modifying const asserted values
// โ Trying to modify readonly data
const settings = {
theme: 'dark',
language: 'en',
notifications: true
} as const;
// settings.theme = 'light'; // โ TypeScript error!
// โ
Create new objects instead
const newSettings = {
...settings,
theme: 'light' as const
};
console.log(`๐จ Theme changed to: ${newSettings.theme}`);
๐ ๏ธ Best Practices
- ๐ฏ Use for Configuration: Perfect for app settings, constants, and enums
- ๐ Prefer as const: More readable than angle bracket syntax
- ๐ Immutable by Design: Plan for immutability from the start
- ๐จ Type-First Thinking: Use const assertions to create precise types
- โก Zero Runtime Cost: Remember itโs compile-time only
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Restaurant Menu System
Create a type-safe restaurant menu system using const assertions:
๐ Requirements:
- ๐ Menu items with name, price, category, and emoji
- ๐ท๏ธ Categories: appetizers, mains, desserts, drinks
- ๐จโ๐ณ Chefโs specials with special pricing
- ๐ Rating system (1-5 stars)
- ๐ Everything should be immutable
๐ Bonus Points:
- Add allergen information
- Create a filtering system by category
- Calculate total bill with tax
๐ก Solution
๐ Click to see solution
// ๐ฝ๏ธ Restaurant menu system with const assertions
const RESTAURANT_MENU = {
categories: {
appetizers: { name: 'Appetizers', emoji: '๐ฅ', order: 1 },
mains: { name: 'Main Courses', emoji: '๐ฝ๏ธ', order: 2 },
desserts: { name: 'Desserts', emoji: '๐ฐ', order: 3 },
drinks: { name: 'Beverages', emoji: '๐ฅค', order: 4 }
},
items: [
{
id: 'app-001',
name: 'Caesar Salad',
price: 12.99,
category: 'appetizers',
emoji: '๐ฅ',
rating: 4.5,
isSpecial: false,
allergens: ['dairy', 'gluten']
},
{
id: 'main-001',
name: 'Grilled Salmon',
price: 24.99,
category: 'mains',
emoji: '๐',
rating: 4.8,
isSpecial: true,
allergens: ['fish']
},
{
id: 'dess-001',
name: 'Chocolate Lava Cake',
price: 8.99,
category: 'desserts',
emoji: '๐ซ',
rating: 4.9,
isSpecial: false,
allergens: ['dairy', 'eggs', 'gluten']
},
{
id: 'drink-001',
name: 'Fresh Lemonade',
price: 4.99,
category: 'drinks',
emoji: '๐',
rating: 4.2,
isSpecial: false,
allergens: []
}
] as const,
tax: {
rate: 0.08,
description: 'Sales Tax'
},
currency: '$'
} as const;
// ๐ฏ Extract types from our const assertion
type MenuCategory = keyof typeof RESTAURANT_MENU.categories;
type MenuItem = typeof RESTAURANT_MENU.items[number];
type MenuItemId = MenuItem['id'];
type Allergen = MenuItem['allergens'][number];
// ๐๏ธ Restaurant ordering system
class RestaurantOrderSystem {
private order: MenuItem[] = [];
// ๐ Display menu by category
displayMenu(category?: MenuCategory): void {
const items = category
? RESTAURANT_MENU.items.filter(item => item.category === category)
: RESTAURANT_MENU.items;
if (category) {
const categoryInfo = RESTAURANT_MENU.categories[category];
console.log(`\n${categoryInfo.emoji} ${categoryInfo.name}`);
console.log('='.repeat(20));
}
items.forEach(item => {
const special = item.isSpecial ? ' โญ SPECIAL' : '';
const stars = 'โญ'.repeat(Math.floor(item.rating));
console.log(`${item.emoji} ${item.name} - ${RESTAURANT_MENU.currency}${item.price}${special}`);
console.log(` Rating: ${stars} (${item.rating})`);
if (item.allergens.length > 0) {
console.log(` โ ๏ธ Contains: ${item.allergens.join(', ')}`);
}
});
}
// โ Add item to order
addToOrder(itemId: MenuItemId): void {
const item = RESTAURANT_MENU.items.find(i => i.id === itemId);
if (item) {
this.order.push(item);
console.log(`โ
Added ${item.emoji} ${item.name} to order`);
} else {
console.log(`โ Item ${itemId} not found`);
}
}
// ๐ Calculate total bill
calculateBill(): void {
console.log('\n๐งพ Order Summary');
console.log('='.repeat(30));
let subtotal = 0;
this.order.forEach(item => {
console.log(`${item.emoji} ${item.name} - ${RESTAURANT_MENU.currency}${item.price}`);
subtotal += item.price;
});
const tax = subtotal * RESTAURANT_MENU.tax.rate;
const total = subtotal + tax;
console.log('โ'.repeat(30));
console.log(`Subtotal: ${RESTAURANT_MENU.currency}${subtotal.toFixed(2)}`);
console.log(`${RESTAURANT_MENU.tax.description}: ${RESTAURANT_MENU.currency}${tax.toFixed(2)}`);
console.log(`๐ฐ Total: ${RESTAURANT_MENU.currency}${total.toFixed(2)}`);
}
// ๐ Filter by allergens
showAllergenFreeItems(excludeAllergens: Allergen[]): void {
console.log('\n๐ฑ Items without specified allergens:');
const safeItems = RESTAURANT_MENU.items.filter(item =>
!item.allergens.some(allergen => excludeAllergens.includes(allergen))
);
safeItems.forEach(item => {
console.log(`${item.emoji} ${item.name} - ${RESTAURANT_MENU.currency}${item.price}`);
});
}
}
// ๐ฝ๏ธ Let's use our restaurant system!
const restaurant = new RestaurantOrderSystem();
// Display full menu
restaurant.displayMenu();
// Add items to order
restaurant.addToOrder('main-001');
restaurant.addToOrder('dess-001');
restaurant.addToOrder('drink-001');
// Show allergen-free options
restaurant.showAllergenFreeItems(['dairy', 'gluten']);
// Calculate the bill
restaurant.calculateBill();
๐ Key Takeaways
Youโve mastered const assertions! Hereโs what you can now do:
- โ
Create readonly types with
as const
๐ - โ Prevent accidental mutations in your code ๐ก๏ธ
- โ Generate precise literal types from data ๐ฏ
- โ Build immutable configurations for apps ๐๏ธ
- โ Type-safe constants that never change โจ
Remember: Const assertions are your friend for creating robust, immutable data structures! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve unlocked the power of const assertions!
Hereโs what to do next:
- ๐ป Practice with the restaurant menu exercise above
- ๐๏ธ Refactor existing code to use const assertions
- ๐ Move on to our next tutorial: Classes in TypeScript
- ๐ Share your immutable data structures with the community!
Remember: Every readonly type starts with as const
. Keep asserting, keep learning! ๐
Happy coding! ๐๐โจ