Is TypeScript Pass by Reference?

In programming, understanding how data is passed between functions is crucial for writing efficient and bug-free code. One of the common questions that developers face, especially when working with TypeScript, is whether it passes variables by reference or by value. This blog post will delve deep into the concept of pass-by-reference in TypeScript, explaining the fundamental concepts, usage methods, common practices, and best practices.

Table of Contents#

  1. Fundamental Concepts
  2. Pass-by-Value vs Pass-by-Reference
  3. Pass-by-Reference in TypeScript
  4. Usage Methods
  5. Common Practices
  6. Best Practices
  7. Conclusion
  8. References

Fundamental Concepts#

Pass-by-Value#

When a variable is passed by value, a copy of the variable's value is made and passed to the function. Any changes made to the parameter inside the function do not affect the original variable outside the function.

Pass-by-Reference#

When a variable is passed by reference, the memory address of the variable is passed to the function. This means that any changes made to the parameter inside the function will directly affect the original variable outside the function.

Pass-by-Value vs Pass-by-Reference#

Let's look at a simple example in JavaScript (which TypeScript is a superset of) to understand the difference between pass-by-value and pass-by-reference.

// Pass - by - value example
let num1 = 10;
function changeValue(num) {
    num = 20;
    console.log(num); // Output: 20
}
changeValue(num1);
console.log(num1); // Output: 10
 
 
// Pass - by - reference example
let obj1 = { value: 10 };
function changeObject(obj) {
    obj.value = 20;
    console.log(obj.value); // Output: 20
}
changeObject(obj1);
console.log(obj1.value); // Output: 20

In the first example, num1 is a primitive value (a number), and it is passed by value. So, the change made inside the changeValue function does not affect the original num1 variable. In the second example, obj1 is an object, and it is passed by reference. So, the change made inside the changeObject function affects the original obj1 variable.

Pass-by-Reference in TypeScript#

TypeScript follows the same rules as JavaScript when it comes to passing variables. Primitive types (such as number, string, boolean, etc.) are passed by value, while non-primitive types (such as object, array, function) are passed by reference.

// Primitive type (pass - by - value)
let numberValue: number = 5;
function modifyNumber(num: number) {
    num = 10;
    console.log(num); // Output: 10
}
modifyNumber(numberValue);
console.log(numberValue); // Output: 5
 
 
// Object type (pass - by - reference)
let person: { name: string } = { name: 'John' };
function modifyPerson(p: { name: string }) {
    p.name = 'Jane';
    console.log(p.name); // Output: Jane
}
modifyPerson(person);
console.log(person.name); // Output: Jane

Usage Methods#

Modifying Objects#

When passing an object by reference, you can easily modify its properties inside a function.

interface Book {
    title: string;
    author: string;
}
 
let myBook: Book = { title: 'TypeScript Guide', author: 'Author Name' };
 
function updateBook(book: Book) {
    book.title = 'Advanced TypeScript Guide';
    book.author = 'New Author';
}
 
updateBook(myBook);
console.log(myBook); // Output: { title: 'Advanced TypeScript Guide', author: 'New Author' }

Working with Arrays#

Arrays are also passed by reference in TypeScript. You can add, remove, or modify elements inside a function.

let numbers: number[] = [1, 2, 3];
 
function addNumber(arr: number[]) {
    arr.push(4);
}
 
addNumber(numbers);
console.log(numbers); // Output: [1, 2, 3, 4]

Common Practices#

Be Aware of Side Effects#

When working with pass-by-reference, be aware of side effects. A side effect occurs when a function modifies the state of an object outside of its scope. This can make the code hard to understand and debug.

let originalArray: number[] = [1, 2, 3];
 
function doubleArray(arr: number[]) {
    for (let i = 0; i < arr.length; i++) {
        arr[i] = arr[i] * 2;
    }
}
 
doubleArray(originalArray);
console.log(originalArray); // Output: [2, 4, 6]

Use Immutable Data Structures#

To avoid side effects, you can use immutable data structures. Instead of modifying the original object, create a new object with the desired changes.

let originalArray: number[] = [1, 2, 3];
 
function doubleArrayImmutable(arr: number[]): number[] {
    return arr.map(num => num * 2);
}
 
let newArray = doubleArrayImmutable(originalArray);
console.log(originalArray); // Output: [1, 2, 3]
console.log(newArray); // Output: [2, 4, 6]

Best Practices#

Document Side Effects#

If your function has side effects, document them clearly in the function's comments. This will help other developers understand the behavior of the function.

/**
 * This function modifies the original array by doubling each element.
 * @param arr - The array to be modified.
 */
function doubleArray(arr: number[]) {
    for (let i = 0; i < arr.length; i++) {
        arr[i] = arr[i] * 2;
    }
}

Use Function Signatures to Indicate Mutability#

You can use function signatures to indicate whether a function will mutate the input object or not. For example, a function that starts with mutate can be assumed to modify the input object.

function mutateArray(arr: number[]) {
    arr.push(10);
}
 
function getDoubledArray(arr: number[]): number[] {
    return arr.map(num => num * 2);
}

Conclusion#

In TypeScript, primitive types are passed by value, while non-primitive types are passed by reference. Understanding this concept is essential for writing efficient and maintainable code. By being aware of side effects, using immutable data structures, and following best practices, you can avoid common pitfalls and write better TypeScript code.

References#