Best Practices for for Loops in TypeScript

Best Practices for for Loops in TypeScript

This article explains the best practices for using for loops in TypeScript.

YouTube Video

Best Practices for for Loops in TypeScript

for statements are a fundamental and powerful syntax for performing repetitive operations. In TypeScript, leveraging type safety and writing code with readability and maintainability in mind allows you to write efficient code with fewer errors.

Choosing the Right Loop Type

TypeScript offers several loop constructs, each suitable for different use cases. Choosing the appropriate loop is crucial for code clarity and performance.

Standard for Loop

1for (let i = 0; i < 10; i++) {
2    console.log(i);
3}

The standard for loop is ideal when you need precise control over the iteration index.

Best Practices
1const maxIterations = 10;
2for (let i = 0; i < maxIterations; i++) {
3    console.log(i);
4}

When writing for statements, keeping the following points in mind can help you write safer and more readable code.

  • Use let for index variables
    • Using let instead of var limits the scope to the block, preventing unintended behavior.
  • Use constants and descriptive variable names to make loop boundaries explicit
    • Avoiding magic numbers and using meaningful variable names improves readability.

for...of Loop

1const array = [1, 2, 3];
2for (let value of array) {
3    console.log(value);
4}

The for...of loop is suitable for iterating over iterable objects like arrays and strings.

Best Practices
1const array = [1, 2, 3];
2for (const value of array) {
3    console.log(value);
4}

When writing for...of loops, paying attention to the following points can help you write safer code.

  • Use const for loop variables
    • If values are not modified within the loop, using const helps prevent accidental reassignment.

for...in Loop

1const obj = { a: 1, b: 2, c: 3 };
2for (const key in obj) {
3    console.log(`${key}: ${obj[key]}`);
4}

The for...in loop iterates over the enumerable properties of an object.

Best Practices
1const obj = { a: 1, b: 2, c: 3 };
2for (const key in obj) {
3    if (obj.hasOwnProperty(key)) {
4        console.log(`${key}: ${obj[key]}`);
5    }
6}

When writing a for...in loop, you can consider the following points.

  • Filter properties
    • If you need to avoid inherited properties, you can use hasOwnProperty.
  • Do not use for...in with arrays Avoid using for...in with arrays. It may iterate over all enumerable properties, including those that are not array indices.

forEach Method

1const array = [1, 2, 3];
2array.forEach((value, index) => {
3    console.log(`Index: ${index}, Value: ${value}`);
4});

When iterating over arrays, forEach is concise and eliminates the need to manage indexes.

Best Practices

When using the forEach method, you can consider the following points.

  • Use arrow functions
    • Use concise arrow functions to improve readability.
  • Avoid interruptions
    • forEach does not support break or continue. Use for...of or for loops when needed:.

Type Safety and Error Prevention

By leveraging TypeScript's type system, you can prevent runtime errors during iteration:.

Define Strict Types for Loop Variables

1const items = [1, 2, 3];
2items.forEach(item => {
3    console.log(item * 2);
4});
1const items: number[] = [1, 2, 3];
2items.forEach((item: number) => {
3    console.log(item * 2);
4});

By explicitly specifying types for loop variables, you can detect type mismatches early.

Avoid Implicit any

1{
2  "compilerOptions": {
3    "noImplicitAny": true
4  }
5}

Also, by enabling noImplicitAny in tsconfig.json, you can prevent variables without explicit types from being implicitly assigned the any type.

Use ReadonlyArray When Necessary

1const numbers: ReadonlyArray<number> = [1, 2, 3];
2for (const value of numbers) {
3    console.log(value);
4}

If you're iterating over an array that shouldn't be modified, you can use ReadonlyArray.

Performance Considerations

Efficiency is crucial for loops processing large datasets or frequently executed loops:.

Choose the optimal loop implementation method.

There are various ways to implement loops, each with differences in readability and execution efficiency.

 1// Prepare input data (an array from 1 to 1000000)
 2const input: number[] = Array.from({ length: 1000000 }, (_, i) => i + 1);
 3
 4// --- for ---
 5console.time('for loop');
 6const squaresFor: number[] = [];
 7for (let i = 0; i < input.length; i++) {
 8    squaresFor.push(input[i] * input[i]);
 9}
10console.timeEnd('for loop');
11
12// --- while ---
13console.time('while loop');
14const squaresWhile: number[] = [];
15let i: number = 0;
16while (i < input.length) {
17    squaresWhile.push(input[i] * input[i]);
18    i++;
19}
20console.timeEnd('while loop');
21
22// --- for-of ---
23console.time('for-of loop');
24const squaresForOf: number[] = [];
25for (const num of input) {
26    squaresForOf.push(num * num);
27}
28console.timeEnd('for-of loop');
29
30// --- forEach ---
31console.time('forEach loop');
32const squaresForEach: number[] = [];
33input.forEach((num: number): void => {
34    squaresForEach.push(num * num);
35});
36console.timeEnd('forEach loop');
37
38// --- map ---
39console.time('map');
40const squaresMap: number[] = input.map((value: number): number => value * value);
41console.timeEnd('map');

Efficiency varies depending on the execution environment, but for example, when running a loop a million times, the difference can become quite noticeable. Choose the optimal loop method considering maintainability and performance.

Use Native Iteration Methods

 1const squares = [1, 2, 3].map(value => value * value);
 2console.log(squares);
 3
 4const numbers = [1, 2, 3, 4, 5, 6];
 5const evenNumbers = numbers.filter(value => value % 2 === 0);
 6console.log(evenNumbers); // [2, 4, 6]
 7
 8const squaredEvens = numbers
 9    .filter(value => value % 2 === 0) // Keep only even numbers
10    .map(value => value * value);     // Square the remaining values
11
12console.log(squaredEvens); // [4, 16, 36]

Methods like map and filter can be more readable in some cases.

Prefer for...of for Readability

Performance should be prioritized only in limited cases; writing readable code is generally more important. For example, prioritizing for...of can improve readability.

1const fruits = ["apple", "banana", "cherry"];
2
3for (let i = 0; i < fruits.length; i++) {
4    console.log(`${i}: ${fruits[i]}`);
5}
Best Practices
1const fruits = ["apple", "banana", "cherry"];
2
3for (const [index, fruit] of fruits.entries()) {
4    console.log(`${index}: ${fruit}`);
5}

By prioritizing for...of loops, you can write more readable and error-resistant code. As shown in this example, if you also need array indexes, combining entries() with for...of is effective.

Avoiding Common Pitfalls

Modifying Collections During Iteration

1const array = [1, 2, 3];
2for (const value of [...array]) {
3    if (value === 2) {
4        array.push(4); // Avoid this!
5    }
6}
7console.log(array);

Modifying an array during iteration can lead to unexpected behavior:. Use a copy if necessary.

Consider edge cases

1const array: number[] = [];
2for (const value of array) {
3    console.log(value); // No output, but no errors
4}

This code works fine, but if you need to handle empty arrays, you can improve it as follows.

1const array: number[] = [];
2if (array.length === 0) {
3    console.log("The array is empty.");
4} else {
5    for (const value of array) {
6        console.log(value);
7    }
8}

Considering edge cases can help prevent errors in subsequent code.

Conclusion

To master the for statement in TypeScript, it’s essential to understand various loop constructs, adhere to type-safe practices, and optimize performance. These best practices help you write cleaner, more reliable, and more maintainable code.

You can follow along with the above article using Visual Studio Code on our YouTube channel. Please also check out the YouTube channel.

YouTube Video