Episode - JavaScript Functions — Every Type Explained (Complete Guide)
Episode - JavaScript Functions — Every Type Explained (Complete Guide)
Hey everyone! Welcome back to Namaste JavaScript. Today we cover one of the most important topics — All Types of Functions in JavaScript!
Functions are the building blocks of JavaScript. But JavaScript has many different kinds of functions — and each behaves differently. By the end of this blog, you will know ALL of them inside out!
What we will cover:
- Function Declaration (Normal Function)
- Function Expression
- Named Function Expression
- Arrow Function
- Immediately Invoked Function Expression (IIFE)
- Generator Function
- Async Function
- Higher-Order Function
- Pure Function
- Recursive Function
- Constructor Function
- Method (Function inside Object)
- Anonymous Function
- Big Comparison Table
1. Function Declaration (Normal Function)
The classic, traditional way. Uses the function keyword followed by a name.
function greet(name) {
return "Hello, " + name + "!";
}
console.log(greet("Shreyesh"));
OUTPUT:
Hello, Shreyesh!
Key Characteristics:
=====================
✅ HOISTED — can be called BEFORE its definition in code!
✅ Has its own 'this' binding
✅ Has 'arguments' object
✅ Can be used as a constructor (with 'new')
✅ Has 'prototype' property
// Hoisting demo
sayHi(); // Works! Even before the function definition!
function sayHi() {
console.log("Hi!");
}
OUTPUT:
Hi!
// The entire function is hoisted to the top of scope.
// This is UNIQUE to function declarations.
When to use: ============= - Main utility functions - Recursive functions (needs a name to call itself) - When you want it available anywhere in the scope - Constructor functions
2. Function Expression
A function assigned to a variable. The function itself has no name.
const greet = function(name) {
return "Hello, " + name + "!";
};
console.log(greet("Shreyesh"));
OUTPUT:
Hello, Shreyesh!
Key Characteristics:
=====================
❌ NOT hoisted — the variable is hoisted, but value is undefined
✅ Has its own 'this' binding
✅ Has 'arguments' object
✅ Can be used as constructor (if assigned to var — careful!)
✅ Function is anonymous (no name property)
// Hoisting demo — the difference from declarations!
console.log(greet); // undefined (var is hoisted, but not value)
greet(); // TypeError: greet is not a function
var greet = function() {
console.log("Hi!");
};
// With const/let:
greet(); // ReferenceError: Cannot access 'greet' before initialization
const greet = function() { console.log("Hi!"); };
When to use:
=============
- Assign function conditionally
- Pass as callback argument
- Assign to object properties
- When you want to prevent hoisting intentionally
// Conditional function assignment
let transform;
if (userPrefersDarkMode) {
transform = function(color) { return invert(color); };
} else {
transform = function(color) { return color; };
}
3. Named Function Expression
A function expression where the function ALSO has a name. The name is only accessible inside the function itself.
const factorial = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1); // Uses 'fact' to call itself!
};
console.log(factorial(5)); // 120
console.log(fact); // ReferenceError! 'fact' not in outer scope
OUTPUT:
120
ReferenceError: fact is not defined
Key Characteristics:
=====================
✅ Has a name — shows up in stack traces (easier to debug!)
✅ Name is ONLY visible inside the function — not outside
✅ Perfect for recursion in expressions
✅ Behaves like function expression in all other ways
// Why named expressions are better for debugging:
// Anonymous function in stack trace:
// at anonymous (app.js:12) ← not helpful!
// Named function in stack trace:
// at calculateTotal (app.js:12) ← much more helpful!
const calculateTotal = function calculateTotal(items) {
return items.reduce((sum, item) => sum + item.price, 0);
};
4. Arrow Function (ES6)
Shorter syntax introduced in ES6. BUT — very different behavior from normal functions!
// Full syntax
const add = (a, b) => {
return a + b;
};
// Implicit return (single expression — no curly braces needed)
const add = (a, b) => a + b;
// Single parameter — no parentheses needed
const double = n => n * 2;
// No parameters — empty parentheses required
const greet = () => "Hello!";
// Returning object — wrap in ()
const getUser = (id) => ({ id, role: "admin" });
console.log(add(3, 4)); // 7
console.log(double(5)); // 10
console.log(greet()); // Hello!
Key Characteristics:
=====================
❌ NO own 'this' — inherits 'this' from lexical scope
❌ NO 'arguments' object — use rest params (...args)
❌ CANNOT be used as constructor (no 'new')
❌ NO 'prototype' property
✅ Implicit return for single expressions
✅ NOT hoisted (stored in variable)
✅ Perfect for callbacks and array methods
// 'this' behavior — most important difference!
const user = {
name: "Shreyesh",
normalGreet: function() {
console.log("Normal:", this.name); // ✅ "Shreyesh"
},
arrowGreet: () => {
console.log("Arrow:", this.name); // ❌ undefined
// Arrow function inherits 'this' from module/global scope
// NOT from the user object!
}
};
user.normalGreet(); // Normal: Shreyesh
user.arrowGreet(); // Arrow: undefined
When to use:
=============
- Callbacks inside methods (forEach, map, filter)
- Promise chains (.then, .catch)
- Short utility functions
- React functional components and hooks
- When you want to preserve 'this' from outer scope
// Perfect use case
const user = {
name: "Shreyesh",
hobbies: ["coding", "reading"],
showHobbies: function() {
// Arrow function here inherits 'this' from showHobbies
this.hobbies.forEach(hobby => {
console.log(this.name + " likes " + hobby); // ✅ works!
});
}
};
5. IIFE — Immediately Invoked Function Expression
A function that is defined AND called immediately at the same time. Runs once and disappears!
// Basic IIFE syntax
(function() {
console.log("I run immediately!");
})();
OUTPUT:
I run immediately!
// IIFE with parameters
(function(name) {
console.log("Hello,", name);
})("Shreyesh");
OUTPUT:
Hello, Shreyesh
// Arrow function IIFE
(() => {
console.log("Arrow IIFE!");
})();
// IIFE with return value
const result = (function(a, b) {
return a + b;
})(10, 20);
console.log(result); // 30
Key Characteristics:
=====================
✅ Creates its own private scope — variables don't pollute global
✅ Runs ONCE and immediately — no separate invocation needed
✅ Variables inside are NOT accessible from outside
✅ Used in the Module Pattern (before ES6 modules)
// Data privacy example
const counter = (function() {
let count = 0; // Private variable!
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count
};
})();
counter.increment();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 3
console.log(count); // ReferenceError! 'count' is private
When to use:
=============
- Initialize something once (DB setup, config)
- Create private scope to avoid global variable pollution
- Module pattern (older codebase)
- Wrapping code to avoid naming conflicts in scripts
6. Generator Function
A special function that can pause and resume its execution! Uses function* syntax and the yield keyword.
function* countUp() {
console.log("Started!");
yield 1; // Pause here, return 1
console.log("Resumed!");
yield 2; // Pause here, return 2
console.log("Resumed again!");
yield 3; // Pause here, return 3
console.log("Done!");
}
const gen = countUp(); // Does NOT run the function yet!
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
OUTPUT:
Started!
{ value: 1, done: false }
Resumed!
{ value: 2, done: false }
Resumed again!
{ value: 3, done: false }
Done!
{ value: undefined, done: true }
Key Characteristics:
=====================
✅ Defined with function* (asterisk after 'function')
✅ Returns a Generator object (an iterator)
✅ yield keyword pauses execution and returns a value
✅ .next() resumes execution from where it paused
✅ .next() returns { value, done } object
✅ 'done: true' when function has no more yields
❌ Cannot be an arrow function (function* only)
// Infinite sequence generator!
function* infiniteIds() {
let id = 1;
while (true) {
yield id++; // Infinite loop is OK — yields pause it!
}
}
const getId = infiniteIds();
console.log(getId.next().value); // 1
console.log(getId.next().value); // 2
console.log(getId.next().value); // 3
// Never exhausts — only runs when you call .next()!
// Spread with generator
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
console.log([...range(1, 5)]); // [1, 2, 3, 4, 5]
Real-World Use Cases:
======================
1. Lazy evaluation — only compute values when needed
2. Infinite sequences — IDs, pagination
3. Custom iterators (for...of loops)
4. Data streaming — process large datasets in chunks
5. Redux-Saga — managing side effects in React apps
function* paginate(data, pageSize) {
for (let i = 0; i < data.length; i += pageSize) {
yield data.slice(i, i + pageSize);
}
}
const pages = paginate([1,2,3,4,5,6,7,8,9,10], 3);
console.log(pages.next().value); // [1, 2, 3]
console.log(pages.next().value); // [4, 5, 6]
console.log(pages.next().value); // [7, 8, 9]
console.log(pages.next().value); // [10]
7. Async Function (ES2017)
A function that always returns a Promise and lets you use the await keyword inside it.
// async function declaration
async function fetchUser(id) {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
return user; // Automatically wrapped in Promise.resolve()!
}
// async function expression
const fetchUser = async function(id) {
const user = await fetch(`/api/users/${id}`).then(r => r.json());
return user;
};
// async arrow function
const fetchUser = async (id) => {
const user = await fetch(`/api/users/${id}`).then(r => r.json());
return user;
};
// Calling an async function — ALWAYS returns a Promise!
fetchUser(1).then(user => console.log(user));
// Or with await inside another async function
async function main() {
const user = await fetchUser(1);
console.log(user);
}
Key Characteristics:
=====================
✅ Always returns a Promise (even if you return a plain value)
✅ await pauses execution until Promise resolves
✅ Can be declaration, expression, arrow, or method
✅ Error handling with try/catch (clean, synchronous-looking)
✅ Runs on microtask queue
async function example() {
return 42; // Becomes Promise.resolve(42)
}
example().then(v => console.log(v)); // 42
// Error handling
async function riskyOperation() {
try {
const result = await mightFail();
return result;
} catch (err) {
console.log("Caught:", err.message);
}
}
// Parallel execution — DON'T await one by one if independent!
// ❌ Sequential (slow — waits for each)
const a = await fetchA();
const b = await fetchB();
// ✅ Parallel (fast — both start at the same time)
const [a, b] = await Promise.all([fetchA(), fetchB()]);
8. Higher-Order Function
A function that either takes another function as argument OR returns a function.
// Higher-Order Function — takes a function as argument
function applyOperation(a, b, operation) {
return operation(a, b);
}
const result1 = applyOperation(10, 5, (a, b) => a + b); // 15
const result2 = applyOperation(10, 5, (a, b) => a * b); // 50
console.log(result1); // 15
console.log(result2); // 50
// Higher-Order Function — returns a function
function multiplier(factor) {
return function(number) {
return number * factor;
};
}
const triple = multiplier(3);
const quadruple = multiplier(4);
console.log(triple(5)); // 15
console.log(quadruple(5)); // 20
// Array methods are Higher-Order Functions!
[1, 2, 3].map(n => n * 2); // map is HOF
[1, 2, 3].filter(n => n > 1); // filter is HOF
[1, 2, 3].reduce((acc, n) => acc + n, 0); // reduce is HOF
9. Pure Function
A function that: (1) given the same inputs always returns the same output, and (2) has NO side effects.
// ✅ PURE FUNCTION — no side effects, predictable
function add(a, b) {
return a + b; // Always same result for same inputs!
}
console.log(add(2, 3)); // Always 5
console.log(add(2, 3)); // Always 5
console.log(add(2, 3)); // Always 5
// ❌ IMPURE — uses external variable (side cause)
let taxRate = 0.1;
function calculateTax(price) {
return price * taxRate; // Depends on external 'taxRate'!
}
// If taxRate changes, same input gives different output!
// ❌ IMPURE — modifies external state (side effect)
let total = 0;
function addToTotal(n) {
total += n; // Modifies external variable!
return total;
}
// Pure version
function addToTotal(currentTotal, n) {
return currentTotal + n; // No mutation — returns new value
}
Why Pure Functions Matter: =========================== ✅ Predictable — easy to reason about ✅ Testable — just check input vs output ✅ Cacheable (memoizable) — same inputs = same output ✅ Foundation of functional programming ✅ React relies on pure rendering — components should be pure!
10. Recursive Function
A function that calls itself until a base condition is met.
// Classic recursion — factorial
function factorial(n) {
if (n <= 1) return 1; // Base case — STOPS recursion
return n * factorial(n - 1); // Recursive call
}
console.log(factorial(5)); // 5 * 4 * 3 * 2 * 1 = 120
// How it works:
// factorial(5)
// = 5 * factorial(4)
// = 4 * factorial(3)
// = 3 * factorial(2)
// = 2 * factorial(1)
// = 1 (base case!)
// = 2 * 1 = 2
// = 3 * 2 = 6
// = 4 * 6 = 24
// = 5 * 24 = 120
// Tree traversal — recursion shines here!
function traverseTree(node) {
if (!node) return; // Base case
console.log(node.value);
traverseTree(node.left); // Recurse left
traverseTree(node.right); // Recurse right
}
// Flatten nested array recursively
function flatten(arr) {
return arr.reduce((acc, item) =>
acc.concat(Array.isArray(item) ? flatten(item) : item), []);
}
console.log(flatten([1, [2, [3, [4]]]])); // [1, 2, 3, 4]
11. Constructor Function
A regular function used with the new keyword to create objects. The predecessor to ES6 classes.
function Person(name, age) {
this.name = name;
this.age = age;
// 'this' refers to the new object being created!
}
// Adding methods via prototype (shared across all instances)
Person.prototype.greet = function() {
return "Hi, I am " + this.name;
};
const p1 = new Person("Shreyesh", 25);
const p2 = new Person("Arjun", 30);
console.log(p1.greet()); // Hi, I am Shreyesh
console.log(p2.greet()); // Hi, I am Arjun
console.log(p1 instanceof Person); // true
// What 'new' does internally:
// 1. Creates empty object: {}
// 2. Sets 'this' to that object
// 3. Runs the constructor function
// 4. Sets the prototype: obj.__proto__ = Person.prototype
// 5. Returns 'this' (the new object)
// ES6 class is just syntactic sugar over this!
class PersonClass {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return "Hi, I am " + this.name;
}
}
// Internally: same prototype-based mechanism!
12. Anonymous Function
A function with no name. Used inline as callbacks or assigned to variables.
// Anonymous function as callback
setTimeout(function() {
console.log("Anonymous callback!");
}, 1000);
// Anonymous arrow function
[1, 2, 3].map(function(n) { return n * 2; });
[1, 2, 3].map(n => n * 2); // Anonymous arrow function
// Anonymous function assigned to variable
// (it's now a function expression)
const greet = function(name) {
return "Hello, " + name;
};
// Downside: anonymous functions show as "(anonymous)"
// in stack traces — harder to debug!
// Prefer named function expressions in production code.
Quick Recap — All Function Types
| Type | Syntax | Hoisted | Own 'this' | arguments | Constructor | Best Use |
|---|---|---|---|---|---|---|
| Declaration | function name() {} | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | Main functions, recursion |
| Expression | const f = function() {} | ❌ No | ✅ Yes | ✅ Yes | ✅ Yes | Callbacks, conditional assign |
| Named Expression | const f = function name() {} | ❌ No | ✅ Yes | ✅ Yes | ✅ Yes | Self-referential, debuggable |
| Arrow | const f = () => {} | ❌ No | ❌ Lexical | ❌ No | ❌ No | Callbacks, array methods |
| IIFE | (function() {})() | N/A | ✅ Yes | ✅ Yes | Rarely | Private scope, run-once |
| Generator | function* name() {} | ✅ Yes | ✅ Yes | ✅ Yes | ❌ No | Lazy sequences, iterators |
| Async | async function() {} | Depends | Depends | Depends | ❌ No | Async/await operations |
| Constructor | function Name() {} | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | Creating objects (pre-class) |
Key Points to Remember
- Function declarations are hoisted — expressions and arrows are NOT
- Arrow functions have no own 'this' — they inherit from lexical scope
- Generator functions pause with yield and resume with .next()
- async functions always return a Promise — even if you return a plain value
- IIFE runs immediately — creates private scope, used for module pattern
- Pure functions — same input, same output, no side effects
- Higher-order functions take or return other functions (map, filter, reduce)
- Named function expressions are better than anonymous for debugging
- Constructor functions are used with 'new' — ES6 classes are sugar over this
- Recursive functions call themselves — always need a base case to stop!
Keep coding, keep learning! See you in the next one!
Post a Comment