3. Functions

Follow along with code examples here!

Table of Contents:

Summary

  • Functions are named containers for statements that can be invoked to execute code, improving readability and reducing repetition.

  • Parameters are variables defined in a function declaration, allowing functions to accept input and change behavior. Arguments are the actual values passed when calling a function.

  • Return statements allow functions to produce values that can be used elsewhere in your program and terminate function execution.

  • Scope determines where variables can be accessed. Global scope variables are accessible everywhere, while local scope variables are only accessible within their function or block.

  • There are multiple ways to define functions: arrow functions (preferred), function declarations, and function expressions. Arrow functions can use implicit returns for concise syntax.

  • Using functions helps you follow the "Don't Repeat Yourself" (DRY) principle and write more maintainable code.

Variables Review

Recall that variables are named containers for data.

Q: What are the benefits of using Variables in our code?

By labeling our data, variables improve the readability of our code and minimize the repetition of commonly used calculations.

console.log(`The sum of 4 + 3 + 2 + 1 is ${4 + 3 + 2 + 1} and the average is ${(4 + 3 + 2 + 1) / 4}`);
// Output: The sum of 4 + 3 + 2 + 1 is 10 and the average is 2.5

const sum = 4 + 3 + 2 + 1;  
const average = sum / 4;   
const message = `The sum of 4 + 3 + 2 + 1 is ${sum} and the average is ${average}`;
console.log(message);
// Output: The sum of 4 + 3 + 2 + 1 is 10 and the average is 2.5

Suppose we wanted to sum, average, and then print the same message as above but with a different set of numbers. We could write code like this:

const sum1 = 4 + 3 + 2 + 1;
const average1 = sum1 / 4;
console.log(`The sum of 4 + 3 + 2 +1 is ${sum1} and the average is ${average1}`);
// Output: The sum of 4 + 3 + 2 + 1 is 10 and the average is 2.5

const sum2 = 5 + 6 + 7 + 8;
const average2 = sum2 / 4;
console.log(`The sum of 5 + 6 + 7 + 8 is ${sum2} and the average is ${average2}`);
// Output: The sum of 5 + 6 + 7 + 8 is 26 and the average is 6.5

const sum3 = -2 + -1 + 1 + 2;
const average3 = sum3 / 4;
console.log(`The sum of -2 + -1 + 1 + 2 is ${sum3} and the average is ${average3}`);
// Output: The sum of -2 + -1 + 1 + 2 is 0 and the average is 0
Q: What isn't great about this code?

It breaks the fundamental software engineering principle "DRY" which stands for "Don't Repeat Yourself". Instead, we should use functions!

Functions

A Function is a reusable container of statements:

  • Functions improve the readability of our code and minimize repetition of commonly used statements.

  • A function can be invoked to execute its statements. A function can be invoked many times.

The same result can be achieved with a printSumAndAverage function:

const printSumAndAverage = (a, b, c, d) => {
  const sum = a + b + c + d;
  const average = sum / 4;
  console.log(`The sum of ${a} + ${b} + ${c} + ${d} is ${sum} and the average is ${average}`);
}

printSumAndAverage(4, 3, 2, 1)
// Output: The sum of 4 + 3 + 2 + 1 is 10 and the average is 2.5

printSumAndAverage(5, 6, 7, 8);
// Output: The sum of 5 + 6 + 7 + 8 is 26 and the average is 6.5

printSumAndAverage(-2, -1, 1, 2);
// Output: The sum of -2 + -1 + 1 + 2 is 0 and the average is 0

We first create the function to contain the logic, give it a descriptive name, and then invoke the function to execute its statements with minimal repetition.

Challenge: Refactor this highly repetitive code for converting a temperature from fahrenheit into celsius. What should the function be called? What are the inputs that make each instance of code different (and what should those parameters be called)?

const tempC1 = 0
const tempF1 = (tempC1 * 9 / 5) + 32;
console.log(`${tempC1}°C is ${tempF1}°F`);
// Output: 0°C is 32°F

const tempC2 = 37
const tempF2 = (tempC2 * 9 / 5) + 32;
console.log(`${tempC2}°C is ${tempF2}°F`);
// Output: 37°C is 98.6°F

const tempC3 = 100
const tempF3 = (tempC3 * 9 / 5) + 32;
console.log(`${tempC3}°C is ${tempF3}°F`);
// Output: 100°C is 212°F

Function Calls

After a function is declared, the function must be invoked in order to use the function's code.

// Adding parentheses after the function name invokes the function
functionName();

Invoking (or "calling") a function causes the program to jump to the first line of code in the function, setting it as the next line of code to be executed.

// 1. console.log is invoked
console.log('This happens first');

// 2. Function sayHello is defined
const sayHello = () => {
  // 5 & 7. console.log is invoked (and again)
  console.log('hello');
}

// 3. console.log is invoked
console.log('this happens second');

// 4. sayHello is invoked
sayHello(); 

// 6. sayHello is invoked again
sayHello();

Note that the order in which statements are executed in our code is not always top to bottom. Declaring the function doesn't cause the code inside to run. We only execute the code inside of sayHello when it is invoked a few lines later.

Experiment: Run this program using the debugger to see how the control flow of the program is affected by invoking a function.

Parameters and Arguments

The function sayHello does the same thing. Every. Single. Time. It always prints "hello".

Parameters are variables created inside of a function that enable a function to change its behavior based on arguments provided in the function call.

  • Parameters go in the parentheses of a function declaration (separated by commas).

  • Arguments go in the parentheses of the function call (also separated by commas).

  • The order of the arguments must match the order of the parentheses.

// printSum can add and print any two given values
// x and y are parameters that reference the values provided when the function is called.
const printSum = (x, y) => {
  console.log(x + y);
}

// 5 and 3 are the arguments. 8 is printed
printSum(5, 3);

// This time, 10 and 2 are the arguments. 12 is printed
printSum(10, 2);

// There is no type-checking, the function will just concatenate these values
// 'hello5' is printed
printSum('hello', 5);

// If an argument is not provided, the parameter will be undefined
// undefined + undefined => NaN is printed
printSum();

Challenge: Refactor this function so that it can print any name and hobby.

const sayHello = () => {
  console.log("Hi, my name is Ben. I like to code!");
}

sayHello();

Test your code by invoking the function with various inputs. What happens when no input is provided?

Solution
const sayHello = (name, hobby) => {
  console.log(`Hi, my name is ${name}$. I like to ${hobby}!`);
}

sayHello("Ben", "rock climb");
// Output: Hi, my name is Ben. I like to rock climb!

sayHello("Gonzalo", "play the bass");
// Output: Hi, my name is Gonzalo. I like to play the bass!

sayHello();
// Output: Hi, my name is undefined. I like to undefined!

Return Statements

The functions above use console.log to print out a result to the console, but that result can't be used later in the program. If we want a function to produce a value that we can be used outside of the function, we add a return statement.

A return statement does two things:

  1. It returns a value to the location of the function call.

  2. It terminates the execution of the function

const add = (x, y) => {
  return x + y;
}

// The function call is replaced by its return value, resolving to `console.log(8)`
console.log(add(5, 3));

Function Call Resolution

When a function like console.log() has another function call inside, like add(5,3), the inner function call is resolved first.

Challenge: What is the result of this expression? Run this with the debugger to find out!

// What is the result of this code? What is the order in which these calls are resolved?
const result = add(add(1, 2), add(3,4));
console.log(result);

Use the "step in" and "step out" buttons to go in and out of specific function calls.

Multiple Returns

A function can have multiple return statements, but only one is ever executed. This is helpful if the value returned depends on the provided argument.

const isPositiveOrNegative = (num) => {
  if (num === 0) {
    return "Neither"
  } 
  if (num > 0) {
    return "Positive"
  }
  return "Negative"
}

console.log(isPositiveOrNegative(-5)); // Output: "Negative"
console.log(isPositiveOrNegative(5)); // Output: "Positive"
console.log(isPositiveOrNegative(0)); // Output: "Neither"

Arrow Functions vs. Function Declarations vs. Function Expressions

There are many ways to create a function, but we will use Arrow Function syntax:

// Best — Arrow Functions are stored in constant variables and aren't hoisted.
const multiply = (x, y) => {
  return x * y;
}

// Not Great — Function Declaration. It is hoisted which can cause unexpected behavior.
function add(x, y) {
  return x + y;
}


// Not Great — Function Expression. It is not hoisted but just use arrow functions!
const divide = function(x, y) {
  return x / y;
}

One nice feature of arrow functions is the implicit return syntax: if the function's body is just a return statement, you can omit the {} and the return keyword.

// Totally Fine - An explicit return
const add = (a, b) => {
  return a + b;
}

// Better - An "implicit return"
const add = (a, b) => a + b;

Challenge: Which of these functions can use implicit returns? Refactor them!

const add = (a, b) => {
  return a + b;
};

const greet = (name) => {
  return `Hello, ${name}!`;
};

const logMessage = (message) => {
  console.log(message);
  return 'message logged';
};

Last updated