Types of Functions in JavaScript — Differences Explained
JavaScript has several ways to define functions. They look similar, but they differ in hoisting, this, arguments, and whether they can be used with new.
If you’ve ever wondered why fn() works before its line for one function but throws for another — or why an arrow “method” prints undefined — this guide is for you.
- Function Declaration
- Function Expression
- Arrow Function
- Object Method
- Constructor Function & Class
- Generator Function
- Async Function
- IIFE (Immediately Invoked Function Expression)
- Quick Comparison Table
- Key Differences
- When to Use What
- Interview Gotchas
1. Function Declaration
function greet(name) {
return `Hello, ${name}`;
}
greet("Amit"); // "Hello, Amit"
Enter fullscreen mode Exit fullscreen mode
Key traits
- Fully hoisted — you can call it before its definition
- Has its own
this(depends on how it’s called) - Has
arguments -
Can be used with
new, but preferclass/ dedicated constructors — don’t treat that as the normal pattern - Best for named, reusable top-level functions
sayHi(); // works — hoisted
function sayHi() {
console.log("Hi");
}
Enter fullscreen mode Exit fullscreen mode
2. Function Expression
const greet = function (name) {
return `Hello, ${name}`;
};
Enter fullscreen mode Exit fullscreen mode
Named function expression (useful for stack traces / recursion):
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1);
};
Enter fullscreen mode Exit fullscreen mode
Key traits
- The variable may be hoisted (
var), but the function value is not available until the assignment runs - With
const/let, accessing before the line is a TDZReferenceError - With
var, calling before assignment is usually aTypeError(undefinedis not a function) - Same
this/argumentsbehavior as a declaration - Can be anonymous or named
- Common when assigning functions to variables or passing as callbacks
// var → TypeError
sayHiVar(); // TypeError: sayHiVar is not a function
var sayHiVar = function () {
console.log("Hi");
};
// const / let → ReferenceError (TDZ)
sayHiConst(); // ReferenceError: Cannot access 'sayHiConst' before initialization
const sayHiConst = function () {
console.log("Hi");
};
Enter fullscreen mode Exit fullscreen mode
3. Arrow Function
const greet = (name) => `Hello, ${name}`;
const add = (a, b) => {
return a + b;
};
Enter fullscreen mode Exit fullscreen mode
Key traits
- Not hoisted
-
No own
this— uses lexicalthisfrom the surrounding scope - No
argumentsobject (use rest params:(...args) => {}) - Cannot be used as a constructor (
newthrows) - No
prototype - Perfect for callbacks,
map/filter/reduce, and preserving outerthis
Lexical this example
const user = {
name: "Amit",
regular() {
setTimeout(function () {
console.log(this.name); // undefined (or global) — lost this
}, 0);
},
arrow() {
setTimeout(() => {
console.log(this.name); // "Amit" — lexical this
}, 0);
},
};
user.regular();
user.arrow();
Enter fullscreen mode Exit fullscreen mode
Rest instead of arguments
const sum = (...nums) => nums.reduce((a, b) => a + b, 0);
sum(1, 2, 3); // 6
Enter fullscreen mode Exit fullscreen mode
Guess the output?const user = {
name: "Amit",
greet: () => this.name,
};
console.log(user.greet());
Enter fullscreen mode Exit fullscreen mode
Answer: undefined in a typical module / strict runtime — the arrow does not take this from user.
4. Object Method
const user = {
name: "Amit",
greet() {
return `Hello, ${this.name}`;
},
};
user.greet(); // "Hello, Amit"
Enter fullscreen mode Exit fullscreen mode
Key traits
-
thisis usually the object that owns the method (when called asobj.method()) - Shorthand method syntax is preferred over
greet: function () {} - Avoid arrow functions as object methods if you need
thisto be the object
const user = {
name: "Amit",
greet: () => `Hello, ${this.name}`, // wrong — lexical this
};
user.greet(); // "Hello, undefined" (in modules / strict mode)
Enter fullscreen mode Exit fullscreen mode
5. Constructor Function & Class
Constructor function
function Person(name) {
this.name = name;
}
Person.prototype.greet = function () {
return `Hello, ${this.name}`;
};
const p = new Person("Amit");
p.greet(); // "Hello, Amit"
Enter fullscreen mode Exit fullscreen mode
Class (modern syntax)
class Person {
constructor(name) {
this.name = name;
}
greet() {
return `Hello, ${this.name}`;
}
}
const p = new Person("Amit");
Enter fullscreen mode Exit fullscreen mode
Key traits
- Meant to be called with
new - Creates instance objects
- Methods usually live on the prototype (shared across instances)
- Prefer
classin modern code; behavior is still prototype-based under the hood -
Classes are hoisted into the TDZ — you cannot use them before the
classline (ReferenceError)
const user = new User(); // ReferenceError
class User {}
Enter fullscreen mode Exit fullscreen mode
React / class-fields note
class Button {
label = "Save";
// Per-instance arrow — lexical this (handy for callbacks)
onPress = () => this.label;
// Prototype method — this depends on call site
handle() {
return this.label;
}
}
Enter fullscreen mode Exit fullscreen mode
6. Generator Function
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}
const gen = idGenerator();
gen.next().value; // 1
gen.next().value; // 2
gen.next().value; // 3
Enter fullscreen mode Exit fullscreen mode
Key traits
- Defined with
function* - Can pause and resume with
yield - Returns an iterator
- Useful for lazy sequences, custom iteration, infinite lists
Also works as a generator method:
const obj = {
*range(n) {
for (let i = 1; i <= n; i++) yield i;
},
};
[...obj.range(3)]; // [1, 2, 3]
Enter fullscreen mode Exit fullscreen mode
There are also async generators (
async function*) used withfor await...of— same pause/resume idea for async streams.
7. Async Function
async function loadUser() {
const res = await fetch("/api/user");
return res.json();
}
// async arrow
const loadUserArrow = async () => {
const res = await fetch("/api/user");
return res.json();
};
Enter fullscreen mode Exit fullscreen mode
Key traits
- Always returns a Promise
- Lets you write async code with
awaitin a sync-looking style - Thrown errors become rejected promises (use
try/catchor.catch()) -
awaiton a non-Promise still works — the value is wrapped
async function example() {
return 42;
}
example(); // Promise that resolves to 42
Enter fullscreen mode Exit fullscreen mode
8. IIFE (Immediately Invoked Function Expression)
Also called a self-calling or self-invoking function.
An IIFE is defined and executed in the same step — you don’t call it later by name.
(function () {
console.log("I run immediately");
})();
// Arrow version
(() => {
console.log("I also run immediately");
})();
Enter fullscreen mode Exit fullscreen mode
Why wrap in parentheses?
Without wrapping, JS treats function as a declaration, and declarations can’t be invoked that way as a statement.
// Valid — force an expression, then invoke
(function () {
console.log("works");
})();
Enter fullscreen mode Exit fullscreen mode
Other (less common) IIFE forms(function () {
console.log("form 1");
})();
(function () {
console.log("form 2");
}());
!function () {
console.log("form 3");
}();
Enter fullscreen mode Exit fullscreen mode
Prefer the (function () { ... })() or (() => { ... })() style in real code.
Pass arguments
(function (name) {
console.log(`Hello, ${name}`);
})("Amit");
Enter fullscreen mode Exit fullscreen mode
Create private scope
(function () {
const secret = "hidden";
// secret is not accessible outside
})();
// console.log(secret); // ReferenceError
Enter fullscreen mode Exit fullscreen mode
Return a value / module-like API
const counter = (function () {
let count = 0;
return {
increment() {
count++;
return count;
},
getCount() {
return count;
},
};
})();
counter.increment(); // 1
counter.getCount(); // 1
// count itself is private
Enter fullscreen mode Exit fullscreen mode
Async IIFE
Useful for top-level async setup when you can’t use top-level await:
(async function () {
const res = await fetch("/api/user");
console.log(await res.json());
})();
Enter fullscreen mode Exit fullscreen mode
Key traits
- Defined and executed immediately
- Creates a private scope
- Can take parameters and return values
- Common before ES modules; still used in bundles, polyfills, one-off setup, and interviews
const result = (function () {
var x = 10;
return x;
})();
console.log(result);
// console.log(x);
Enter fullscreen mode Exit fullscreen mode
Answer: result is 10. Logging x throws ReferenceError — x stayed private inside the IIFE.
9. Quick Comparison Table
| Feature | Declaration | Expression | Arrow | Method | Constructor / Class | Generator | Async |
|---|---|---|---|---|---|---|---|
| Hoisting | Yes (full) | No (assignment); var/const differ |
No | No | Function ctor: yes · Class: TDZ | Like its form | Like its form |
Own this
|
Yes | Yes | No (lexical) | Yes (usually) | Yes (instance) | Yes | Yes (unless arrow) |
arguments |
Yes | Yes | No | Yes | Yes | Yes | Yes (unless arrow) |
Can use new
|
Possible* | Possible* | No | Not intended | Yes | Avoid | No |
| Returns | Any | Any | Any | Any | Instance | Iterator | Promise |
| Best for | Named utils | Assigned fns | Callbacks | Object behavior | OOP / instances | Lazy iteration | Async / await |
* Technically allowed for classic functions, but prefer class for constructors.
10. Key Differences
Declaration vs Expression
| Point | Function Declaration | Function Expression |
|---|---|---|
| Syntax | function fn() {} |
const fn = function () {} |
| Before definition | Callable |
var → TypeError · const/let → ReferenceError
|
| Name required | Yes | Optional |
| Use case | Top-level helpers | Assign to vars, pass as values |
hello(); // works
function hello() {
console.log("declaration");
}
bye(); // ReferenceError with const
const bye = function () {
console.log("expression");
};
Enter fullscreen mode Exit fullscreen mode
Arrow Function vs Normal Function
“Normal function” = declaration or classic expression.
| Feature | Normal Function | Arrow Function |
|---|---|---|
Own this
|
Yes — depends on how it’s called | No — lexical |
arguments |
Yes | No — use ...args
|
Can use new
|
Yes | No (TypeError) |
prototype |
Yes | No |
| Implicit return | No | Yes — () => value
|
| Best for | Methods, constructors, own this
|
Callbacks, keep outer this
|
this binding
const user = {
name: "Amit",
normal: function () {
console.log(this.name); // "Amit"
},
arrow: () => {
console.log(this.name); // undefined (not the object)
},
};
user.normal();
user.arrow();
Enter fullscreen mode Exit fullscreen mode
Constructor usage
function Person(name) {
this.name = name;
}
new Person("Amit"); // OK
const PersonArrow = (name) => {
this.name = name;
};
new PersonArrow("Amit"); // TypeError
Enter fullscreen mode Exit fullscreen mode
When to use which
-
Normal function → object methods, constructors, handlers where you need call-site
this -
Arrow function → callbacks (
map,filter,setTimeout) where you want the parent’sthis
Method vs Arrow as Method
| Point | Method greet() {}
|
Arrow property greet: () => {}
|
|---|---|---|
this |
Owning object (usual call) | Outer / lexical this
|
| Recommended for objects? | Yes | Usually no |
const user = {
name: "Amit",
method() {
return this.name; // "Amit"
},
arrow: () => this.name, // wrong for object method
};
Enter fullscreen mode Exit fullscreen mode
Constructor Function vs Class
| Point | Constructor Function | Class |
|---|---|---|
| Syntax | function Person() {} |
class Person {} |
| Style | Older / prototype style | Modern syntactic sugar |
| Before definition | Function is hoisted |
TDZ — ReferenceError
|
| Internals | Prototype model | Same prototype model |
function PersonFn(name) {
this.name = name;
}
class PersonClass {
constructor(name) {
this.name = name;
}
}
Enter fullscreen mode Exit fullscreen mode
Function Expression vs IIFE
| Point | Function Expression | IIFE |
|---|---|---|
| When it runs | When you call it later | Immediately |
| Stored in variable? | Usually yes | Often not needed |
| Purpose | Reuse later | One-time run + private scope |
const greet = function (name) {
console.log(name);
};
greet("Amit");
(function (name) {
console.log(name);
})("Amit");
Enter fullscreen mode Exit fullscreen mode
An IIFE can be an arrow:
(() => {
console.log("arrow IIFE");
})();
Enter fullscreen mode Exit fullscreen mode
Normal vs Async vs Generator (quick)
| Point | Normal | Async | Generator |
|---|---|---|---|
| Keyword | function |
async function |
function* |
| Returns | Any value | Always a Promise | An iterator |
| Special | — | await |
yield / .next()
|
function syncFn() {
return 10; // number
}
async function asyncFn() {
return 10; // Promise → 10
}
function* gen() {
yield 1;
yield 2;
}
Enter fullscreen mode Exit fullscreen mode
11. When to Use What
| Need | Prefer |
|---|---|
| Named reusable helper | Function declaration |
| Pass / assign a function | Function expression or arrow |
Callback / array method / keep outer this
|
Arrow function |
| Behavior on an object | Object method / class method |
| Create many similar objects | class |
| Pause / resume / lazy values | Generator |
| Fetch / timers / promises | Async function |
| Run once + private variables | IIFE (or an ES module) |
12. Interview Gotchas
1. Hoisting: declaration vs expression
fnDecl(); // works
function fnDecl() {}
fnVar(); // TypeError
var fnVar = function () {};
fnConst(); // ReferenceError (TDZ)
const fnConst = function () {};
Enter fullscreen mode Exit fullscreen mode
2. Arrow functions don’t bind their own this
const button = {
id: "save",
bindRegular() {
setTimeout(function () {
console.log(this.id); // undefined — lost this
}, 0);
},
bindArrow() {
setTimeout(() => {
console.log(this.id); // "save" — lexical this
}, 0);
},
};
Enter fullscreen mode Exit fullscreen mode
In the browser, a regular
addEventListenercallback gets the element asthis; an arrow does not.
3. Losing this when extracting a method
const user = {
name: "Amit",
greet() {
return this.name;
},
};
const fn = user.greet;
fn(); // undefined (this is lost)
fn.call(user); // "Amit"
Enter fullscreen mode Exit fullscreen mode
4. Async always returns a Promise
async function getValue() {
return 10;
}
getValue().then(console.log); // 10
Enter fullscreen mode Exit fullscreen mode
5. Don’t use arrow functions as constructors
const Person = (name) => {
this.name = name;
};
new Person("Amit"); // TypeError
Enter fullscreen mode Exit fullscreen mode
6. IIFE creates private scope
(function () {
var x = 10;
})();
// console.log(x); // ReferenceError
Enter fullscreen mode Exit fullscreen mode
Practical Rule of Thumb
- Declaration → top-level named functions
-
Arrow → callbacks and short logic where lexical
thishelps - Method / class → object behavior and OOP
- Async → anything that waits on promises
-
Generator → when you need
yieldor custom iterators - IIFE → run once immediately with private scope (or just use a module)
Master these differences and you can explain not just how to write a function, but why one form is better than another — in interviews and in real projects.
If this helped, leave a ❤️ and comment which function type still confuses you the most.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.