Prerequisites
- Basic TypeScript types knowledge ๐
- Understanding of JavaScript arrays โก
- TypeScript development environment ๐ป
What you'll learn
- Master array types and operations ๐ฏ
- Understand tuples for fixed-length collections ๐๏ธ
- Apply array methods with type safety ๐
- Build real-world collection handling โจ
๐ฏ Introduction
Welcome to the wonderful world of TypeScript arrays and tuples! ๐ In this guide, weโll explore how to work with collections of data in a type-safe way that makes your code bulletproof.
Think of arrays as your trusty toolbox ๐งฐ - they hold multiple items of the same type. Tuples are like a perfectly organized drawer ๐๏ธ - each slot has a specific purpose and type. Together, theyโre your dynamic duo for handling collections in TypeScript!
By the end of this tutorial, youโll be confidently managing lists, coordinates, shopping carts, and more! Letโs dive in! ๐โโ๏ธ
๐ Understanding Arrays and Tuples
๐ค What Are Arrays in TypeScript?
Arrays in TypeScript are like supercharged JavaScript arrays ๐. Theyโre ordered collections that can grow or shrink, but with one crucial difference - TypeScript ensures all elements are of the same type!
In TypeScript, arrays give you:
- โจ Type safety for all elements
- ๐ Better autocomplete and IntelliSense
- ๐ก๏ธ Protection from type-related bugs
- ๐ Self-documenting code
๐ก What Are Tuples?
Tuples are TypeScriptโs special arrays with a twist ๐ช๏ธ. They have:
- ๐ Fixed length
- ๐ฏ Specific type for each position
- ๐ Order matters!
Think of tuples like coordinates on a map ๐บ๏ธ - you need exactly two numbers (latitude, longitude) in that specific order!
๐ง Working with Arrays
๐ Array Basics
Letโs start with the fundamentals:
// ๐จ Two ways to declare arrays
let numbers: number[] = [1, 2, 3, 4, 5];
let fruits: Array<string> = ["apple", "banana", "orange"];
// ๐ TypeScript infers the type
let scores = [95, 87, 92]; // TypeScript knows it's number[]
// โจ Mixed arrays need union types
let mixed: (string | number)[] = ["hello", 42, "world", 100];
// ๐ฏ Array of objects
interface Player {
name: string;
score: number;
emoji: string;
}
let leaderboard: Player[] = [
{ name: "Alice", score: 9500, emoji: "๐ฅ" },
{ name: "Bob", score: 8700, emoji: "๐ฅ" },
{ name: "Charlie", score: 8200, emoji: "๐ฅ" }
];
๐ฎ Array Methods with Type Safety
TypeScript makes array methods safer and smarter:
// ๐ Shopping list example
interface Item {
id: number;
name: string;
price: number;
emoji: string;
quantity: number;
}
let shoppingCart: Item[] = [
{ id: 1, name: "TypeScript Book", price: 39.99, emoji: "๐", quantity: 1 },
{ id: 2, name: "Coffee", price: 4.99, emoji: "โ", quantity: 3 },
{ id: 3, name: "Mechanical Keyboard", price: 149.99, emoji: "โจ๏ธ", quantity: 1 }
];
// ๐ฏ Map - transform each element
const itemNames: string[] = shoppingCart.map(item =>
`${item.emoji} ${item.name}`
);
console.log("Items:", itemNames);
// Output: ["๐ TypeScript Book", "โ Coffee", "โจ๏ธ Mechanical Keyboard"]
// ๐ฐ Reduce - calculate total
const total: number = shoppingCart.reduce((sum, item) =>
sum + (item.price * item.quantity), 0
);
console.log(`Total: $${total.toFixed(2)} ๐ธ`);
// ๐ Filter - find expensive items
const expensiveItems: Item[] = shoppingCart.filter(item =>
item.price > 50
);
console.log("Expensive items:", expensiveItems.map(i => i.emoji));
// โจ Find - get first match
const coffeeItem: Item | undefined = shoppingCart.find(item =>
item.name.includes("Coffee")
);
if (coffeeItem) {
console.log(`Found ${coffeeItem.emoji} at $${coffeeItem.price}`);
}
// ๐จ Some and Every
const hasExpensiveItem: boolean = shoppingCart.some(item => item.price > 100);
const allUnderBudget: boolean = shoppingCart.every(item => item.price < 200);
๐๏ธ Multi-dimensional Arrays
Arrays can contain other arrays:
// ๐ฎ Game board (Tic-Tac-Toe)
type Cell = "X" | "O" | " ";
type Board = Cell[][];
let gameBoard: Board = [
["X", "O", "X"],
["O", "X", "O"],
[" ", " ", "X"]
];
// ๐บ๏ธ Map coordinates
type Coordinate = [number, number];
let treasureMap: Coordinate[] = [
[10, 20],
[30, 40],
[50, 60]
];
// ๐ Data matrix
let salesData: number[][] = [
[100, 150, 200], // Q1
[120, 180, 220], // Q2
[140, 200, 250], // Q3
[160, 220, 280] // Q4
];
// ๐ฏ Working with 2D arrays
function printBoard(board: Board): void {
console.log("๐ฎ Game Board:");
board.forEach((row, i) => {
console.log(` ${row.map(cell => cell || "ยท").join(" | ")}`);
if (i < board.length - 1) console.log(" ---------");
});
}
๐ก Understanding Tuples
๐ฏ Tuple Basics
Tuples are perfect for fixed-size, ordered data:
// ๐ Simple tuple - coordinates
let position: [number, number] = [10, 20];
let rgb: [number, number, number] = [255, 128, 0];
// ๐ท๏ธ Named tuples (TypeScript 4.0+)
type Point3D = [x: number, y: number, z: number];
let playerPosition: Point3D = [100, 50, 25];
// ๐จ Mixed type tuples
type UserRecord = [id: number, name: string, isActive: boolean];
let user: UserRecord = [1, "Alice", true];
// ๐ Database result tuple
type QueryResult = [data: any[], count: number, hasMore: boolean];
let result: QueryResult = [
[{id: 1, name: "Item 1"}, {id: 2, name: "Item 2"}],
2,
false
];
// โจ Destructuring tuples
let [x, y, z] = playerPosition;
console.log(`Player at: X=${x}, Y=${y}, Z=${z}`);
// ๐ฎ Function returning tuple
function getPlayerStats(): [health: number, mana: number, level: number] {
return [100, 50, 12];
}
let [health, mana, level] = getPlayerStats();
console.log(`โก Health: ${health} | ๐ Mana: ${mana} | ๐ฏ Level: ${level}`);
๐ Advanced Tuple Patterns
Tuples shine in specific scenarios:
// ๐ Color system with alpha
type RGBA = [red: number, green: number, blue: number, alpha: number];
class ColorPalette {
private colors: Map<string, RGBA> = new Map();
// ๐จ Add color
addColor(name: string, color: RGBA): void {
this.colors.set(name, color);
console.log(`๐จ Added color "${name}"`);
}
// ๐ Mix two colors
mixColors(color1: RGBA, color2: RGBA): RGBA {
return [
Math.round((color1[0] + color2[0]) / 2),
Math.round((color1[1] + color2[1]) / 2),
Math.round((color1[2] + color2[2]) / 2),
(color1[3] + color2[3]) / 2
];
}
// ๐ Convert to CSS
toCSS(color: RGBA): string {
return `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3]})`;
}
}
// ๐ฎ Game inventory slots
type InventorySlot = [
item: string | null,
quantity: number,
maxStack: number
];
class Inventory {
private slots: InventorySlot[] = [];
constructor(size: number) {
// Initialize empty slots
for (let i = 0; i < size; i++) {
this.slots.push([null, 0, 99]);
}
}
// ๐ Add item to inventory
addItem(itemName: string, quantity: number = 1): boolean {
// Find empty or matching slot
for (let i = 0; i < this.slots.length; i++) {
let [item, qty, max] = this.slots[i];
if (item === null || (item === itemName && qty < max)) {
const spaceAvailable = item === null ? max : max - qty;
const toAdd = Math.min(quantity, spaceAvailable);
this.slots[i] = [
itemName,
(item === null ? 0 : qty) + toAdd,
max
];
console.log(`โ
Added ${toAdd}x ${itemName} to slot ${i}`);
return toAdd === quantity;
}
}
console.log(`โ No space for ${itemName}!`);
return false;
}
}
๐ Practical Examples
๐ Data Analysis Dashboard
Letโs build a real data analysis system:
// ๐ Stock market data
type StockPrice = [timestamp: Date, open: number, high: number, low: number, close: number];
type StockSymbol = string;
class StockTracker {
private stocks: Map<StockSymbol, StockPrice[]> = new Map();
// ๐ Add price data
addPriceData(symbol: StockSymbol, price: StockPrice): void {
if (!this.stocks.has(symbol)) {
this.stocks.set(symbol, []);
}
this.stocks.get(symbol)!.push(price);
const [time, open, high, low, close] = price;
console.log(`๐ ${symbol}: $${close} at ${time.toLocaleTimeString()}`);
}
// ๐น Calculate moving average
getMovingAverage(symbol: StockSymbol, periods: number): number | null {
const prices = this.stocks.get(symbol);
if (!prices || prices.length < periods) return null;
const recentPrices = prices.slice(-periods);
const sum = recentPrices.reduce((total, [, , , , close]) => total + close, 0);
return sum / periods;
}
// ๐ฏ Find highest price
getHighestPrice(symbol: StockSymbol): number | null {
const prices = this.stocks.get(symbol);
if (!prices || prices.length === 0) return null;
return Math.max(...prices.map(([, , high]) => high));
}
}
// ๐ฎ Game leaderboard system
interface Score {
player: string;
points: number;
level: number;
achievements: string[];
timestamp: Date;
}
class Leaderboard {
private scores: Score[] = [];
private readonly MAX_ENTRIES = 10;
// ๐ Add score
addScore(score: Score): void {
this.scores.push(score);
this.scores.sort((a, b) => b.points - a.points);
this.scores = this.scores.slice(0, this.MAX_ENTRIES);
console.log(`๐ฏ New score: ${score.player} - ${score.points} points!`);
this.displayTop3();
}
// ๐ฅ Display top 3
private displayTop3(): void {
console.log("\n๐ Top 3 Players:");
const medals = ["๐ฅ", "๐ฅ", "๐ฅ"];
this.scores.slice(0, 3).forEach((score, index) => {
console.log(`${medals[index]} ${score.player}: ${score.points} pts (Level ${score.level})`);
});
}
// ๐ Get statistics
getStats(): [average: number, highest: number, totalGames: number] {
if (this.scores.length === 0) return [0, 0, 0];
const total = this.scores.reduce((sum, s) => sum + s.points, 0);
const average = total / this.scores.length;
const highest = this.scores[0].points;
return [average, highest, this.scores.length];
}
}
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Array Mutation Confusion
// โ Dangerous - mutating arrays unexpectedly
const originalArray = [1, 2, 3];
const copiedArray = originalArray; // This is NOT a copy!
copiedArray.push(4);
console.log(originalArray); // [1, 2, 3, 4] ๐ฑ
// โ
Safe - proper array copying
const properCopy1 = [...originalArray]; // Spread operator
const properCopy2 = originalArray.slice(); // Slice method
const properCopy3 = Array.from(originalArray); // Array.from
// ๐ฏ Deep copying for nested arrays
const nestedArray = [[1, 2], [3, 4]];
const deepCopy = nestedArray.map(arr => [...arr]);
๐คฏ Pitfall 2: Tuple vs Array Confusion
// โ Wrong - treating tuple as array
let coordinate: [number, number] = [10, 20];
// coordinate.push(30); // Error! Tuples have fixed length
// โ Wrong - wrong types in tuple positions
// let user: [string, number] = [25, "Alice"]; // Error! Wrong order
// โ
Correct - respecting tuple structure
let user: [name: string, age: number] = ["Alice", 25];
let [userName, userAge] = user; // Destructuring works great!
// ๐ก Convert tuple to array when needed
let flexibleArray: number[] = [...coordinate];
flexibleArray.push(30); // Now this is OK!
๐ต Pitfall 3: Empty Array Type Inference
// โ Problematic - TypeScript can't infer the type
const items = []; // Type is never[]
// items.push("hello"); // Error!
// โ
Solution 1 - Type annotation
const items1: string[] = [];
items1.push("hello"); // Works!
// โ
Solution 2 - Type assertion
const items2 = [] as string[];
// โ
Solution 3 - Generic array constructor
const items3 = new Array<string>();
๐ ๏ธ Best Practices
๐ฏ Array Best Practices
-
๐ Prefer Readonly Arrays: When arrays shouldnโt change
function processScores(scores: readonly number[]): number { // scores.push(100); // Error! Can't modify return scores.reduce((a, b) => a + b, 0); }
-
๐จ Use Descriptive Types: Make intent clear
type ProductID = string; type ShoppingCart = ProductID[]; // Better than just string[]
-
โจ Leverage Array Methods: Donโt reinvent the wheel
// โ Good - using built-in methods const doubled = numbers.map(n => n * 2); // โ Avoid - manual loops when not needed const doubled2 = []; for (let i = 0; i < numbers.length; i++) { doubled2.push(numbers[i] * 2); }
-
๐ก๏ธ Guard Against Empty Arrays: Handle edge cases
function getAverage(numbers: number[]): number { if (numbers.length === 0) return 0; // Guard clause return numbers.reduce((a, b) => a + b) / numbers.length; }
-
๐ Use Type Guards: For mixed arrays
function processItems(items: (string | number)[]) { items.forEach(item => { if (typeof item === 'string') { console.log(`Text: ${item.toUpperCase()}`); } else { console.log(`Number: ${item * 2}`); } }); }
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Task Management System
Create a type-safe task tracker with arrays and tuples:
๐ Requirements:
- โ Store tasks in typed arrays
- ๐ท๏ธ Use tuples for task metadata [created: Date, updated: Date]
- ๐ฅ Track assigned users with arrays
- ๐ Implement task filtering and sorting
- ๐จ Add emoji status indicators
๐ Bonus Points:
- Implement task history using tuples
- Add batch operations on task arrays
- Create statistics with array reduction
๐ก Solution
๐ Click to see solution
// ๐ฏ Task management system with arrays and tuples
type TaskStatus = "todo" | "in-progress" | "done" | "archived";
type TaskPriority = "low" | "medium" | "high";
type TaskMetadata = [created: Date, updated: Date, version: number];
interface Task {
id: string;
title: string;
description: string;
status: TaskStatus;
priority: TaskPriority;
assignees: string[];
tags: string[];
metadata: TaskMetadata;
emoji: string;
}
class TaskManager {
private tasks: Task[] = [];
private history: Array<[taskId: string, action: string, timestamp: Date]> = [];
// โ Create new task
createTask(
title: string,
description: string,
priority: TaskPriority = "medium"
): Task {
const now = new Date();
const task: Task = {
id: `TASK-${Date.now()}`,
title,
description,
status: "todo",
priority,
assignees: [],
tags: [],
metadata: [now, now, 1],
emoji: this.getEmojiForPriority(priority)
};
this.tasks.push(task);
this.addHistory(task.id, "created");
console.log(`โ
Created task: ${task.emoji} ${task.title}`);
return task;
}
// ๐ฅ Assign users to task
assignUsers(taskId: string, users: string[]): void {
const task = this.tasks.find(t => t.id === taskId);
if (task) {
task.assignees = [...new Set([...task.assignees, ...users])];
this.updateTaskMetadata(task);
this.addHistory(taskId, `assigned ${users.join(", ")}`);
console.log(`๐ฅ Assigned ${users.length} users to task`);
}
}
// ๐ Update task status
updateStatus(taskId: string, status: TaskStatus): void {
const task = this.tasks.find(t => t.id === taskId);
if (task) {
task.status = status;
task.emoji = this.getEmojiForStatus(status);
this.updateTaskMetadata(task);
this.addHistory(taskId, `status changed to ${status}`);
console.log(`๐ Task status updated to: ${status}`);
}
}
// ๐ Filter tasks
filterTasks(
criteria: {
status?: TaskStatus;
priority?: TaskPriority;
assignee?: string;
tag?: string;
}
): Task[] {
return this.tasks.filter(task => {
if (criteria.status && task.status !== criteria.status) return false;
if (criteria.priority && task.priority !== criteria.priority) return false;
if (criteria.assignee && !task.assignees.includes(criteria.assignee)) return false;
if (criteria.tag && !task.tags.includes(criteria.tag)) return false;
return true;
});
}
// ๐ Get statistics
getStats(): [total: number, byStatus: Record<TaskStatus, number>, avgAssignees: number] {
const total = this.tasks.length;
const byStatus = this.tasks.reduce((acc, task) => {
acc[task.status] = (acc[task.status] || 0) + 1;
return acc;
}, {} as Record<TaskStatus, number>);
const totalAssignees = this.tasks.reduce((sum, task) =>
sum + task.assignees.length, 0
);
const avgAssignees = total > 0 ? totalAssignees / total : 0;
return [total, byStatus, avgAssignees];
}
// ๐ฏ Batch operations
batchUpdatePriority(taskIds: string[], priority: TaskPriority): void {
const updatedTasks = this.tasks
.filter(task => taskIds.includes(task.id))
.map(task => {
task.priority = priority;
task.emoji = this.getEmojiForPriority(priority);
this.updateTaskMetadata(task);
return task;
});
console.log(`๐ฏ Updated priority for ${updatedTasks.length} tasks`);
}
// ๐ Get task timeline
getTaskTimeline(taskId: string): Array<[action: string, when: Date]> {
return this.history
.filter(([id]) => id === taskId)
.map(([, action, timestamp]) => [action, timestamp]);
}
// Private helpers
private updateTaskMetadata(task: Task): void {
const [created, , version] = task.metadata;
task.metadata = [created, new Date(), version + 1];
}
private addHistory(taskId: string, action: string): void {
this.history.push([taskId, action, new Date()]);
}
private getEmojiForPriority(priority: TaskPriority): string {
const emojis: Record<TaskPriority, string> = {
low: "๐ข",
medium: "๐ก",
high: "๐ด"
};
return emojis[priority];
}
private getEmojiForStatus(status: TaskStatus): string {
const emojis: Record<TaskStatus, string> = {
todo: "๐",
"in-progress": "๐ง",
done: "โ
",
archived: "๐ฆ"
};
return emojis[status];
}
// ๐ Display tasks
displayTasks(): void {
console.log("\n๐ All Tasks:");
this.tasks.forEach(task => {
console.log(`${task.emoji} ${task.title} (${task.status})`);
console.log(` Priority: ${task.priority} | Assignees: ${task.assignees.join(", ") || "none"}`);
});
const [total, byStatus, avgAssignees] = this.getStats();
console.log("\n๐ Statistics:");
console.log(` Total tasks: ${total}`);
console.log(` By status:`, byStatus);
console.log(` Avg assignees: ${avgAssignees.toFixed(1)}`);
}
}
// ๐ฎ Test the system
const taskManager = new TaskManager();
// Create tasks
const task1 = taskManager.createTask(
"Learn TypeScript Arrays",
"Master arrays and their methods",
"high"
);
const task2 = taskManager.createTask(
"Practice Tuples",
"Understand fixed-length collections",
"medium"
);
// Assign users
taskManager.assignUsers(task1.id, ["Alice", "Bob"]);
taskManager.assignUsers(task2.id, ["Charlie"]);
// Update status
taskManager.updateStatus(task1.id, "in-progress");
// Filter tasks
const highPriorityTasks = taskManager.filterTasks({ priority: "high" });
console.log(`\n๐ฅ High priority tasks: ${highPriorityTasks.length}`);
// Display all
taskManager.displayTasks();
// Check timeline
const timeline = taskManager.getTaskTimeline(task1.id);
console.log("\n๐
Task Timeline:");
timeline.forEach(([action, when]) => {
console.log(` ${when.toLocaleTimeString()}: ${action}`);
});
๐ Key Takeaways
Youโve mastered arrays and tuples in TypeScript! Hereโs what you can now do:
- โ Create type-safe arrays for any data collection ๐ช
- โ Use array methods with full type inference ๐ฏ
- โ Work with tuples for fixed-size data structures ๐๏ธ
- โ Handle multi-dimensional arrays like a pro ๐
- โ Build real-world applications with confidence! ๐
Remember: Arrays and tuples are your foundation for handling collections in TypeScript. Use arrays for dynamic lists and tuples for fixed structures! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve become an arrays and tuples expert!
Hereโs what to do next:
- ๐ป Complete the task management exercise
- ๐๏ธ Refactor existing code to use proper array types
- ๐ Learn about object types and type aliases
- ๐ Explore advanced array manipulation patterns!
Remember: Every shopping cart, game leaderboard, and data dashboard starts with arrays. Keep practicing! ๐
Happy coding! ๐๐โจ