Skip to main content

TypeScript Lists

When working with TypeScript, lists are a key tool for storing and managing data collections. This article will explore advanced techniques and best practices for handling TypeScript lists, including how to create and manipulate arrays, use array methods, define typed arrays, and more.

Creating and Handling Arrays in TypeScript

Arrays are essential data structures in TypeScript that store collections of elements. You can create and manipulate them with precision thanks to TypeScript's type system. To create an array, you have two options: using array literal notation or the Array constructor. The array literal approach is more common and concise:

// Array literal with type annotation
let numbers: number[] = [1, 2, 3];

// Alternative syntax using generic Array<T>
let strings: Array<string> = ["hello", "world"];

// Empty array with type
let emptyArray: number[] = [];

Once created, you can manipulate arrays with several built-in methods. The most common operations include adding, removing, and modifying elements:

let fruits: string[] = ["apple", "banana"];

// Add elements
fruits.push("orange"); // Adds to end: ["apple", "banana", "orange"]
fruits.unshift("mango"); // Adds to beginning: ["mango", "apple", "banana", "orange"]

// Remove elements
fruits.pop(); // Removes from end: ["mango", "apple", "banana"]
fruits.shift(); // Removes from beginning: ["apple", "banana"]

// Modify elements with splice
fruits.splice(1, 0, "kiwi"); // Insert at index 1: ["apple", "kiwi", "banana"]
fruits.splice(0, 1, "grape"); // Replace at index 0: ["grape", "kiwi", "banana"]

TypeScript's type system helps catch errors at compile time when working with arrays. For example, trying to add a number to a string array will result in a type error:

let names: string[] = ["Alice", "Bob"];
// Error: Argument of type 'number' is not assignable to parameter of type 'string'
names.push(42);

To learn more about comprehensive array operations, check out the TypeScript array documentation. For more advanced array filtering techniques in Convex applications, see how to implement complex filters in Convex.

Effectively Using TypeScript Array Methods

TypeScript arrays come with a rich set of methods that make data manipulation straightforward. Understanding these methods helps you write cleaner, more efficient code.

forEach() Method

The TypeScript forEach method iterates through each element in an array, executing a callback function for each one:

let fruits: string[] = ["apple", "banana", "orange"];

fruits.forEach((fruit, index) => {
console.log(`${index}: ${fruit.toUpperCase()}`);
});
// Output:
// 0: APPLE
// 1: BANANA
// 2: ORANGE

map() Method

The TypeScript map method transforms every element in an array, returning a new array with the transformed values:

let numbers: number[] = [1, 2, 3, 4, 5];
let doubled: number[] = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

filter() Method

Use TypeScript filter to create a new array containing only elements that meet specific criteria:

let scores: number[] = [85, 92, 78, 95, 88];
let passingScores: number[] = scores.filter(score => score >= 90);
console.log(passingScores); // [92, 95]

reduce() Method

The TypeScript reduce method processes each element to return a single value, perfect for calculations like sums or complex data transformations:

let expenses: number[] = [100, 50, 75, 120];
let total: number = expenses.reduce((sum, expense) => sum + expense, 0);
console.log(total); // 345

// More complex example: grouping objects
let items = [
{ category: "fruit", name: "apple" },
{ category: "fruit", name: "banana" },
{ category: "vegetable", name: "carrot" }
];

let grouped = items.reduce((acc, item) => {
if (!acc[item.category]) {
acc[item.category] = [];
}
acc[item.category].push(item.name);
return acc;
}, {} as Record<string, string[]>);
// Result: { fruit: ["apple", "banana"], vegetable: ["carrot"] }

These array methods form the foundation of data manipulation in TypeScript. For real-world applications, you can combine them to create powerful data processing pipelines, similar to what you might do when working with complex filters in Convex.

Working with Typed Arrays in TypeScript

Typed arrays leverage TypeScript's type system to ensure your array elements conform to specific shapes and structures. This goes beyond basic type annotations to create more complex, type-safe data structures.

Defining Arrays with Object Types

When working with objects in arrays, you can define precise type structures:

interface User {
id: number;
name: string;
email?: string; // Optional property
}

let users: User[] = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob', email: 'bob@example.com' }
];

// TypeScript catches type errors
// Error: Property 'id' is missing
let invalidUser: User[] = [{ name: 'Charlie' }];

Type Inference in Arrays

TypeScript often infers array types correctly, but explicit typing helps with maintainability:

// Type inference
let inferredNumbers = [1, 2, 3]; // number[]

// Explicit typing (preferred for complex structures)
let products: { id: number; name: string; price: number }[] = [
{ id: 1, name: 'Laptop', price: 999 },
{ id: 2, name: 'Mouse', price: 49 }
];

Working with Generic Array Types

Generics provide flexibility when creating reusable array-based functions:

function getFirstElement<T>(array: T[]): T | undefined {
return array[0];
}

let numbers = [1, 2, 3];
let first = getFirstElement(numbers); // Type is inferred as number

let strings = ['a', 'b', 'c'];
let firstString = getFirstElement(strings); // Type is inferred as string

Read-Only Arrays

TypeScript offers read-only arrays to prevent accidental modifications:

const readOnlyArray: ReadonlyArray<number> = [1, 2, 3];
// Error: Property 'push' does not exist on type 'readonly number[]'
readOnlyArray.push(4);

// Alternative syntax
const readOnlyArray2: readonly number[] = [1, 2, 3];

For more information on types, see TypeScript types. When implementing these patterns in Convex applications, follow the Convex best practices for type-safe data modeling.

Iterating Over Arrays with forEach and map

Array iteration is fundamental to JavaScript and TypeScript development. The TypeScript forEach and TypeScript map methods serve different purposes and understanding when to use each leads to more effective code.

Using forEach for Side Effects

The forEach method runs a function on each array element but doesn't return a new array. It's ideal for operations with side effects like logging, updating external state, or modifying DOM elements:

let users: { id: number; name: string; lastLogin?: Date }[] = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];

// Update each user's last login
users.forEach(user => {
user.lastLogin = new Date();
});

// Using index parameter
users.forEach((user, index) => {
console.log(`User ${index + 1}: ${user.name}`);
});

Transforming Data with map

The map method creates a new array by transforming each element. Unlike forEach, map always returns a new array of the same length:

interface Product {
name: string;
price: number;
}

let products: Product[] = [
{ name: 'Laptop', price: 999 },
{ name: 'Mouse', price: 49 }
];

// Create an array of product names
let productNames: string[] = products.map(product => product.name);
console.log(productNames); // ['Laptop', 'Mouse']

// Create a new array with discounted prices
let discountedProducts = products.map(product => ({
...product,
price: product.price * 0.9
}));

Choosing Between forEach and map

Choose forEach when:

  • You need to perform side effects
  • You don't need a new array returned
  • You're working with external systems or DOM manipulation

Choose map when:

  • You need to transform data
  • You want a new array with modified elements
  • You're maintaining immutability in your code

For more complex data operations, consider exploring how Convex handles array operations. The article on useState less demonstrates efficient state management patterns that work well with array methods.

Handling Multidimensional Arrays

Multidimensional arrays store arrays inside arrays, creating structures for representing matrices, grids, or complex data hierarchies. TypeScript's type system helps maintain structure and prevent errors when working with these nested arrays.

Creating Multidimensional Arrays

Define multidimensional arrays with proper type annotations:

// 2D array (matrix)
let matrix: number[][] = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];

// 3D array
let cube: number[][][] = [
[[1, 2], [3, 4]],
[[5, 6], [7, 8]]
];

// Array of mixed types
let grid: (string | number)[][] = [
["A", 1, "B"],
[2, "C", 3]
];
;

Accessing and Modifying Elements

Access nested elements using multiple indices:

let gameBoard: string[][] = [
["X", "O", " "],
[" ", "X", "O"],
["O", " ", "X"]
];

// Access element
console.log(gameBoard[1][2]); // "O"

// Modify element
gameBoard[1][1] = "O";

// Check bounds before accessing
function safeAccess(arr: any[][], row: number, col: number): any | undefined {
if (row >= 0 && row < arr.length && col >= 0 && col < arr[row].length) {
return arr[row][col];
}
return undefined;
}

Iterating Through Multidimensional Arrays

Use nested loops to traverse multidimensional arrays:

let scores: number[][] = [
[85, 92, 78],
[90, 88, 95],
[72, 85, 80]
];

// Basic iteration
for (let i = 0; i < scores.length; i++) {
for (let j = 0; j < scores[i].length; j++) {
console.log(`Student ${i + 1}, Test ${j + 1}: ${scores[i][j]}`);
}
}

// Using forEach
scores.forEach((studentScores, studentIndex) => {
studentScores.forEach((score, testIndex) => {
console.log(`Student ${studentIndex + 1}, Test ${testIndex + 1}: ${score}`);
});
});

// Calculate averages per student
let averages = scores.map(studentScores =>
studentScores.reduce((sum, score) => sum + score, 0) / studentScores.length
);

Type-Safe Operations

TypeScript helps prevent common errors with multidimensional arrays:

type Matrix = number[][];

function addMatrices(a: Matrix, b: Matrix): Matrix {
if (a.length !== b.length || a[0].length !== b[0].length) {
throw new Error("Matrices must have the same dimensions");
}

return a.map((row, i) =>
row.map((val, j) => val + b[i][j])
);
}

For more advanced array operations, see the TypeScript array documentation. When implementing complex data structures in real applications, refer to the Convex best practices guide.

Sorting and Filtering Arrays

Sorting and filtering are fundamental operations for organizing and refining data. TypeScript provides type-safe ways to perform these operations while maintaining data integrity.

Array Sorting

The sort() method modifies the original array and returns a reference to it. TypeScript helps ensure your comparison function handles the correct types:

// Basic number sorting
let numbers: number[] = [4, 2, 7, 1, 3];
numbers.sort((a, b) => a - b);
console.log(numbers); // [1, 2, 3, 4, 7]

// Sorting objects by property
interface User {
name: string;
age: number;
}

let users: User[] = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
{ name: "Charlie", age: 35 }
];

// Sort by age
users.sort((a, b) => a.age - b.age);

// Sort by name (alphabetically)
users.sort((a, b) => a.name.localeCompare(b.name));

Array Filtering

The TypeScript filter method creates a new array containing elements that pass a test:

interface Product {
name: string;
price: number;
inStock: boolean;
}

let products: Product[] = [
{ name: "Laptop", price: 1200, inStock: true },
{ name: "Mouse", price: 30, inStock: false },
{ name: "Keyboard", price: 80, inStock: true }
];

// Filter products in stock
let availableProducts = products.filter(product => product.inStock);

// Filter by multiple conditions
let affordableInStock = products.filter(
product => product.inStock && product.price < 1000
);

// Type guard filtering
function isExpensive(product: Product): product is Product & { price: number } {
return product.price > 500;
}

let expensiveProducts = products.filter(isExpensive);

Combined Operations

Sort and filter often work together to create refined datasets:

let employees = [
{ name: "Alice", department: "IT", salary: 80000 },
{ name: "Bob", department: "HR", salary: 60000 },
{ name: "Charlie", department: "IT", salary: 90000 }
];

// Filter IT employees and sort by salary descending
let topITEmployees = employees
.filter(emp => emp.department === "IT")
.sort((a, b) => b.salary - a.salary);

When building applications with Convex, these array operations become invaluable. The complex filters in Convex guide demonstrates how to implement advanced filtering logic in your database queries.

Ensuring Type Safety with Arrays

Type safety in TypeScript arrays prevents runtime errors and makes code more predictable. Let's explore key techniques for maintaining type integrity when working with arrays.

Using Type Annotations

Always define array types explicitly to catch errors early:

// Good: Explicit type annotations
let numbers: number[] = [1, 2, 3];
let names: string[] = ["Alice", "Bob"];

// Error: Type 'string' is not assignable to type 'number'
let mixedArray: number[] = [1, "two", 3];

// Union types for mixed arrays
let mixed: (string | number)[] = [1, "two", 3];

Type Guards for Runtime Safety

Create custom type guards to validate array contents:

interface User {
name: string;
age: number;
}

function isUser(obj: any): obj is User {
return typeof obj === 'object' &&
'name' in obj && typeof obj.name === 'string' &&
'age' in obj && typeof obj.age === 'number';
}

let data: any[] = [
{ name: "Alice", age: 30 },
{ invalid: "data" },
{ name: "Bob", age: 25 }
];

// Filter valid users
let users: User[] = data.filter(isUser);
console.log(users.length); // 2

Generics for Flexible Type Safety

Use generics to create reusable, type-safe array functions:

function getLastElement<T>(array: T[]): T | undefined {
return array[array.length - 1];
}

function filterUnique<T>(array: T[]): T[] {
return [...new Set(array)];
}

// Works with any type
let lastNumber = getLastElement([1, 2, 3]); // Type: number | undefined
let lastString = getLastElement(['a', 'b', 'c']); // Type: string | undefined

Readonly Arrays

Prevent accidental mutations with readonly arrays:

function processReadonlyArray(items: readonly string[]) {
// Error: Property 'push' does not exist on type 'readonly string[]'
items.push("new item");

// OK: Create new array instead
return [...items, "new item"];
}

Array type safety forms the foundation of robust TypeScript applications. For more on TypeScript's type system, see TypeScript types. When building full-stack applications, the Convex best practices guide provides additional insights on maintaining type safety throughout your application stack.

Common Challenges and Solutions

Working with TypeScript arrays presents specific challenges. Here are three common issues and their solutions:

Handling Empty Arrays

When dealing with empty arrays, TypeScript needs explicit type information:

// Problem: TypeScript can't infer the type
let emptyArray = []; // Type: any[]

// Solution: Provide explicit type annotation
let emptyNumbers: number[] = [];
let emptyUsers: User[] = [];

// Alternative: Type assertion
let assertedArray = [] as string[];

Working with Mixed Type Arrays

Mixed-type arrays require careful handling to maintain type safety:

type UserOrProduct = User | Product;

let mixedData: UserOrProduct[] = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Laptop", price: 999 }
];

// Type guards for safe access
function isUser(item: UserOrProduct): item is User {
return !('price' in item);
}

// Separate processing based on type
let users = mixedData.filter(isUser);
let products = mixedData.filter((item): item is Product => !isUser(item));

Handling Index Out of Bounds

TypeScript doesn't prevent accessing non-existent array indices:

let numbers: number[] = [1, 2, 3];
let value = numbers[10]; // undefined, but TypeScript thinks it's number

// Solution: Optional chaining and bounds checking
function safeGet<T>(array: T[], index: number): T | undefined {
return array?.[index];
}

// Type-safe array access with validation
function getElement<T>(array: T[], index: number): T {
if (index < 0 || index >= array.length) {
throw new Error(`Index ${index} out of bounds`);
}
return array[index];
}

For comprehensive array handling patterns, explore the TypeScript array documentation. When building robust applications with Convex, be sure to follow the best practices for data validation and error handling.

Working with TypeScript Arrays

TypeScript arrays provide robust tools for managing collections with type safety. From basic operations like adding and removing items to advanced techniques like filtering and mapping, you now have the foundation to work effectively with arrays in TypeScript.