What are arrow functions and what are their differences?

👨‍💻 Frontend Developer 🟡 Often Asked 🎚️ Medium
#JavaScript #Functions #JS Basics

Brief Answer

Arrow functions are a concise syntax for declaring functions introduced in ES6. They have lexical this context and more compact notation.

Key differences from regular functions:

  • Lexical this — inherit context from parent scope
  • No hoisting — available only after declaration
  • Cannot be used as constructor — no new
  • No own arguments
  • Concise syntax — less code

What are Arrow Functions

Arrow functions are an alternative way to write functions that appeared in ES6 (2015). They use the => (“arrow”) symbol to define a function.

Basic Syntax

// Regular function
function add(a, b) {
  return a + b;
}
 
// Arrow function
const add = (a, b) => {
  return a + b;
};
 
// Short form (implicit return)
const add = (a, b) => a + b;

Syntax Variations

1. Full Form

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

2. Short Form (Implicit Return)

// Single expression — automatic return
const double = x => x * 2;
const sum = (a, b) => a + b;
const getMessage = () => "Hello, world!";

3. No Parameters

const sayHello = () => "Hello!";
const getRandomNumber = () => Math.random();

4. Single Parameter (Parentheses Optional)

// With parentheses
const square = (x) => x * x;
 
// Without parentheses
const square = x => x * x;

5. Returning Objects

// ❌ Wrong — interpreted as code block
const createUser = name => { name: name, age: 25 };
 
// ✅ Correct — wrap in parentheses
const createUser = name => ({ name: name, age: 25 });
const createUser = name => ({ name, age: 25 }); // ES6 shorthand

Main Differences from Regular Functions

1. this Context

The most important difference — arrow functions don’t have their own this.

const person = {
  name: "Alexander",
  age: 30,
  
  // Regular function — own this
  regularMethod: function() {
    console.log(this.name); // "Alexander"
    
    setTimeout(function() {
      console.log(this.name); // undefined (this = window/global)
    }, 1000);
  },
  
  // Arrow function — lexical this
  arrowMethod: function() {
    console.log(this.name); // "Alexander"
    
    setTimeout(() => {
      console.log(this.name); // "Alexander" (inherits this)
    }, 1000);
  }
};

2. Hoisting

// ✅ Works — function declaration is hoisted
console.log(regularFunc()); // "Works!"
 
function regularFunc() {
  return "Works!";
}
 
// ❌ Error — arrow function is not hoisted
console.log(arrowFunc()); // ReferenceError
 
const arrowFunc = () => "Works!";

3. Using as Constructor

// ✅ Regular function — can be used with new
function Person(name) {
  this.name = name;
}
 
const person1 = new Person("Alexander"); // Works
 
// ❌ Arrow function — cannot be used with new
const PersonArrow = (name) => {
  this.name = name;
};
 
const person2 = new PersonArrow("Maria"); // TypeError

4. arguments Object

// ✅ Regular function — has arguments
function regularFunc() {
  console.log(arguments); // [1, 2, 3]
}
 
regularFunc(1, 2, 3);
 
// ❌ Arrow function — no arguments
const arrowFunc = () => {
  console.log(arguments); // ReferenceError
};
 
// ✅ Use rest parameters
const arrowFuncWithRest = (...args) => {
  console.log(args); // [1, 2, 3]
};
 
arrowFuncWithRest(1, 2, 3);

Comparison Table

FeatureRegular FunctionArrow Function
Syntaxfunction() {}() => {}
Hoisting✅ Yes❌ No
thisDynamicLexical
new✅ Can❌ Cannot
arguments✅ Has❌ No
super✅ Has❌ No
Prototype✅ Has❌ No
Generators✅ Supports❌ Doesn’t support

Practical Examples

1. Array Processing

const numbers = [1, 2, 3, 4, 5];
 
// Old way
const doubled = numbers.map(function(n) {
  return n * 2;
});
 
// Arrow functions
const doubled = numbers.map(n => n * 2);
const filtered = numbers.filter(n => n > 3);
const sum = numbers.reduce((acc, n) => acc + n, 0);
 
console.log(doubled);  // [2, 4, 6, 8, 10]
console.log(filtered); // [4, 5]
console.log(sum);      // 15

2. Event Listeners

class Button {
  constructor(element) {
    this.element = element;
    this.clickCount = 0;
    
    // ❌ Problem with this in regular function
    this.element.addEventListener('click', function() {
      this.clickCount++; // this = element, not Button
      console.log(this.clickCount); // NaN
    });
    
    // ✅ Arrow function preserves this
    this.element.addEventListener('click', () => {
      this.clickCount++; // this = Button instance
      console.log(this.clickCount); // 1, 2, 3...
    });
  }
}

3. Promises and Async/Await

// Promise chain with arrow functions
fetch('/api/users')
  .then(response => response.json())
  .then(users => users.filter(user => user.active))
  .then(activeUsers => console.log(activeUsers))
  .catch(error => console.error(error));
 
// Async/await with arrow functions
const fetchUsers = async () => {
  try {
    const response = await fetch('/api/users');
    const users = await response.json();
    return users.filter(user => user.active);
  } catch (error) {
    console.error(error);
  }
};

4. Object Methods

const calculator = {
  value: 0,
  
  // ✅ Regular function for methods
  add(num) {
    this.value += num;
    return this;
  },
  
  // ❌ Arrow function — loses this
  multiply: (num) => {
    this.value *= num; // this doesn't point to calculator
    return this;
  },
  
  // ✅ Arrow function inside method
  processArray(numbers) {
    return numbers.map(n => n + this.value); // this = calculator
  }
};

Practice Tasks

Task 1

const obj = {
  name: "Alexander",
  
  getName: () => {
    return this.name;
  }
};
 
console.log(obj.getName());
Answer undefined — arrow function doesn't have its own this and inherits it from global context where this.name is not defined.

Task 2

function Timer() {
  this.seconds = 0;
  
  setInterval(() => {
    this.seconds++;
    console.log(this.seconds);
  }, 1000);
}
 
const timer = new Timer();
Answer Will output 1, 2, 3... every second. Arrow function inherits this from Timer function, so this.seconds correctly refers to instance property.

Task 3

const numbers = [1, 2, 3, 4, 5];
 
const result = numbers
  .filter(n => n > 2)
  .map(n => n * 2)
  .reduce((sum, n) => sum + n);
 
console.log(result);
Answer 24 — filter numbers greater than 2 [3, 4, 5], multiply by 2 [6, 8, 10], sum 6 + 8 + 10 = 24.

Task 4

const user = {
  name: "Alexander",
  
  greet() {
    console.log(`Hello, ${this.name}!`);
    
    const inner = () => {
      console.log(`Inside: ${this.name}`);
    };
    
    inner();
  }
};
 
user.greet();
const greetFunc = user.greet;
greetFunc();
Answer First call: "Hello, Alexander!" and "Inside: Alexander". Second call: "Hello, undefined!" and "Inside: undefined" — when extracting method, this context is lost.

Task 5

const createMultiplier = (factor) => {
  return (number) => number * factor;
};
 
const double = createMultiplier(2);
const triple = createMultiplier(3);
 
console.log(double(5));
console.log(triple(4));
Answer 10 and 12 — arrow functions work great with closures, preserving access to factor variable from outer scope.

Task 6

const Person = (name) => {
  this.name = name;
};
 
const person = new Person("Alexander");
console.log(person.name);
Answer TypeError: Person is not a constructor — arrow functions cannot be used as constructors with new operator.

When to Use Arrow Functions

✅ Good Cases

// 1. Array callbacks
const users = ['Alexander', 'Maria', 'Ivan'];
const greetings = users.map(name => `Hello, ${name}!`);
 
// 2. Short functions
const isEven = n => n % 2 === 0;
const getFullName = (first, last) => `${first} ${last}`;
 
// 3. Preserving this context
class Component {
  constructor() {
    this.state = { count: 0 };
  }
  
  handleClick() {
    // Arrow function preserves this
    setTimeout(() => {
      this.state.count++;
    }, 1000);
  }
}
 
// 4. Higher-order functions
const createValidator = (rule) => (value) => rule.test(value);
 
// 5. Promises and async/await
const fetchData = async () => {
  const response = await fetch('/api/data');
  return response.json();
};

❌ Bad Cases

// 1. Object methods
const obj = {
  name: "Alexander",
  
  // ❌ Bad — loses this
  greet: () => {
    console.log(`Hello, ${this.name}!`); // undefined
  },
  
  // ✅ Good
  greet() {
    console.log(`Hello, ${this.name}!`);
  }
};
 
// 2. Constructors
// ❌ Bad — doesn't work
const Person = (name) => {
  this.name = name;
};
 
// ✅ Good
class Person {
  constructor(name) {
    this.name = name;
  }
}
 
// 3. Prototype methods
// ❌ Bad
Person.prototype.greet = () => {
  console.log(this.name); // undefined
};
 
// ✅ Good
Person.prototype.greet = function() {
  console.log(this.name);
};
 
// 4. Event handlers with needed this
// ❌ Bad — if element this is needed
button.addEventListener('click', () => {
  this.style.color = 'red'; // this is not button
});
 
// ✅ Good
button.addEventListener('click', function() {
  this.style.color = 'red'; // this = button
});

Best Practices

1. Choosing Between Arrow and Regular Functions

// ✅ Use arrow for short functions
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);
 
// ✅ Use regular for object methods
const user = {
  name: "Alexander",
  
  greet() {
    return `Hello, ${this.name}!`;
  }
};
 
// ✅ Use arrow for preserving context
class Timer {
  constructor() {
    this.time = 0;
    
    setInterval(() => {
      this.time++; // this = Timer instance
    }, 1000);
  }
}

2. Code Readability

Arrow functions are better for simple operations, while regular functions are better for complex business logic with multiple conditions and calculations.

3. Parameter Destructuring

// ✅ Arrow functions work great with destructuring
const users = [
  { name: "Alexander", age: 30 },
  { name: "Maria", age: 25 }
];
 
const names = users.map(({ name }) => name);
const adults = users.filter(({ age }) => age >= 18);
 
// Creating objects
const createUser = ({ name, age = 18 }) => ({ 
  name, 
  age, 
  id: Math.random() 
});

Modern Features

1. Async Arrow Functions

// Simple async arrow functions
const fetchUser = async (id) => {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
};
 
// In arrays
const userIds = [1, 2, 3];
const users = await Promise.all(
  userIds.map(async (id) => {
    const user = await fetchUser(id);
    return user;
  })
);
 
// Error handling
const safeAsyncOperation = async () => {
  try {
    const result = await riskyOperation();
    return result;
  } catch (error) {
    console.error('Error:', error);
    return null;
  }
};

2. Arrow Functions in React

// Components
const UserCard = ({ name, age }) => (
  <div>
    <h3>{name}</h3>
    <p>Age: {age}</p>
  </div>
);
 
// Event handlers
const Button = () => {
  const [count, setCount] = useState(0);
  
  // Arrow function preserves context
  const handleClick = () => {
    setCount(prev => prev + 1);
  };
  
  return <button onClick={handleClick}>{count}</button>;
};
 
// useEffect
useEffect(() => {
  const timer = setInterval(() => {
    console.log('Timer fired');
  }, 1000);
  
  return () => clearInterval(timer);
}, []);

Common Mistakes

1. Losing this Context

// ❌ Error
const obj = {
  name: "Alexander",
  greet: () => console.log(this.name) // undefined
};
 
// ✅ Fix
const obj = {
  name: "Alexander",
  greet() {
    console.log(this.name); // "Alexander"
  }
};

2. Wrong Object Return

// ❌ Error — interpreted as&nbsp;code block
const createUser = name => { name: name, age: 25 };
 
// ✅ Fix — wrap in&nbsp;parentheses
const createUser = name => ({ name: name, age: 25 });

3. Using as Constructor

// ❌ Error
const Person = (name) => {
  this.name = name;
};
const person = new Person("Alexander"); // TypeError
 
// ✅ Fix
class Person {
  constructor(name) {
    this.name = name;
  }
}

Summary

Arrow functions are a powerful ES6 tool that:

Advantages:

  • Concise syntax — less code
  • Lexical this — predictable behavior
  • Great for callbacks and functional programming
  • Readability — especially for simple operations

Limitations:

  • No hoisting — available only after declaration
  • Cannot be used as constructor
  • No own this — not suitable for object methods
  • No arguments — use rest parameters

When to use:

  • ✅ Array callbacks (map, filter, reduce)
  • ✅ Short functions
  • ✅ Preserving this context
  • ✅ Promises and async/await
  • ❌ Object methods
  • ❌ Constructors
  • ❌ When dynamic this is needed

Understanding arrow functions is the key to modern JavaScript and functional programming!


Want more interview preparation articles? Subscribe to EasyAdvice, bookmark the site and improve yourself every day 💪