Arrow Functions
vs. Regular Functions

Syntax

Regular functions use the function keyword. Arrow functions use the => fat-arrow syntax and have several shorthand forms.

Regular
// Declaration
function add(a, b) {
  return a + b;
}

// Expression
const add = function(a, b) {
  return a + b;
};
Arrow
// Block body
const add = (a, b) => {
  return a + b;
};

// Concise body (implicit return)
const add = (a, b) => a + b;

// Single param — parens optional
const double = n => n * 2;
Implicit return caveat Returning an object literal in concise form requires wrapping in parentheses: const f = () => ({ key: val }); — without parens the {} is parsed as a block, not an object.

this Binding

This is the most consequential difference. Regular functions define their own this at call time. Arrow functions have no this of their own — they close over the this of the surrounding lexical scope at definition time.

Regular function — dynamic this

const obj = {
  name: 'obj',
  greet: function() {
    console.log(this.name); // 'obj' — this = obj
  }
};
obj.greet(); // → 'obj'

// Detach and call: this changes
const fn = obj.greet;
fn(); // → undefined (strict) or window.name (sloppy)

Arrow function — lexical this

function Timer() {
  this.count = 0;

  setInterval(() => {
    this.count++; // this = Timer instance, always
    console.log(this.count);
  }, 1000);
}

new Timer(); // → 1, 2, 3 …

// With a regular function callback this would be
// undefined (strict) or window (sloppy) inside setInterval.

.call(), .apply(), .bind()

These methods can rebind this on regular functions. On arrow functions they are syntactically valid but have no effect on this.

const arrow = () => this;
const regular = function() { return this; };

arrow.call({ x: 1 });   // → outer this (unchanged)
regular.call({ x: 1 }); // → { x: 1 }

arguments Object

Regular functions receive an arguments array-like object automatically. Arrow functions do not — they inherit arguments from the enclosing scope, or it's undefined at the top level in strict mode.

function regular() {
  console.log(arguments); // Arguments [1, 2, 3]
}
regular(1, 2, 3);

const arrow = () => {
  console.log(arguments); // ReferenceError in strict mode
};
arrow(1, 2, 3);

// Rest parameters work in both — prefer them over arguments
const sum = (...args) => args.reduce((a, b) => a + b, 0);

Constructor Usage

Regular functions can be invoked with new. Arrow functions cannot — they throw a TypeError if you try.

function Person(name) {
  this.name = name;
}
const p = new Person('Alice'); // ✓ works

const Animal = (name) => {
  this.name = name;
};
const a = new Animal('Cat'); // ✗ TypeError: Animal is not a constructor

prototype Property

Regular functions automatically have a prototype property (an object with a constructor back-reference). Arrow functions have no prototype property at all.

function Foo() {}
console.log(Foo.prototype); // → { constructor: ƒ }

const Bar = () => {};
console.log(Bar.prototype); // → undefined

Hoisting

Function declarations are fully hoisted — they can be called before the line they appear on. Function expressions (including arrow functions assigned to variables) follow the hoisting rules of their binding keyword (var, let, const).

sayHi(); // → 'hi'  ✓ declaration is hoisted

function sayHi() {
  console.log('hi');
}

greet(); // ✗ ReferenceError: Cannot access 'greet' before initialization

const greet = () => console.log('hello');

Object Methods & Classes

Because arrow functions capture this lexically, they are unsuitable as object methods when dynamic dispatch via this is needed. Use regular functions (or shorthand method syntax) for object methods and class methods.

// ✗ Arrow as method — this is the outer scope, not obj
const obj = {
  val: 42,
  get: () => this.val // undefined
};

// ✓ Regular function / method shorthand
const obj = {
  val: 42,
  get() { return this.val; } // 42
};

// ✓ Arrow as class field — useful for event handler binding
class Button {
  handleClick = () => {
    console.log(this); // always the instance
  };
}
Class fields note Arrow functions as class fields create a new function per instance (no shared prototype method). Regular methods are defined once on the prototype and shared across all instances. For hot paths or large object counts, prefer regular methods.

Generators & async

Arrow functions cannot be generator functions (function* syntax is unavailable). Both regular and arrow functions can be async.

// Generator — only regular functions
function* seq() { yield 1; yield 2; }

// async — both work
async function fetchData() { return await getData(); }
const fetchData = async () => await getData();

Summary

Feature Regular Function Arrow Function
this binding Dynamic (call-site) Lexical (definition-site)
arguments object ✓ present ✗ absent
new / constructor ✓ allowed ✗ TypeError
prototype property ✓ present ✗ absent
Hoisted (declaration form) ✓ yes ✗ no (expression only)
Generator (function*) ✓ yes ✗ no
async ✓ yes ✓ yes
Implicit return ✗ no ✓ concise body only
.call / .apply / .bind (this) Effective No effect on this
Suitable as object method ✓ yes ✗ generally no

When to Use Which

Use arrow functions for:

Callbacks and higher-order functions (.map, .filter, .reduce, Promise.then, setTimeout); any place where you want the surrounding this preserved without manual binding; short, single-expression functions where implicit return aids readability.

Use regular functions for:

Object methods and class prototype methods where dynamic this dispatch is required; constructor functions (when not using ES6 classes); generators; top-level named utility functions where hoisting or a self-documenting stack trace name matters.

More JS articles

Goldmine - idle game (~200 lines)

Tunnel run game (~170 lines)

Tower game (84 lines)

Fireworks (60 lines)

Physics engine for beginners