Functions and Scoping

· Seokhyeon Byun

Note: This post is based on my old programming study notes when I taught myself.

Normal Functions

Syntax:

function functionName(parameters) {
  return RESULT;
}
  • A list of parameters are enclosed by parentheses and separated by commas.
  • Statements that define the function are enclosed by curly brackets { }
  • The statement return specifies the value returned by the function.

Types of parameters

  1. Value parameter
  2. Object parameter
  3. Array parameter

Passing Functions as Arguments

Before understanding of Passing functions as arguments, it is required to get familiar with Function expressions first.

Function Expressions

Function Expression means that function keyword can be used to define a function inside an expression.

Sometimes function can be anonymous; it does not have to have name and in this case, we can use function expression.

const mathSquare = function (number) {
  return number * number;
};

const x = mathSquare(3); // x gets 9

In above code, a name is provided with a function expression. Providing a name allows the function to refer to itself.

Now, below code is example of passing a function as an argument to another function.

//Normal function map
function map(f, a) {
  const result = new Array(a.length);
  for (let i = 0; i < a.length; i++) {
    result[i] = f(a[i]);
  }
  return result;
}

//Using Function expression
const f = function (x) {
  return x * x * x;
};

//Array 'numbers'
const numbers = [0, 1, 2, 5, 10];

const cube = map(f, numbers);
console.log(cube); //[0, 1, 8, 125, 1000]

In above code, map function that receives a function as first argument and an array as second argument. A function f is defined by function expression.

Arrow Functions(a.k.a fat arrow(=>))

Syntax of arrow functions

Refer: MDN Arrow functions

Case 1:

(param) => expression;

Case 2:

(param1, paramN) => expression;

Case 3:

(param) => {
  statements;
};

Case 4:

(param1, paramN) => {
  statements;
};

Features of Arrow function expressions:

  1. Always anonymous, unnamed.
  2. Shorter syntax compared to function expressions
  3. Arrow functions don’t have its own this, argument, super, or new.target and cannot be used as methods.
  4. Arrow functions cannot be used as constructors. Calling them with new throws a TypeError. They also don’t have access to the new.target keyword.
  5. Arrow functions cannot use yield within their body and cannot be created as generator functions.
  6. Rest parametersdefault parameters, and destructuring within params are supported, and always require parentheses.
  7. Like Case 1 and Case 2, parentheses around the parameter can only be omitted if the function has a single parameter.
  8. Parentheses around parameters are required when it has
    • multiple parameters
    • no parameters
    • default destructed
    • or rest parameters
  9. The braces { } can be omitted if the function directly returns an expression.
  10. If the body has additional lines of processing, the braces { } and return are required.
  11. If arrow function needs to call itself, use a named function expression instead, or assign the arrow function to a variable so it has a name.

Example:

const func = (x) => x * x;
// concise body syntax, implied "return"

const func2 = (x, y) => {
  return x + y;
};
// with block body, explicit "return" needed
  1. Returning object literals using the concise body syntax (params) => { object: literal } does not work, because JavaScript only sees the arrow function as having a concise body if the token following the arrow is not a left brace, so the code inside braces () is parsed as a sequence of statements, not a key in an object literal.
    • To fix this, wrap the object literal in parentheses: const func=()=>({foo:1})

Stack Trace and Call Stack

A call stack is typically “the current stack of operations” - i.e. while it’s running. A stack trace is typically a copy of the call stack which is logged at some sort of failure, e.g. an exception.

In other words, while you’re debugging you will look at the current call stack - but when you look at logs, you’ll get a stack trace.

How Call Stack Works

Based on Advanced Javascript Error and Stack trace:

  1. Function Calling: Every time a function is called, it gets pushed onto the top of the stack.
  2. Function Return: When a function finishes executing, it gets removed from the top of the stack.
  3. LIFO Structure: The stack follows Last In, First Out (LIFO) principle - the last item added is the first to be removed.
function first() {
  console.log("First function start");
  second();
  console.log("First function end");
}

function second() {
  console.log("Second function start");
  third();
  console.log("Second function end");
}

function third() {
  console.log("Third function");
}

first();
// Call stack: first() -> second() -> third()
// Output order: First start -> Second start -> Third -> Second end -> First end

Interview Question: Explain how JavaScript’s call stack works and what happens during a stack overflow.

Answer: The call stack keeps track of function calls in LIFO order. Stack overflow occurs when too many functions are called without returning, typically in infinite recursion scenarios.


Hoisting

Definition: Hoisting is JavaScript’s behavior of moving var declarations and function declarations to the top of their scope during compilation phase.

Key Points:

  • JavaScript is synchronous, so hoisting completes before code execution begins
  • JavaScript interpreter hoists the entire function declaration to the top of the current scope
  • Function hoisting only works with function declarations — not with function expressions

Hoisting Examples

// Variable hoisting with var
console.log(x); // undefined (not ReferenceError)
var x = 5;

// Equivalent to:
var x;
console.log(x); // undefined
x = 5;

// Function declaration hoisting
sayHello(); // "Hello!" - works due to hoisting

function sayHello() {
  console.log("Hello!");
}

// Function expression - NOT hoisted
sayGoodbye(); // TypeError: sayGoodbye is not a function
var sayGoodbye = function () {
  console.log("Goodbye!");
};

Interview Question: What’s the difference between var, let, and const hoisting?

Answer:

  • var: Hoisted and initialized with undefined
  • let/const: Hoisted but not initialized (Temporal Dead Zone)
  • Function declarations: Fully hoisted (can be called before declaration)

Refer to MDN Hoisting doc

Scoping

Definition: The scope is the current context of execution in which values and expressions are “visible” or can be referenced. If a variable or expression is not in the current scope, it will not be available for use.

Key Principle: Scopes can be layered in a hierarchy, so that child scopes have access to parent scopes, but not vice versa.

Types of Scope

1. Global Scope

Variables declared outside any function or block have global scope.

var globalVar = "I'm global";
let globalLet = "I'm also global";

function test() {
  console.log(globalVar); // Accessible
  console.log(globalLet); // Accessible
}

2. Function Scope

Variables declared inside a function are only accessible within that function.

function myFunction() {
  var functionScoped = "Only accessible inside function";

  function innerFunction() {
    console.log(functionScoped); // Accessible (closure)
  }
}

// console.log(functionScoped); // ReferenceError

3. Block Scope (let and const)

Variables declared with let and const have block scope.

if (true) {
  var varVariable = "I'm function scoped";
  let letVariable = "I'm block scoped";
  const constVariable = "I'm also block scoped";
}

console.log(varVariable); // "I'm function scoped"
// console.log(letVariable); // ReferenceError
// console.log(constVariable); // ReferenceError

Lexical Scoping and Closures

JavaScript uses lexical scoping - functions have access to variables in their outer scope at the time they were defined.

function outerFunction(x) {
  // Outer scope
  function innerFunction(y) {
    // Inner scope - has access to 'x'
    return x + y;
  }
  return innerFunction;
}

const addFive = outerFunction(5);
console.log(addFive(3)); // 8 (closure maintains access to x=5)

Interview Question: Explain the difference between function scope and block scope. Give examples.

Answer:

  • Function scope: Variables declared with var are function-scoped, accessible throughout the entire function
  • Block scope: Variables declared with let/const are block-scoped, only accessible within the nearest enclosing block {}

Interview Question: What is a closure and how does it work?

Answer: A closure is when an inner function has access to variables from its outer (enclosing) scope even after the outer function has finished executing. The inner function “closes over” the outer function’s variables.

Refer to MDN Scope doc