308H.5 - Functions

Learning Objectives

By the end of this lesson, learners should be able to:

  • Describe the benefits of using functions in programming.
  • Create and call JavaScript functions.
  • Differentiate between function declarations and function expressions.
  • Use Arrow functions.
  • Use ES6 default parameters.
  • Define hoisting, and describe its effects on variables and functions.
  • Explain the difference between Pass by Value and Pass by Reference.

Lesson Setup

For this lesson, we're going to code along using a Vanilla CodeSandbox - you can name it "JavaScript Functions and Scope Practice".


 CodeSandbox

This lesson uses CodeSandbox as one of its tools.

If you are unfamiliar with CodeSandbox, or need a refresher, please visit our reference page on CodeSandbox for instructions on:

  • Creating an Account
  • Making a Sandbox
  • Navigating your Sandbox
  • Submitting a link to your Sandbox to Canvas

Introduction to Functions

A function is a reusable block of code designed to perform a single purpose.

It optionally takes in data as input and/or returns a single piece of data (including complex data such as objects, other functions, etc.).

Functions are the building blocks of programs!

The code in a function does not execute until that function itself is executed (or "called"; we'll talk more about this shortly).

Sometimes functions execute in response to events happening, such as when:

  • When a user clicks something.
  • A timer "ticks", etc.

In addition to the functions we write, programming languages typically include numerous built-in functions.

Functions commonly call other functions.


Why we use Functions

Functions are everywhere in code, so why do we use them so much?

  1. Tackle Complexity

    • There's no better way to tackle a complex problem than by breaking it into smaller problems.
    • Functions allow us to break up programs into more manageable blocks of code.
  2. Code Reuse

    • Functions provide code reuse because they can be called over and over.
    • Without functions, we might have to write the same code in multiple places of the app which violates a key principle known as DRY - Don't Repeat Yourself!
  3. Documentation & Debugging

    • Simply naming functions appropriately, e.g., renderBoard, documents what the program is doing.
    • Organizing code into functions also makes it easier to find and fix code that's not working as expected, a process known as debugging.

In summary, it would be impractical to create applications without breaking up the application into functions.

Here's a simple example of how an app that sends out a daily business report might be broken up into functions:

let date = new Date();
let sales = getSalesData(date);
let labor = getLaborCosts(date);
let budget = getBudget(date);
let report = generateReport(date, sales, labor, budget);
sendReport(report);

function getSalesData(forDate) {
  let netSales = getNetSales(forDate);
  let salesTax = computeSalesTax(netSales);
  return {netSales, salesTax};
}

function getLaborCosts(forDate) {
  let staffCosts = getStaffCosts(forDate);
  let mgtCosts = getMgtCosts(forDate);
  return {staffCosts, mgtCosts};
}

function getBudget(forDate) {
  let salesBudget = getSalesBudget(forDate);
  let laborBudget = getLaborBudget(forDate);
  return {salesBudget, laborBudget};
}

function generateReport(forDate, dailySales, dailyLabor, budget) {
  ...
}

function sendReport(report) {
  ...
}

// etc.

This example also illustrates several components of functions which we will cover later:

  • Function Declaration
  • Function Parameters and Arguments
  • Returns
  • Function Calls

Defining Functions

There are three primary ways to define functions in JS:

  1. Function Declaration (AKA Function Definitions)
function sayHello(name) {
    console.log('Hello ' + name + '!');
}
  1. Function Expression
const sayHello = function(name) {
    console.log('Hello ' + name + '!');
};

For all practical purposes, the difference between these two approaches is that function expressions cannot be invoked before they are defined; whereas function declarations are hoisted to the top of their scope and can therefore be invoked even if they are defined later in the source code.

We'll talk more about hoisting when we cover scope later, but here's an example:

fnDeclaration();  // thank you function declarations :)
fnExpression();  // TypeError: fnExpression is not a function

function fnDeclaration() {
	console.log("I'm coming from a function declaration.");
}

const fnExpression = function() {
	console.log("I'm coming from a function expression.");
};

Note: Attempting to execute a function expression before it's been assigned to its variable is the source of many an error for JS developers!

  1. Arrow Functions

ES2015, the latest major JavaScript revisions, delivered a third approach to defining functions - Arrow Functions.

The following function declaration and the following arrow function are equivalent:

// Function Declaration
function add(a, b) {
	return a + b;
}
// Arrow Function
const add = (a, b) => a + b;

Arrow Functions offer:

  • A more concise syntax.
  • Implicit return of a single expression.
  • A single rule for binding the this keyword.

However, as cool as Arrow Functions are, they cannot be used in every scenario due to the way they bind this.

Rest assured, we'll be using Arrow Functions during this course. However, for this lesson we'll focus on Function Declarations and Expressions which have served the language well for over 20 years.


Calling Functions

Regardless of which of the three approaches are used to define functions, we call them the same way:

add(25, 100);  // returns 125

Developer Vocab: Developers might say call, execute, invoke or "run a function" - they all mean the same thing.


Exercise

Let's write the following function together in our script.js file:

function areBothEven(n1, n2) {
  return !(n1 % 2) && !(n2 % 2);
}

The areBothEven function is defined to accept two arguments. These arguments should be numbers, otherwise the function as written won't work as expected.

The return keyword returns the result of the expression that follows it - which looks kind of crazy, but isn't as intimidating as it appears when you break it down.

Note: In the real world, much of the code you write will be code designed prevent and handle error conditions. For example, in the areBothEven function above, it would be important to ensure that both of the inputs are numbers. However, in this course, we will minimize the amount of error handling code so that we can focus more on what it is we're trying to teach. There just isn't enough time, so we must prioritize.

Now it's your turn...

You're going to write two functions, one as a function declaration and the other as a function expression.

This will be an individual exercise, however, feel free to seek guidance from a partner via direct message or posting a question in the classroom Slack channel if you get stuck.

Practice Activity 1: Write a Function Declaration

Write a function named computeArea using the function declaration approach.

  • It will have two parameters: width & height.
  • It will compute the area of a rectangle (width multiplied by height) and return a string in the following form:

    The area of a rectangle with a width of _ and a height of _ is ___ square units.

Invoke the function to test it.

Practice Activity 2: Write a Function Expression

Write a function named planetHasWater using the function expression syntax.

  • It will have one parameter: planet.
  • Return true if the planet argument is either "Earth" or "Mars", otherwise return false.
  • Bonus points if you ensure the function will work regardless of the casing of the planet being passed in ('earth', 'MARS', etc.).

Invoke the function a couple of times to test it!


Knowledge Check

❓ How many different ways are there to define a function?

❓ What's the only practical difference between a function definition and a function expression?


Parameters and Arguments

Parameters and arguments are the data that functions accept as inputs. First, let us answer a common question: "What's the difference between a parameter and an argument?"

There are a few tidbits about parameters and arguments to ponder:

  • Parameters become local variables inside the function body.
  • Therefore, in the example above, bottle and cap are variables that can be accessed anywhere within the function.
  • Just like when naming variables and functions, it's important to name parameters using identifiers that convey the data they will hold.
  • Arguments are assigned to their respective parameter positionally. In the example above, the bottle parameter would be assigned the string "green bottle" because they are the first parameter and argument respectively.

JavaScript is very flexible and won't complain when the number of arguments is not the same as the number of parameters defined.

If fewer arguments are passed than parameters defined, then the parameter variables without a matching argument would be set to undefined.

Note: Unlike some other programming languages, JavaScript won't complain if fewer (or extra) arguments are passed to a function. However, the function that depends on certain arguments to do its job might raise an error or return an unexpected result if it doesn't receive the proper arguments.

Let's pretend you need to write a function that accepts an unknown number of arguments.

For example, let's say we would like to be able to call a function that accepts a developer's name and any number of their job skills, something like the following:

let maria = getDevObject('Maria', 'HTML', 'CSS', 'JavaScript', 'jQuery');

And we want that function to return a JS object shaped like this:

{
	devName: 'Maria',
	jobSkills: ['HTML', 'CSS', 'JavaScript', 'jQuery']
}

A non-arrow function can access all of its arguments using a "hidden" variable inside of the function named arguments.

arguments is an array-like JS object that has a length property and allows its values to be accessed via square bracket notation.

This is how we could use the arguments object to code the function:

function getDevObject(name) {
  let skills = [];
  for (let i = 1; i < arguments.length; i++) {
    skills.push(arguments[i]);
  }
  return {
    devName: name,
    jobSkills: skills
  };
}

Although the above function works, ES2015 delivered a better approach to working with extra arguments called Rest Parameters.

Using rest parameters, the above function can be written as follows:

function getDevObject(name, ...skills) {
  return {
    devName: name,
    jobSkills: skills
  };
}

The ...skills that is defined will be a true array (unlike arguments) holding any extra arguments provided to the function.

Obviously, there can only be a single rest parameter and it must be the last parameter in the list.

When writing new code, devs should use rest parameters instead of arguments because:

  • The existence of the rest parameter in the parameter list better documents the function.
  • The rest parameter is a true array and thus includes all of the nifty methods that arrays have.

ES2015 Default Parameters

What if your function requires certain arguments and you want to provide a default value for the parameter if an argument is not supplied when the function is invoked?

Prior to ES2015, here is trivial example of what we had to do:

function setColor(bicycle, color) {
	// set color to 'purple' if not provided
	bicycle.color = color || 'purple';
}

const bike = new Bicycle();
setColor(bike, 'blue');  // sets color to blue
setColor(bike);  // sets color to purple by default

Now, using default parameters, we can do this:

function setColor(bicycle, color = 'purple') {
	bicycle.color = color;
}

Any expression can be provided as a default, including objects, etc.


Functions as Arguments

In JavaScript, it's easy to pass around functions like data - because they are - they're objects!

Often functions or methods (functions attached to an object) will require a function be provided as an argument.

For example, the forEach method on arrays:

var a = ['red', 'green', 'blue'];

a.forEach(function(color) {
  console.log(color);
});

Since the function provided to the forEach will never be called anywhere else in the code, why create a separate named function and pass it in?

Anonymous functions like shown above can really come in handy!


Knowledge Check

❓ What's the difference between an argument and a parameter?

❓ Explain how arguments and parameters are "matched up".


Immediately Invoked Function Expressions (IIFE)

One way we can prevent our code from leaking into the global scope is by wrapping it with a construct known as an Immediately Invoked Function Expression, or "IIFE" (pronounced "iffy").

It looks like this:

(function() {
	// your code here...
})();

❓ Why does this construct virtually prevent variables and functions from being created in the global scope?


Hoisting

We mentioned that we can call function declarations before they are defined thanks to hoisting, but what is that and how does it work?

Hoisting refers to the process whereby the interpreter appears to move the declaration of functions, variables or classes to the top of their scope, prior to execution of the code.

A variable's declaration (but not its assignment), is hoisted to the top of the function when it's declared using var.

For example, when we write code like this:

function hoist() {
	console.log(x);  // outputs undefined, not a ReferenceError
	var x = 25;
	console.log(x);  // outputs 25
}

Internally, the JS engine actually sees this:

function hoist() {
	var x;
	console.log(x);  // outputs undefined, not a ReferenceError
	x = 25;
	console.log(x);  // outputs 25
}

Note that the variable declaration is hoisted, but its assignment is not!

let and const behave differently; try the same code with let instead of var:

function hoist() {
	console.log(x);  // outputs ReferenceError
	let x = 25;
	console.log(x);
}

The reason for this difference is because "Hoisting" is not a universally agreed-upon term, nor is it defined in the ECMAScript specification, so hoisting actually refers to three separate groups of behaviors:

  1. Being able to use a variable's value in its scope before the line it is declared. ("Value hoisting")
  2. Being able to reference a variable in its scope before the line it is declared, without throwing a ReferenceError, but the value is always undefined. ("Declaration hoisting")
  3. The declaration of the variable causes behavior changes in its scope before the line in which it is declared.

var is hoisted with type 2 behavior, whereas let, const, and class (which we will cover in a later lesson) are hoisted with type 3 behavior. To analyze what type 3 behavior really is, let's look at this block of code:

let x = 5;

{
  let y = 6;
  console.log(x); // outputs 5
}

console.log(y); // outputs ReferenceError

This works perfectly fine, and as expected. The first console.log statement has access to x because it is in an outer scope, but the second console.log cannot access y within the block scope.

So, following this simple logic, what would we expect of the following?

let x = 5;

{
  console.log(x);
  let x = 6;
}

If the const x = 6 declaration is not hoisted at all, then the console.log(x) statement should be able to read the x value from the upper scope, but that is not the case...

This code does not output 5, nor does it output 6! Looking at the definition of a type 3 hoisting behavior again, we can see that "the declaration of the variable causes behavior changes in its scope before the line in which it is declared." This change in behavior is such that the variable declaration "taints" the scope that it is within, causing a ReferenceError in this case.

Basically, JavaScript sees that x exists within the same scope as the console.log but it is not initialized yet; it assumes that the same-scope x is what you meant to use, but since it can't reference that value yet... ReferenceError.

It is best not to rely on hoisting variables in your code, as it can lead to unexpected behaviors.


Hoisting Functions

Hoisting can be dangerous, but it can also be very useful. We only covered type 2 and type 3 hoisting with var and lexical declarations (let, const, class), so what about type 1?

Function declarations can also be hoisted, and behave as type 1 describes! This is the most common usage of hoisting in JavaScript programming, and you will see it everywhere.

Let's look at a practical example to demonstrate this effect. Let's assume you're working on a very large codebase with many dozens of functions, and you are creating a script that uses them all in a specific order to accomplish a specific task. You could do this:

const val = { data: [...], type: ..., dateTime: ..., ... };

function populateValue(val) {
  /**
   * Do a bunch of logic in here.
   */
}

const transformedData = populateValue(val);

function sendRequest(data, config, ...) {
  /**
   * More function logic.
   */
}

const response = sendRequest(data, {
  someValue: ...,
  otherConfigOption: ...,
}, {
  moreData: ...
});

function processResponse(res) {
  /**
   * Dozens of lines of code.
   */
}

const success = processResponse(response);

console.log(success);

You can see how this would get messy. These are also functions, which means they're being used multiple times throughout our code; placing their definitions in order with our logic doesn't make sense. What if we had more logic steps between them? What if we were putting this all into a bigger function, but we needed to access these functions in the outer scope?

The simple answer to this is to define all of your functions first, but in a large file this could bury the logic we actually care about. Luckily, we don't need to do that.

Thanks to hoisting, we can use a function (and its entire value) before it is ever declared. This means we can transform the mess above into this:

const val = { data: [...], type: ..., dateTime: ..., ... };
const transformedData = populateValue(val);
const response = sendRequest(data, {
  someValue: ...,
  otherConfigOption: ...,
}, {
  moreData: ...
});
const success = processResponse(response);

console.log(success);

function populateValue(val) {
  /**
   * Do a bunch of logic in here.
   */
}

function sendRequest(data, config, ...) {
  /**
   * More function logic.
   */
}

function processResponse(res) {
  /**
   * Dozens of lines of code.
   */
}

Now we can clearly see the logic we're creating without needing to search through our code to find snippets in between functions. This also allows us to organize our functions in any way we desire, even in external files! (More on this soon.)


Nesting Functions

As the examples so far have shown, we can define functions within functions!

Why would we want to do this? Well, Perhaps an outer function needs a "helper" function that would only be relevant to the given function. It would be good programming practice to "hide" that function from the rest of the program by nesting it within the function that actually needs it.

For example (no need to execute this):

function openNewAccount(name, openingBalance) {
  let acctNum = generateAcctNum();

  // createAccount is a function available outside this function
  let acct = createAccount(acctNum, openingBalance);
  return acct;

  // helper function that provides a unique account number
  function generateAcctNum() {
    return Date.now();  // super amazing algorithm :)
  }
}

As you can see, there's a nifty generateAcctNum function in there and it's only relevant to when a new account is opened, so it's nested within the openNewAcount function.


Pass by Value

In programming, you will encounter the terms "Pass by Value" and "Pass by Reference" in almost every language. To explain these concepts, we need to look back at the way that variables work, and revisit JavaScript data types.

As you may recall, a variable is simply a reserved location in memory that allows a program to store values with a defined reference. Take the following:

let myVar = 20;

This statements is telling JavaScript to take a place in memory, name it "myVar," and store a Number value of 20 in that location. Every time your program references myVar, the interpreter finds that location, retrieves the data stored there, and uses it to execute whatever expressions you have defined.

So what happens when we execute the following code?

let myVar = 20;

function passBy(myVar) {
    myVar = myVar * 10;
    return myVar;
}

What is the value of myVar after calling passBy(myVar)? Let's inspect the results with a few logs.

console.log(myVar);           // 20
console.log(passBy(myVar));   // 200
console.log(myVar);           // 20
  • Our first log shows that myVar is 20, as expected before anything has been called to change it.
  • The second log shows that passBy(myVar) has returned 200, as expected.
  • The third log shows that myVar is still 20! Is this right?

What we're witnessing in this example is the magic of scope and pass by value.

In JavaScript, all primitive data types are passed by value, which means that:

  • A copy of the original value is made in a new location in memory.
  • That new value is assigned to a new variable (in this case, the function-scoped myVar).
  • That new variable is independant of the old one. Any changes made to one will not be reflected in another.

The primitive data types, as a refresher, are:

  • string
  • number
  • bigint
  • boolean
  • undefined
  • symbol
  • null

Why does this matter? Let's look at our example again with a slight modification.

let myVar = 20;

function passBy(x) {
    x = x * 10;
}

What happens if we remove the return statement? Looking at this function, we might expect it to simply modify the value of whatever variable we give it. As we have seen, though, this is not the case.


Pass by Reference

For non-primitive data types - objects and arrays, the program behaves differently. Since objects and arrays are complex collections of data, creating copies of them in memory too frequently would create inefficiencies in the way an application operates. Instead, these data types are passed by reference to functions and other expressions.

Modifying our simple example above, let's explore what this means:

let myVar = { value: 20, type: 'Number' }

function passBy(x) {
    x.value = x.value * 20;
}

You're expecting something different, at this point, so what exactly happens?

console.log(myVar);           // 20
passBy(myVar);                // 200
console.log(myVar);           // 20

Since myVar is an object, it is passed by reference. This means that the function-scoped variable x is set to the same memory location as myVar. They each reference the same location in memory. Any time a change is made to x, that change is also made to myVar.

It is extremely important to keep this in mind when working with objects and arrays. Pass by reference is a powerful tool when used properly, but can also lead to many unexpected behaviors and unforeseen bugs when not accounted for.

Let's look at another example using arrays:

let numArray = [10, 20, 0, -10, 15, 28, -1000, 42, 7];

function returnEven(array) {
    for (let i in array) {
        if (array[i] % 2 !== 0) {
            array.splice(i, 1);
        }
    }

    return array;
}

console.log(numArray);                  // (9) [10, 20, 0, -10, 15, 28, -1000, 42, 7]
let newArray = returnEven(numArray);
console.log(newArray);                  // (7) [10, 20, 0, -10, 28, -1000, 42]
console.log(numArray);                  // (7) [10, 20, 0, -10, 28, -1000, 42]

If we assume the intent for returnEven() was to return a new array of only even numbers, the fuction is successful; however, it also modifies the original array due to pass by reference. This means that the data in the original array is now lost!

There are a few ways around this behavior. For example, we could modify our original function to add elements to a new, empty array and then return that array instead of the one passed into our function parameters, as below.

let numArray = [10, 20, 0, -10, 15, 28, -1000, 42, 7];

function returnEven(array) {
    let newArray = [];

    array.forEach((element) => {
        if (element % 2 == 0) {
            newArray.push(element);
        }
    });

    return newArray;
}

console.log(numArray);                  // (9) [10, 20, 0, -10, 15, 28, -1000, 42, 7]
let newArray = returnEven(numArray);
console.log(newArray);                  // (7) [10, 20, 0, -10, 28, -1000, 42]
console.log(numArray);                  // (9) [10, 20, 0, -10, 15, 28, -1000, 42, 7]

Here, we've modified the function to push to a new array (creating a new location in memory that is separate from the original reference) instead of using splice to remove them from the original one. We've also changed our for...in loop to a forEach loop to demonstrate the differences between the two, but either would have worked in this scenario.

There are still issues with this approach, however. Inspect the following code, then discuss the answers to the questions that follow.

let numArray = [
    { value: 10 },
    { value: 20 },
    { value: 0 },
    { value: -10 }, 
    { value: 15 }, 
    { value: 28 }, 
    { value: -1000 },
    { value: 42 },
    { value: 7 }
];

function returnEven(array) {
    let newArray = [];

    array.forEach((element) => {
        if (element.value % 2 == 0) {
            newArray.push(element);
        }
    });

    return newArray;
}

let newArray = returnEven(numArray);

newArray[0].value += 10;

numArray[3].value = 67;

let myEvenValue = newArray[1].value;
myEvenValue /= 2;

After the execution of this code (click each question for an explanation of its answer)...

❓ What is the value of numArray[0].value?

Answer: 20. Since the elements of the arrays are objects, they are passed by reference to the push function. Modifying newArray[0].value also modifies numArray[0].value. Note that the indexes match only by coincidence. If we reversed the order of numArray, which has length 9, then the objects located at newArray[0] and numArray[8] would reference the same location in memory.

❓ What is the value of newArray[3].value?

Answer: 67. Since the elements of the arrays are objects, they are passed by reference to the push function. Modifying numArray[3].value also modifies newArray[3].value.

❓ What is the value of myEvenValue?

Answer: 10. newArray[1].value is equal to 20, and myEvenValue is modified using the /= operator to divide it by 2.

❓ What is the value of newArray[1].value?

Answer: 20. The actual .value property of the object located at newArray[1] is a number - a primitive data type. It is not passed by reference, even though it is contained within an object. Modifications to myEvenValue do not modify newArray[1].value, because the original value was copied in memory, not referenced.

As we can see, things get complicated when you nest non-primitive data types. Thankfully, there are a few ways of making this simpler.


Shallow Copies

A shallow copy, like the one demonstrated in the example above, is a copy of an object in which the properties share the same references as those of the source object. In our numArray and newArray example, we did create two separate arrays with separate locations in memory, but each of their elements was an object pointing to the same location in memory as its source object. This was a shallow copy.

By contrast, a "deep copy" removes all references to the original object, regardless of how many layers deep the nesting structure goes.

There are many ways to generically create shallow copies:

// Assume these variables are reset for each of the examples below.
let numbers = [[1], [2], [3]];
let numbersCopy = [];

// For Loops
for (let i = 0; i < number.length; i++) {
    numbersCopy[i] = numbers[i];
}

// While Loops
let j = -1;
while (++j < numbers.length) {
    numbersCopy[j] = numbers[j];
}

// Spread Operator
numbersCopy = [...numbers];

// Array.map
numbersCopy = numbers.map((ele) => ele);

These all create shallow copies of the original data. Note that the other array functions, such as filter, slice, reduce, from, and concat all create shallow copies.

You must keep in mind how the functions and expressions you are working with handle your data. Does the data get passed by reference or by value? Is the function creating a shallow copy, a deep copy, or operating on the data in place?


Deep Copies

So then, how do you create a deep copy?

If you know the exact data structure you are working with, you could create a function that copies that data, but would you want to? Here's an example:

const myPerson = {
    name: {
        first: 'Timmy',
        last: 'Timtim'
    },
    age: 30,
    location: {
        city: 'New York',
        state: 'New York',
        zip: 10001
    }
}

function copyPerson(oldPerson) {
    const newPerson = {
        name: {},
        age: null,
        location: {}
    };

    newPerson.name.first = oldPerson.name.first;
    newPerson.name.last = oldPerson.name.last;
    newPerson.age = oldPerson.age;
    newPerson.location.city = oldPerson.location.city;
    newPerson.location.state = oldPerson.location.state;
    newPerson.location.zip = oldPerson.location.zip;

    return newPerson;
}

const myPersonCopy = copyPerson(myPerson);

Testing this code will reveal that myPerson and myPersonCopy do not share any references, even in their nested objects.

Can you imagine doing this for a more complex object? There must be an easier way!

JavaScript provides two functions for dealing with JavaScript Object Notation (JSON) data:

  • JSON.stringify turns an object into a string.
  • JSON.parse turns a string into an object.

Since strings are primitive data types, they are always passed by value, not reference.

const myPerson = {
    name: {
        first: 'Timmy',
        last: 'Timtim'
    },
    age: 30,
    location: {
        city: 'New York',
        state: 'New York',
        zip: 10001
    }
}

function copyPerson(oldPerson) {
    let jsonString = JSON.stringify(oldPerson);

    return JSON.parse(jsonString);
}

const myPersonCopy = copyPerson(myPerson);

Note: This only works on serializable objects. Many JavaScript objects are not serializable. Ror example, functions (with closures), Symbols, objects that represent HTML elements, recursive data, and many other cases. JSON.stringify() will fail when attempting to stringify these non-serializable objects, so there's no way to make deep copies of such objects.


Working with Pass by Value and Pass by Reference

There are some challenges within ALAB 308H.5.3 - Pass by Value and Pass by Reference that will allow you to practice these topics and become more familiar with how to use them in practical ways. Work with a partner to solve these challenges, and speak with your instructors if you get stuck.

Copyright © Per Scholas 2024