Arrow Functions vs Normal Functions in JavaScript

Episode - Arrow Functions vs Normal Functions in JavaScript — The Complete Guide

Hey everyone! Welcome back to Namaste JavaScript. Today we are going to study one of the most confusing yet important topics in JavaScript — Arrow Functions vs Normal Functions!

I am super excited because this topic is asked in almost every JavaScript interview. Once you truly understand the difference, you will know exactly when to use which — and you will never get confused again!

What we will cover:

  • What is a Normal Function?
  • What is an Arrow Function?
  • Syntax Differences
  • The this Keyword — The Biggest Difference!
  • The arguments Object
  • Constructor Functions and the new Keyword
  • The prototype Property
  • Implicit Return
  • Hoisting Behavior
  • When to Use Which
  • Famous Interview Questions

First — What is a Normal Function?

Normal functions (also called regular functions or function declarations/expressions) are the traditional way to define functions in JavaScript.

// Function Declaration
function greet(name) {
    return "Hello, " + name + "!";
}

// Function Expression
const greet = function(name) {
    return "Hello, " + name + "!";
};

greet("Shreyesh");

OUTPUT:
Hello, Shreyesh!
Normal Function has:
=====================

✅ Its own 'this' binding
✅ Its own 'arguments' object
✅ Can be used as a constructor (new keyword)
✅ Has a 'prototype' property
✅ Hoisted (function declarations only)

What is an Arrow Function?

Arrow functions were introduced in ES6 (2015). They provide a shorter, cleaner syntax — but they also have very different behavior from normal functions under the hood!

// Arrow Function
const greet = (name) => {
    return "Hello, " + name + "!";
};

// Even shorter with implicit return!
const greet = name => "Hello, " + name + "!";

greet("Shreyesh");

OUTPUT:
Hello, Shreyesh!
Arrow Function has:
====================

❌ No own 'this' — borrows from outer scope
❌ No 'arguments' object
❌ Cannot be used as a constructor
❌ No 'prototype' property
✅ Shorter syntax
✅ Implicit return
✅ Great for callbacks!

Syntax Differences

Let's compare the syntax side by side!

// ─── Normal Function ──────────────────────────────
function add(a, b) {
    return a + b;
}

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

// ─── Arrow with Implicit Return ───────────────────
const add = (a, b) => a + b;

// All three do the same thing!
console.log(add(3, 4));  // 7
Arrow Function Syntax Rules:
==============================

// No parameters → empty parentheses required
const sayHi = () => "Hi!";

// One parameter → parentheses optional
const double = x => x * 2;
const double = (x) => x * 2;  // also valid

// Multiple parameters → parentheses required
const add = (a, b) => a + b;

// Returning an object literal → wrap in parentheses!
const getUser = () => ({ name: "Shreyesh", age: 25 });
//                     ↑ without () → JS thinks {} is function body!

// Multi-line body → use curly braces + explicit return
const calculate = (a, b) => {
    const result = a * b;
    return result;
};

The 'this' Keyword — The BIGGEST Difference!

This is the most important difference between arrow and normal functions. Get this right and you will understand 90% of the confusion!

Normal Function: 'this' depends on HOW it is called

// Normal Function — 'this' changes based on call site!

const user = {
    name: "Shreyesh",
    greet: function() {
        console.log("Hello, " + this.name);
        // 'this' refers to 'user' object here!
    }
};

user.greet();

OUTPUT:
Hello, Shreyesh
// The Problem — 'this' gets lost inside nested functions!

const user = {
    name: "Shreyesh",
    hobbies: ["coding", "reading"],
    showHobbies: function() {
        this.hobbies.forEach(function(hobby) {
            // ❌ 'this' is NOT 'user' here!
            // 'this' is window/undefined inside regular callback!
            console.log(this.name + " likes " + hobby);
        });
    }
};

user.showHobbies();

OUTPUT (in browser):
undefined likes coding
undefined likes reading

// 'this.name' is undefined because inside the callback,
// 'this' refers to the global object (window), NOT user!
The Old Fix — Store 'this' in a variable:
==========================================

const user = {
    name: "Shreyesh",
    hobbies: ["coding", "reading"],
    showHobbies: function() {
        const self = this;  // 'self' captures the correct 'this'
        this.hobbies.forEach(function(hobby) {
            console.log(self.name + " likes " + hobby);
        });
    }
};

user.showHobbies();

OUTPUT:
Shreyesh likes coding
Shreyesh likes reading

Arrow Function: 'this' is LEXICALLY inherited

Arrow functions do NOT have their own 'this'. They inherit 'this' from their surrounding lexical scope — wherever they are defined!

// ✅ Arrow function fixes 'this' automatically!

const user = {
    name: "Shreyesh",
    hobbies: ["coding", "reading"],
    showHobbies: function() {
        this.hobbies.forEach((hobby) => {
            // ✅ Arrow function inherits 'this' from showHobbies!
            // 'this' is still 'user' here!
            console.log(this.name + " likes " + hobby);
        });
    }
};

user.showHobbies();

OUTPUT:
Shreyesh likes coding
Shreyesh likes reading
Why Does This Work?
====================

Arrow function does NOT create its own 'this'.
It LOOKS UP the scope chain for 'this'.

Inside showHobbies() → 'this' = user object
Arrow function inside forEach → "Where is this?"
  → Looks at surrounding scope → showHobbies → 'this' = user ✅

Think of it like closures!
Arrow function closes over 'this' from its parent scope.

┌─────────────────────────────────────────────┐
│          Normal Function                    │
│  Has OWN 'this' — set by caller             │
│  this = depends on HOW function is called   │
└─────────────────────────────────────────────┘

┌─────────────────────────────────────────────┐
│          Arrow Function                     │
│  NO own 'this' — inherited from lexical     │
│  scope (where function was WRITTEN)         │
│  this = depends on WHERE function is defined│
└─────────────────────────────────────────────┘

The Danger — Arrow Functions as Object Methods!

Arrow functions should NOT be used as object methods because of 'this'!

// ❌ Wrong — Arrow function as object method!

const user = {
    name: "Shreyesh",
    greet: () => {
        // 'this' is NOT 'user' here!
        // Arrow function inherited 'this' from GLOBAL scope!
        console.log("Hello, " + this.name);
    }
};

user.greet();

OUTPUT (in browser):
Hello, undefined

OUTPUT (in Node.js):
Hello, undefined

// Arrow function doesn't have its own 'this',
// so it looks up to the GLOBAL scope where 'this' is window/{}
// NOT the user object!
// ✅ Correct — Use normal function as object method!

const user = {
    name: "Shreyesh",
    greet: function() {
        console.log("Hello, " + this.name);
    }
};

user.greet();

OUTPUT:
Hello, Shreyesh
Rule to Remember:
==================

Object METHOD → use Normal Function
  (needs its own 'this' pointing to the object)

Callback INSIDE method → use Arrow Function
  (inherit 'this' from the method)

const obj = {
    method: function() {    // ← Normal Function (has own 'this')
        [1,2,3].map(n => {  // ← Arrow Function (borrows 'this')
            console.log(this);  // 'this' = obj ✅
        });
    }
};

The 'arguments' Object

Normal functions have a special arguments object — arrow functions do NOT!

// Normal Function — has 'arguments' object

function sum() {
    console.log(arguments);
    let total = 0;
    for (let i = 0; i < arguments.length; i++) {
        total += arguments[i];
    }
    return total;
}

sum(1, 2, 3, 4, 5);

OUTPUT:
Arguments(5) [1, 2, 3, 4, 5]
15
// ❌ Arrow Function — NO 'arguments' object!

const sum = () => {
    console.log(arguments);  // ReferenceError!
};

sum(1, 2, 3);

OUTPUT:
ReferenceError: arguments is not defined

// Arrow functions do NOT have their own 'arguments' object!
// They will inherit 'arguments' from enclosing normal function (if any)
// ✅ Arrow Function — Use REST parameters instead!

const sum = (...nums) => {
    console.log(nums);  // actual array!
    return nums.reduce((total, n) => total + n, 0);
};

sum(1, 2, 3, 4, 5);

OUTPUT:
[1, 2, 3, 4, 5]
15

// REST parameters are a proper Array!
// 'arguments' is array-LIKE (not a real array)
// So REST is actually BETTER than 'arguments'!
arguments vs rest parameters:
================================

arguments:
  ✅ Available in normal functions automatically
  ❌ Not a real array (can't use .map, .filter, .reduce)
  ❌ Not available in arrow functions
  ❌ Includes ALL arguments even if not named

rest parameters (...args):
  ✅ A real Array — all array methods work!
  ✅ Available in BOTH normal and arrow functions
  ✅ Only captures extra parameters
  ✅ Modern and preferred way

Constructor Functions and the 'new' Keyword

// ✅ Normal Function — can be used as a constructor!

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

const shreyesh = new Person("Shreyesh", 25);
console.log(shreyesh.name);  // Shreyesh
console.log(shreyesh.age);   // 25
console.log(shreyesh instanceof Person);  // true
// ❌ Arrow Function — CANNOT be a constructor!

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

const shreyesh = new Person("Shreyesh", 25);

OUTPUT:
TypeError: Person is not a constructor

// Arrow functions cannot be used with 'new'!
// Reason: 'new' needs its own 'this', which arrow functions don't have!
What does 'new' actually do?
==============================

When you call: new Person("Shreyesh", 25)

1. A new empty object {} is created
2. 'this' inside the function is set to that new object
3. Properties are added to 'this'
4. The new object is returned

Arrow functions have NO 'this' of their own!
So step 2 fails → TypeError!

The 'prototype' Property

// Normal Function — HAS a prototype property

function greet() {}
console.log(greet.prototype);  // { constructor: greet }
// Used for prototype-based inheritance!

// Arrow Function — NO prototype property!

const greet = () => {};
console.log(greet.prototype);  // undefined
Why Does This Matter?
======================

Normal functions → used to build classes/constructors:

function Animal(name) {
    this.name = name;
}
Animal.prototype.speak = function() {
    console.log(this.name + " speaks!");
};

const dog = new Animal("Dog");
dog.speak();  // Dog speaks!

Arrow functions CAN'T do this — no prototype!

This is why ES6 Classes use normal functions internally.
class is just syntactic sugar over prototype-based functions!

Implicit Return — Arrow Functions Only!

This is one of the nicest features of arrow functions!

// Normal Function — always needs explicit return

function double(n) {
    return n * 2;  // 'return' is required!
}

// Arrow Function — implicit return when no curly braces!

const double = n => n * 2;  // No 'return' needed!

console.log(double(5));  // 10
Implicit Return Examples:
===========================

// Simple expression
const square = n => n * n;

// String concatenation
const greet = name => "Hello, " + name;

// Object literal → wrap in parentheses!
const getUser = id => ({ id, role: "admin" });
//                      ↑ () needed so {} isn't mistaken for function body

// Conditional (ternary)
const isAdult = age => age >= 18 ? "adult" : "minor";

// Chained array methods (super clean!)
const evenSquares = nums => nums
    .filter(n => n % 2 === 0)
    .map(n => n * n);

console.log(evenSquares([1, 2, 3, 4, 5]));  // [4, 16]
Where Implicit Return Shines:
================================

// 1. Array methods
const numbers = [1, 2, 3, 4, 5];

const doubled    = numbers.map(n => n * 2);        // [2,4,6,8,10]
const evens      = numbers.filter(n => n % 2 === 0); // [2,4]
const total      = numbers.reduce((acc, n) => acc + n, 0); // 15

// 2. Promise chains
fetch("/api/users")
    .then(response => response.json())
    .then(data => data.users)
    .then(users => users.filter(u => u.active));

// 3. React JSX
const UserCard = ({ name, age }) => (
    <div>
        <h2>{name}</h2>
        <p>{age}</p>
    </div>
);

Hoisting Behavior

// Function DECLARATION — fully hoisted!

sayHi();  // Works! Called before definition!

function sayHi() {
    console.log("Hi!");
}

OUTPUT:
Hi!

// Function declarations are HOISTED — moved to top of scope!
// You can call them BEFORE they are defined in code!
// Function EXPRESSION — NOT hoisted!

sayHi();  // ❌ Error!

const sayHi = function() {
    console.log("Hi!");
};

OUTPUT:
ReferenceError: Cannot access 'sayHi' before initialization

// Arrow functions are stored in variables (const/let/var)
// Variables with const/let are NOT hoisted (in temporal dead zone)
Hoisting Summary:
==================

function declaration → HOISTED ✅ (can call before definition)
function expression  → NOT hoisted ❌
arrow function       → NOT hoisted ❌ (stored in variable)

// Why does this matter?
// Use function declarations for main functions you want accessible anywhere.
// Use arrow functions / expressions for callbacks and helper functions.

When to Use Arrow Function vs Normal Function

USE NORMAL FUNCTION when:
==========================

1. Object Methods — needs own 'this':

const user = {
    name: "Shreyesh",
    greet: function() {       // ✅ Normal function
        console.log(this.name);
    }
};

2. Constructor Functions — needs new keyword:

function Car(model) {         // ✅ Normal function
    this.model = model;
}
const myCar = new Car("Tesla");

3. Generator Functions — must be normal:

function* generateIds() {     // ✅ Only normal function
    yield 1;
    yield 2;
}

4. When you need 'arguments' object:

function logAll() {           // ✅ Normal function
    console.log(arguments);
}

5. Named functions for recursion / debugging:

function factorial(n) {       // ✅ Name shows in stack traces!
    return n <= 1 ? 1 : n * factorial(n - 1);
}
USE ARROW FUNCTION when:
==========================

1. Callbacks — especially inside object methods:

const user = {
    name: "Shreyesh",
    hobbies: ["coding", "reading"],
    showHobbies: function() {
        this.hobbies.forEach(hobby => {  // ✅ Arrow function
            console.log(this.name + ": " + hobby);
        });
    }
};

2. Array methods (map, filter, reduce):

const evens = [1,2,3,4].filter(n => n % 2 === 0);  // ✅ Arrow
const doubled = [1,2,3].map(n => n * 2);             // ✅ Arrow

3. Promise chains:

fetch("/api/data")
    .then(res => res.json())   // ✅ Arrow functions
    .then(data => data.items)
    .catch(err => console.error(err));

4. Short utility functions:

const add = (a, b) => a + b;   // ✅ Clean and concise
const greet = name => `Hi, ${name}!`;

5. React functional components and hooks:

const Button = ({ label, onClick }) => (  // ✅ Arrow function
    <button onClick={onClick}>{label}</button>
);

const [count, setCount] = useState(0);
const increment = () => setCount(prev => prev + 1);  // ✅ Arrow

Famous Interview Questions

Q1: What is the main difference between arrow and normal functions?

"The biggest difference is how 'this' works. Normal functions have their own 'this' that is determined by how the function is called. Arrow functions do not have their own 'this' — they inherit it from their lexical (surrounding) scope where they were defined. Other differences include: arrow functions have no 'arguments' object, cannot be used as constructors, have no prototype property, and support implicit return."

Q2: What is lexical 'this' in arrow functions?

"Lexical 'this' means the arrow function does not bind its own 'this'. Instead, 'this' is taken from the enclosing scope at the time the arrow function is defined — not when it is called. It's similar to how closures remember their outer variables. This makes arrow functions ideal for callbacks inside object methods, where you want 'this' to remain the parent object."

Q3: Why can't arrow functions be used as constructors?

"When you use 'new' with a function, JavaScript creates a new empty object and sets 'this' to it inside the constructor. Arrow functions don't have their own 'this', so JavaScript has no object to bind to. Arrow functions also lack the 'prototype' property that 'new' uses to set up the prototype chain. That is why calling 'new' with an arrow function throws a TypeError."

Q4: What is the difference between 'arguments' and rest parameters?

"'arguments' is an array-like object automatically available in normal functions that holds all passed arguments. It is NOT a real array, so methods like .map or .filter don't work on it. Arrow functions don't have 'arguments' at all. Rest parameters (...args) are the modern replacement — they give you a real Array of the remaining arguments, work in both normal and arrow functions, and support all array methods."

Q5: When should you NOT use an arrow function?

"You should not use arrow functions as object methods, because 'this' inside the arrow function will not refer to the object — it will refer to the outer (usually global) scope. You should also avoid arrow functions as constructors (they throw a TypeError with 'new'), as generator functions (only normal functions support function*), or when you need dynamic 'this' binding via .call(), .apply(), or .bind() — these methods have no effect on arrow functions' 'this'."

Q6: Can you change 'this' in an arrow function using .call(), .apply(), or .bind()?

"No! Arrow functions completely ignore .call(), .apply(), and .bind() when it comes to 'this'. You can still pass arguments using .call() and .apply(), but the 'this' inside the arrow function will always be the lexical 'this' — it cannot be overridden. This is fundamentally different from normal functions where .call(), .apply(), and .bind() explicitly set 'this'."

Q7: What is implicit return in arrow functions?

"When an arrow function body has no curly braces, it implicitly returns the result of the expression. For example, 'const add = (a, b) => a + b' returns a + b automatically without writing 'return'. If you want to implicitly return an object literal, you must wrap it in parentheses — 'const getUser = () => ({ name: 'Shreyesh' })' — otherwise the curly braces are treated as the function body."

Q8: Are arrow functions hoisted?

"Arrow functions are not hoisted because they are assigned to variables (const or let). Variables declared with const and let are in a temporal dead zone from the start of the block until the declaration is reached. Function declarations, on the other hand, are fully hoisted — you can call them before they appear in the code. Function expressions assigned to var are also not fully hoisted — the variable is hoisted but the value is undefined until the assignment line is reached."


Quick Recap

Feature Normal Function Arrow Function
Syntax function name() {} const name = () => {}
'this' binding Own 'this' — depends on call site No own 'this' — inherits from lexical scope
'arguments' object Has its own 'arguments' No 'arguments' — use rest params
Constructor (new) Can be used as constructor ✅ Cannot be used as constructor ❌
prototype Has prototype property ✅ No prototype property ❌
Implicit return Always needs 'return' Implicit return without {}
Hoisting Declarations are fully hoisted Not hoisted (stored in variable)
.call/.apply/.bind Changes 'this' ✅ Cannot change 'this' ❌
Generator Supports function* ✅ Cannot be a generator ❌
Best use case Methods, constructors, generators Callbacks, array methods, React components

Key Points to Remember

  • Arrow functions have no own 'this' — they inherit it lexically
  • Normal function 'this' depends on how it is called
  • Arrow function 'this' depends on where it is defined
  • Never use arrow functions as object methods — 'this' won't be the object
  • Arrow functions are perfect as callbacks inside object methods
  • Arrow functions have no 'arguments' object — use rest parameters
  • Arrow functions cannot be constructors — no 'new' keyword
  • Arrow functions have no 'prototype' property
  • .call(), .apply(), .bind() cannot change 'this' in arrow functions
  • Arrow functions support implicit return when curly braces are omitted
  • To implicitly return an object literal, wrap in parentheses: () => ({})
  • Function declarations are hoisted; arrow functions are NOT
  • Arrow functions make React components and hooks much cleaner
  • Use arrow functions for map, filter, reduce, forEach, then — they shine there!

Keep coding, keep learning! See you in the next one!