Great Confusion About Differences Between Regular and Arrow Functions

kirill ibrahim
8 min readSep 13, 2024

--

In JavaScript, there are two main ways to write functions: regular functions and arrow functions.
While they might look similar, there are some key differences between them that can be tricky to understand. These differences often come up in interviews, where you might face questions that test your knowledge on these details. In this article, we’ll walk through the major differences between regular and arrow functions so you can better understand them:

1. this value
2. constructors
3. Hoisting
4. Arguments objects
5. Return Values
6. Duplicated Named Parameters
7. Methods in classes

1. this Value

1.1 Regular Function

In a regular JavaScript function, the value of this (also known as the execution context) is dynamic. This means that this can change depending on how the function is called. There are four main ways to call a regular function, and each affects the value of this differently:

  • Simple Invocation: If you call a regular function directly, this will point to the global object (or undefined in strict mode).
function myFunction() {
console.log(this);
}

// Simple invocation
myFunction(); // logs global object (window)
  • Method Invocation: When a function is called as a method of an object, this points to the object that owns the method.
const myObject = {
method() {
console.log(this);
}
};

// Method invocation
myObject.method(); // logs myObject
  • Indirect Invocation: If you use myFunction.call(thisVal) or myFunction.apply(thisVal), this will be set to the first argument you pass.
function myFunction() {
console.log(this);
}

const myContext = { value: 'A' };

myFunction.call(myContext); // logs { value: 'A' }
  • Constructor Invocation: When you use the new keyword, this refers to the new instance being created.
function MyFunction() {
console.log(this);
}

new MyFunction(); // logs an instance of MyFunction
function MyFunction(name) {
this.name = name;
console.log(this);
}

new MyFunction("Alex"); // logs: MyFunction {name: 'mark'}

1.2 Arrow Function

Arrow functions work differently when it comes to this. They don’t have their own this context. Instead, they inherit this from the surrounding code (the lexical scope).

No matter how or where you call an arrow function, this inside it will always refer to the this value from the outer function. This behavior is one of the main differences between regular and arrow functions.

Here’s an example:

const myObject = {
name: "alex",
myMethod(items) {
const callback = () => {
console.log(this);
};
items.forEach(callback);
}
};

myObject.myMethod([1, 2, 3]); // logs myObject 3 times: {name: 'kiro', myMethod: ƒ}

In this example, myMethod() is the outer function, and callback() is the arrow function. The this value inside callback() is the same as in myMethod(), which is myObject. This lexical scoping of this is a great feature of arrow functions, making them useful in situations where you don’t want to lose the this context, like in callbacks.

But this can also cause some surprises. Consider this interview question:

let x = {
name: "alex",
arrow: () => {
console.log(this.name);
},
reg: function regular(){
console.log(this.name);
}
};

x.arrow(); // Output?
x.reg(); // Output?

Explanation:

  • x.arrow(): Since arrow functions don’t have their own this, it refers to the this from the surrounding scope, which is the global object (window in browsers). So, window.name is undefined, and the output is undefined.
  • x.reg(): Here, this points to the object x, so this.name will output "ahmed".

Final Output:

  • x.arrow(): undefined
  • x.reg(): "ahmed"

Understanding how this works in regular vs. arrow functions is crucial for writing reliable JavaScript code, especially when you’re dealing with objects and callbacks.

2. Constructors

2.1 Regular Function

Regular functions in JavaScript can be used as constructors. This means they can create instances of objects when used with the new keyword. For example, consider a function that creates car objects:

function Person(name) {
this.name = name;
}

const alex = new Person('alex');
console.log(alex instanceof Person); // => true

console.log(alex ); // => Person {name: 'alex'}

In this example, Person is a regular function. When you call it with new Person('alex'), it creates a new instance of Person, and this inside the function refers to that new instance. This is how regular functions can be used to construct objects.

2.2 Arrow Function

Arrow functions, on the other hand, cannot be used as constructors. This is because they don’t have their own this context—they rely on the surrounding context instead. If you try to use an arrow function as a constructor, JavaScript will throw an error.

const Person = (name) => {
this.name = name;
}

const alex = new Person('alex'); // TypeError: Person is not a constructor

In this case, attempting to create a new Person instance using an arrow function results in a TypeError, because arrow functions simply don’t support this kind of operation. This is a key limitation of arrow functions that you need to be aware of.

3. Hoisting

3.1 Hoisting in Regular Functions

In JavaScript, function declarations are hoisted to the top of their containing scope. This means you can call a regular function even before it’s defined in the code.

regularFunction();

function regularFunction() {
console.log("This is a regular function.");
}
// logs "This is a regular function."

In this example, regularFunction is called before its definition, but it works just fine because of hoisting.

3.2 Hoisting in Arrow Functions

Arrow functions, however, are not hoisted. If you try to call an arrow function before it’s defined, you’ll get a ReferenceError.

arrowFunction();

const arrowFunction = () => {
console.log("This is an arrow function.");
};
// Uncaught ReferenceError: Cannot access 'arrowFunction' before initialization

In this case, attempting to call arrowFunction before its declaration results in an error, showing that arrow functions need to be defined before they’re used.

4. Arguments Object

4.1 Regular Function

In a regular function, there’s a special object called arguments that contains all the arguments passed to the function. This object is useful when you need to work with a variable number of arguments.

function myFunction() {
console.log(arguments);
}

myFunction('a', 'b'); // logs Arguments(2) ['a', 'b', callee: ƒ, length: 2, Symbol(Symbol.iterator): ƒ]

Here, arguments is an array-like object that holds all the arguments passed to myFunction. You can access each argument using an index, just like you would with an array.

Example from official MDN:

function func1(a, b, c) {
console.log(arguments[0]);
// Expected output: 1

console.log(arguments[1]);
// Expected output: 2

console.log(arguments[2]);
// Expected output: 3
}

func1(1, 2, 3);

4.2 Arrow Function

Arrow functions do not have an arguments object. This can be surprising if you’re used to working with regular functions.

const myArrowFunction = () => {
console.log(arguments);
};

myArrowFunction('c', 'd'); // Uncaught ReferenceError: arguments is not defined

Instead, they inherit arguments from the surrounding function (if there is one): Just like with this, the arguments object in an arrow function is resolved lexically, meaning it grabs the arguments from the outer function.
Let’s see this in action:

function myRegularFunction() {
const myArrowFunction = () => {
console.log(arguments); //Arguments(2) ['a', 'b', callee: ƒ, Symbol(Symbol.iterator): ƒ]
}

myArrowFunction('c', 'd');
}

myRegularFunction('a', 'b');

In this example, myArrowFunction() is called with 'c' and 'd', but inside its body, arguments actually refers to the arguments from myRegularFunction()—so it logs 'a' and 'b' instead.

Since arrow functions don’t have their own arguments object, trying to access it directly inside an arrow function will result in a ReferenceError.

If you need to handle arguments in an arrow function, you can use the rest parameter syntax to capture them:

const myArrowFunction = (...args) => {
console.log(args);
};

myArrowFunction('c', 'd'); // Uncaught ReferenceError: arguments is not defined

In this example, the rest parameter ...args collects all the arguments passed to the arrow function into an array.

5. Return Values

5.1 Regular Function

In regular functions, you can return a value using the return statement. If no return statement is present, or if it’s empty, the function returns undefined by default.

function myFunction() {
return 42;
}

console.log(myFunction()); // => 42

function myEmptyFunction() {
42;
}

console.log(myEmptyFunction()); // => undefined

In the first function, the value 42 is explicitly returned. In the second function, since there’s no return statement, the function returns undefined.

5.2 Arrow Function

Arrow functions also use the return statement, but they offer a shortcut when the function body consists of a single expression. In such cases, you can omit the curly braces and the return keyword—the value of the expression will be returned automatically.

const increment = (num) => num + 1;
console.log(increment(41)); // => 42

Here, the arrow function increment returns the result of num + 1 without needing an explicit return statement, making the code more concise.

6. Duplicate Named Parameters

6.1 Duplicate Parameters in Regular Functions

In regular functions, if you have duplicate parameter names, the last one takes precedence (priority). This behavior changes if you’re in strict mode.

function exampleFunction(a, b, a) {
console.log(a, b);
}
exampleFunction("first", "second", "third"); // logs "third second"

In non-strict mode, the second a parameter overrides the first, so the output is "third second". But in strict mode, this would cause a syntax error:

"use strict";
function exampleFunction(a, b, a) {
console.log(a, b);
}
exampleFunction("first", "second", "third");
// Uncaught SyntaxError: Duplicate parameter name not allowed in this context

6.2 Duplicate Parameters in Arrow Functions

Arrow functions do not allow duplicate parameter names, even in non-strict mode. Attempting to use duplicate names will result in a syntax error.

const exampleFunction = (a, b, a) => {
console.log(a, b);
}
exampleFunction("first", "second", "third");
// Uncaught SyntaxError: Duplicate parameter name not allowed in this context
This strict enforcement helps prevent accidental bugs in your code.

7. Methods in Classes

7.1 Regular Function

Regular functions are typically used to define methods in JavaScript classes. When used this way, the function’s this value refers to the class instance.

class Person {
constructor(personName) {
this.personName = personName;
}
logName() {
console.log(this.personName);
}
}
const alex = new Person('alex');

console.log(alex.logName()); // alex

In this example, the logName method is a regular function that prints the hero’s name. The this value inside logName refers to the Person instance.

However, when you pass logName as a callback (for example, to setTimeout), you might run into issues with the this value:

setTimeout(alex.logName, 1000); // after 1 second logs "undefined"

Here, setTimeout calls logName with a simple invocation, causing this to point to the global object, not the alex instance.

To fix this, you can manually bind this to the correct context:

setTimeout(alex.logName.bind(alex), 1000); // after 1 second logs "Batman"

While this works, it can be cumbersome if you have many methods to bind.

7.2 Arrow Function

Arrow functions offer a solution to the this binding problem in classes. When you use an arrow function as a method, it binds this lexically to the class instance automatically.

class Person {
constructor(personName) {
this.personName = personName;
}
logName = ()=> {
console.log(this.personName);
}
}
const alex = new Person('alex');

setTimeout(alex.logName, 1000);

With the arrow function logName, you don’t need to worry about manually binding this. Even when used as a callback, this remains bound to the alex instance:

setTimeout(alex.logName, 1000); // after 1 second logs "Batman"

Conclusion

Understanding the differences between regular and arrow functions in JavaScript is crucial for writing efficient, bug-free code. Whether it’s how this is handled, the ability to use functions as constructors, or how hoisting works, each has its own set of rules that can trip you up if you’re not careful. By mastering these differences, you’ll be better prepared for coding challenges and interviews, and you'll know when to use each type of function in your projects.

Good luck with your coding journey, and happy coding!

--

--

kirill ibrahim

I am a Web Front-End Engineer with 8 years of commercial experience working in various web-related roles. https://kirillibrahim.work/ My Twitter: @IbraKirill