Javascript · Functions

Functions Interview Questions
With Answers & Code Examples

19 carefully curated Functions interview questions with working code examples and real interview gotchas.

Practice Interactively →← All Categories
19 questions0 beginner11 core8 advanced
Q1Core

What is the difference between call, apply, and bind?

💡 Hint: All set "this" — call=comma, apply=array, bind=returns new fn

All three explicitly set this:

  • call(thisArg, arg1, arg2) — invoke immediately, args individually
  • apply(thisArg, [args]) — invoke immediately, args as array
  • bind(thisArg, arg1) — returns new permanently-bound function
function greet(greeting, punct) {
  return `${greeting}, ${this.name}${punct}`;
}
const user = { name: 'Priya' };
greet.call(user, 'Hello', '!');     // "Hello, Priya!"
greet.apply(user, ['Hi', '.']);     // "Hi, Priya."
const fn = greet.bind(user, 'Hey');
fn('?');                            // "Hey, Priya?"
💡 Call=Comma, Apply=Array, Bind=returns Bound fn
Practice this question →
Q2Core

How do arrow functions differ from regular functions?

💡 Hint: No own this, no arguments object, no new, no prototype

Arrow functions are not just shorter syntax — key behavioral differences:

  • No own this — inherits lexical this from outer scope
  • No arguments object — use rest params (...args)
  • Cannot be constructors — new throws TypeError
  • No prototype property
const obj = {
  name: 'Dev',
  regular() { console.log(this.name); },  // 'Dev'
  arrow: () => console.log(this.name),    // undefined
};
💡 Use arrow fns for callbacks (inherit this). Use regular fns for methods and constructors.
Practice this question →
Q3Core

What is a pure function and why does it matter?

💡 Hint: Same input → same output, no side effects

A pure function: (1) always returns the same output for same inputs, (2) has zero side effects.

// Pure ✅
const add = (a, b) => a + b;

// Impure ❌ — modifies external state
let total = 0;
const addToTotal = (n) => { total += n; return total; };

Pure functions are predictable, testable, and cacheable. React expects components and reducers to be pure.

Practice this question →
Q4Core

What are Higher-Order Functions (HOF)?

💡 Hint: Functions that take other functions as arguments, or return functions as results

A Higher-Order Function is a function that either:

  • Accepts a function as an argument, OR
  • Returns a function as its result (or both)
// Takes a function as argument
[1, 2, 3].map(x => x * 2);        // map is HOF — takes callback
[1, 2, 3].filter(x => x > 1);     // filter is HOF
setTimeout(() => console.log('hi'), 1000); // HOF

// Returns a function
function multiplier(factor) {
  return (n) => n * factor; // returns a new function
}
const double = multiplier(2);
const triple = multiplier(3);
double(5); // 10
triple(5); // 15

// Does both (debounce)
function debounce(fn, delay) {
  let timer;
  return (...args) => {          // returns function
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay); // takes fn
  };
}

HOFs are foundational to functional programming, enabling code reuse, composition, and abstractions without mutation.

💡 map, filter, reduce, forEach, addEventListener, setTimeout — all HOFs you use every day without realizing it.
Practice this question →
Q5Core

What is an IIFE (Immediately Invoked Function Expression) and when do you use it?

💡 Hint: Defined and called immediately — creates a private scope

An IIFE is a function that is both defined and invoked immediately. It creates an isolated scope.

// Classic IIFE syntax
(function() {
  const private = 'inaccessible outside';
  console.log(private);
})();

// Arrow IIFE
(() => {
  // isolated scope
})();

// IIFE with parameters
(function(global) {
  global.myLib = {};
})(window);

// IIFE returning a value
const result = (() => {
  const x = computeExpensiveThing();
  return x * 2;
})();

Use cases:

  • Avoid polluting global scope (classic library pattern)
  • Create truly private variables (module pattern)
  • Capture loop variables (pre-let closure fix)
  • One-time initialization logic
💡 In modern JS, ES modules and block-scoped let/const make IIFEs less necessary. But they're heavily used in legacy code and still valid for specific patterns.
Practice this question →
Q6Core

What does it mean that functions are "first-class citizens" in JavaScript?

💡 Hint: Functions are values — assignable, passable, returnable, storable

Functions are first-class citizens — they're treated as values just like strings or numbers. This means:

  • Assign to variables
  • Pass as arguments
  • Return from functions
  • Store in arrays/objects
  • Have properties and methods attached
// Assigned to variable
const greet = (name) => 'Hello, ' + name;

// Passed as argument (callback)
[1, 2, 3].forEach(function(n) { console.log(n); });

// Returned from function
function makeAdder(x) {
  return (y) => x + y; // ← function as return value
}
const add5 = makeAdder(5);
add5(3); // 8

// Stored in object
const math = {
  add: (a, b) => a + b,
  sub: (a, b) => a - b,
};

// Has properties
function fn() {}
fn.version = '1.0';
console.log(fn.name);   // 'fn'
console.log(fn.length); // 0 (param count)
💡 First-class functions are what enable HOFs, callbacks, closures, and all functional programming patterns in JS.
Practice this question →
Q7Advanced

What is currying and how do you implement a generic curry function?

💡 Hint: Transform f(a,b,c) into f(a)(b)(c) — each call returns a new function waiting for more args

Currying transforms a multi-argument function into a chain of unary functions, each waiting for one argument at a time.

// Manual curried function
const add = a => b => c => a + b + c;
add(1)(2)(3); // 6
const add1 = add(1);     // partially applied — waits for b and c
const add1and2 = add1(2); // waits for c
add1and2(3);              // 6

// Generic curry utility
function curry(fn) {
  return function curried(...args) {
    if (args.length >= fn.length) { // enough args collected?
      return fn(...args);
    }
    return (...moreArgs) => curried(...args, ...moreArgs);
  };
}

const sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum);
curriedSum(1)(2)(3); // 6
curriedSum(1, 2)(3); // 6 — also works (partial application hybrid)

Currying enables: partial application, point-free style, composable specialized functions.

💡 Currying vs Partial Application: currying always breaks a function into unary steps. Partial application pre-fills SOME arguments and returns a function waiting for the rest.
Practice this question →
Q8Advanced

What is memoization and how do you implement it?

💡 Hint: Cache results keyed by arguments — avoid recomputing for the same inputs

Memoization is an optimization where a function caches its results. Calling with the same inputs returns the cached result instead of recomputing.

function memoize(fn) {
  const cache = new Map();
  return function(...args) {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key); // cache hit
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

// Fibonacci without memoization: O(2^n)
// With memoization: O(n)
const fib = memoize(function(n) {
  if (n <= 1) return n;
  return fib(n - 1) + fib(n - 2); // self-referencing
});

fib(40); // instant — 40 cache entries
fib(40); // instant again — cache hit

When to use: Pure functions with expensive computation and repeated same-argument calls. React's useMemo and useCallback implement this concept.

💡 Only memoize PURE functions — same input must always give same output. Don't memoize time-dependent or side-effectful functions.
Practice this question →
Q9Advanced

What is function composition and how do compose() and pipe() differ?

💡 Hint: Chain functions: output of one becomes input of next — compose=right-to-left, pipe=left-to-right

Function composition combines multiple functions where the output of one becomes the input of the next, building complex operations from simple pieces.

// compose — right to left (mathematical convention)
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);

// pipe — left to right (more readable)
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);

const trim      = str => str.trim();
const lowercase = str => str.toLowerCase();
const addBang   = str => str + '!';

// compose: addBang(lowercase(trim(x)))
const processC = compose(addBang, lowercase, trim);

// pipe: trim → lowercase → addBang
const processP = pipe(trim, lowercase, addBang);

processP('  Hello World  '); // 'hello world!'
processC('  Hello World  '); // 'hello world!'

// Without composition (harder to read as chain grows)
const manual = str => addBang(lowercase(trim(str)));
💡 compose() mirrors mathematical f∘g notation. pipe() reads like a Unix pipeline — more natural for most developers. Both are equivalent, just different argument order.
Practice this question →
Q10Core

What are rest parameters and how do they differ from the arguments object?

💡 Hint: Rest (...args) is a real Array; arguments is array-like, no arrow support, no Array methods

Rest parameters (...args) collect remaining function arguments into a real Array.

// Rest parameters — modern
function sum(first, ...rest) {
  console.log(first);   // 1
  console.log(rest);    // [2, 3, 4] — real Array!
  return rest.reduce((a, b) => a + b, first);
}
sum(1, 2, 3, 4); // 10
rest.map(x => x * 2); // ✅ has Array methods

// arguments — legacy
function old() {
  console.log(arguments);        // { 0:1, 1:2, ... } — array-LIKE
  console.log(arguments.map);    // undefined — NOT a real Array
  const arr = Array.from(arguments); // convert needed
}

// Arrow functions have NO arguments object
const arrow = () => {
  console.log(arguments); // ReferenceError!
  // Use rest: (...args) => { console.log(args) }
};

Key differences:

  • Rest is a real Array → has all array methods
  • arguments is array-like → no map/filter/etc
  • Arrow functions don't have arguments
  • Rest collects only the remaining args after named params
💡 Always use rest parameters in modern code. arguments is legacy and has quirks that trip people up.
Practice this question →
Q11Core

What is recursion and what causes a stack overflow?

💡 Hint: Function calling itself — needs a base case; too many calls = call stack exhausted

Recursion is when a function calls itself. Every recursive function needs:

  1. A base case — a stopping condition
  2. A recursive case — that moves toward the base case
// Factorial
function factorial(n) {
  if (n <= 1) return 1;         // base case
  return n * factorial(n - 1); // recursive case
}
factorial(5); // 120

// Flatten nested array
function flatten(arr) {
  return arr.reduce((acc, item) =>
    Array.isArray(item)
      ? acc.concat(flatten(item)) // recurse
      : acc.concat(item),
  []);
}
flatten([1, [2, [3]]]); // [1, 2, 3]

Stack overflow: Each recursive call adds a stack frame. Without a base case (or with very deep recursion), the call stack fills up:

function infinite(n) {
  return infinite(n + 1); // no base case!
}
infinite(1); // RangeError: Maximum call stack size exceeded
💡 Tail Call Optimization (TCO) can theoretically prevent stack overflow for tail-recursive calls, but TCO is only reliably supported in Safari. For deep recursion, use iteration or trampolining.
Practice this question →
Q12Advanced

What is a Named Function Expression (NFE)?

💡 Hint: A function expression with an internal name — visible inside the body only

A Named Function Expression has a name after function, but unlike a declaration, the name is only visible inside the function body — not outside.

// Anonymous function expression — self-reference is fragile
const factorial = function(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1); // relies on outer var 'factorial'
};
// If factorial is reassigned, self-reference breaks!

// Named function expression — safe self-reference
const factorial = function fact(n) {
  if (n <= 1) return 1;
  return n * fact(n - 1); // 'fact' is always THIS function
};

console.log(factorial.name); // 'fact'
console.log(typeof fact);    // 'undefined' — not accessible outside!

// If outer var is reassigned, NFE still works
const f = factorial;
factorial = null;
f(5); // 120 — 'fact' still refers to itself correctly
💡 Use NFEs for recursive function expressions. Better stack traces (shows 'fact' not 'anonymous') and reliable self-reference even if the outer variable changes.
Practice this question →
Q13Advanced

What are the Module Pattern and the Revealing Module Pattern?

💡 Hint: IIFE + closure = private scope; expose only public API; revealing = explicitly name what's public

Pre-ES-modules patterns for creating encapsulated, private state in JavaScript.

// Module Pattern
const counter = (function() {
  let _count = 0; // private — inaccessible from outside

  return {
    increment() { _count++; },
    decrement() { _count--; },
    getCount()  { return _count; }
  };
})();

counter.increment();
counter.getCount(); // 1
counter._count;     // undefined — truly private ✓

// Revealing Module Pattern
// Define everything privately, then reveal selectively
const bankAccount = (function() {
  let _balance = 1000;
  let _transactions = [];

  function _log(type, amount) {
    _transactions.push({ type, amount, date: Date.now() });
  }

  function deposit(amount) {
    if (amount > 0) { _balance += amount; _log('deposit', amount); }
  }

  function withdraw(amount) {
    if (amount > 0 && amount <= _balance) {
      _balance -= amount; _log('withdrawal', amount);
    }
  }

  function getBalance() { return _balance; }
  function getHistory() { return [..._transactions]; }

  // Explicitly reveal the public interface
  return { deposit, withdraw, getBalance, getHistory };
})();
💡 Before ES modules, this was THE pattern for encapsulation. jQuery, Lodash, and most pre-2015 JS libraries used this. Today, use ES modules instead.
Practice this question →
Q14Advanced

What is partial application and how does it differ from currying?

💡 Hint: Partial application = pre-fill some args, return function waiting for the rest; currying = always one arg at a time

Both techniques create specialized functions from general ones — but differ in how arguments are collected.

// Partial Application — pre-fill SOME args
function partial(fn, ...presetArgs) {
  return function(...laterArgs) {
    return fn(...presetArgs, ...laterArgs);
  };
}

const add = (a, b, c) => a + b + c;
const add10 = partial(add, 10);         // pre-fill first arg
const add10and20 = partial(add, 10, 20); // pre-fill two args

add10(5, 3);     // 18 — takes remaining 2 args AT ONCE
add10and20(7);   // 37 — takes remaining 1 arg

// Currying — always ONE arg at a time
const curriedAdd = a => b => c => a + b + c;
curriedAdd(1)(2)(3); // 6 — strictly one at a time

// Practical partial application with bind()
function greet(greeting, punct, name) {
  return `${greeting}, ${name}${punct}`;
}
const hello = greet.bind(null, 'Hello', '!'); // partial via bind
hello('Alice'); // 'Hello, Alice!'
hello('Bob');   // 'Hello, Bob!'

Summary:

  • Currying: f(a, b, c) → f(a)(b)(c) — each call takes exactly ONE argument
  • Partial application: f(a, b, c) → f(a, b)(c) — pre-fill any number of args
💡 In practice, curried functions support partial application too (you can call with multiple args and they accumulate). The distinction is mostly theoretical.
Practice this question →
Q15Core

What is the difference between function declarations and function expressions?

💡 Hint: Declarations are hoisted fully; expressions are not — and expression form gives more control

Both create functions but behave differently with hoisting and syntax.

// Function Declaration — hoisted completely (name + body)
greet(); // ✅ works BEFORE the declaration
function greet() { return 'hello'; }

// Function Expression — NOT hoisted as a function
sayHi(); // ❌ TypeError: sayHi is not a function (var hoisted as undefined)
var sayHi = function() { return 'hi'; };

// Arrow function expression
const add = (a, b) => a + b;

// Named function expression (NFE) — name only visible inside
const fact = function factorial(n) {
  return n <= 1 ? 1 : n * factorial(n - 1); // factorial = self
};
console.log(typeof factorial); // undefined — not accessible outside

When to prefer each:

  • Declaration — top-level utility functions, when you want full hoisting
  • Expression — callbacks, conditional function creation, storing in variables, passing as args
💡 Most style guides prefer expressions (especially arrow) for callbacks and class methods. Declarations are fine for named utility functions at module scope.
Practice this question →
Q16Core

What is the spread operator (...) and what are its use cases?

💡 Hint: Expand iterable into individual elements — arrays, function args, object spreading
// 1. Spread in function calls — expand array as arguments
const nums = [1, 5, 3, 2, 4];
Math.max(...nums); // 5 — same as Math.max(1,5,3,2,4)

// 2. Copy and combine arrays (immutable operations)
const a = [1, 2, 3];
const b = [4, 5, 6];
const copy    = [...a];          // [1,2,3] — shallow copy
const merged  = [...a, ...b];    // [1,2,3,4,5,6]
const prepend = [0, ...a];       // [0,1,2,3]

// 3. Spread in object literals (ES2018)
const base = { a: 1, b: 2 };
const extended = { ...base, c: 3 };        // { a:1, b:2, c:3 }
const override = { ...base, b: 99 };       // { a:1, b:99 } — later wins

// 4. Convert iterable to array
const set = new Set([1,2,3]);
[...set]; // [1,2,3]
[...'hello']; // ['h','e','l','l','o']
[...document.querySelectorAll('p')]; // NodeList → Array

// 5. Clone + update (immutable pattern)
const state = { user: 'Alice', count: 0 };
const newState = { ...state, count: state.count + 1 };
💡 Spread creates SHALLOW copies — nested objects are still shared references. Use structuredClone() for deep copies. Spread in objects is order-sensitive: later properties win.
Practice this question →
Q17Core

What are default parameters and how do they work?

💡 Hint: Evaluated at call time, only when arg is undefined — can reference earlier params and outer scope
// Basic default parameters
function greet(name = 'World', greeting = 'Hello') {
  return `${greeting}, ${name}!`;
}
greet();              // 'Hello, World!'
greet('Alice');       // 'Hello, Alice!'
greet('Bob', 'Hi');   // 'Hi, Bob!'
greet(undefined, 'Hey'); // 'Hey, World!' — undefined triggers default
greet(null, 'Hey');   // 'Hey, null!' — null does NOT trigger default

// Defaults can reference earlier parameters
function range(start = 0, end = start + 10) {
  return { start, end };
}
range();     // { start: 0, end: 10 }
range(5);    // { start: 5, end: 15 }

// Defaults can be expressions / function calls
let count = 0;
function makeId(id = ++count) { return id; } // evaluated each call
makeId(); // 1
makeId(); // 2
makeId(99); // 99 (provided, so default not evaluated)

// Defaults + destructuring (very common pattern)
function createUser({ name = 'Anonymous', role = 'user', active = true } = {}) {
  return { name, role, active };
}
createUser({ name: 'Alice' }); // { name:'Alice', role:'user', active:true }
createUser();                  // {} → uses = {} → all defaults apply
💡 Default params replaced the old pattern: name = name || 'World'. The old way was buggy (falsy values like 0 or '' triggered the default). New defaults only trigger for undefined.
Practice this question →
Q18Advanced

What is tail call optimization (TCO) and how does it prevent stack overflow?

💡 Hint: A tail call is the last operation in a function — engine can reuse the stack frame instead of adding a new one

A tail call is when the last action of a function is calling another function. If the engine applies TCO, it reuses the current stack frame instead of pushing a new one — preventing stack overflow for deep recursion.

// Regular recursion — O(n) stack frames
function factorial(n) {
  if (n <= 1) return 1;
  return n * factorial(n - 1); // NOT tail call — must multiply AFTER return
}
factorial(100000); // Stack overflow!

// Tail-recursive version — last op is the call
function factTail(n, accumulator = 1) {
  if (n <= 1) return accumulator;
  return factTail(n - 1, n * accumulator); // tail call — nothing after it
}
// With TCO, this would run in O(1) stack space

// Current reality:
// TCO is in the ES6 spec BUT only Safari implements it fully
// V8 (Node/Chrome) removed their TCO implementation
// So tail recursion is NOT safe in Node.js / Chrome!

// Practical alternatives:
// 1. Iteration (always safe)
function factIterative(n) {
  let result = 1;
  for (let i = 2; i <= n; i++) result *= i;
  return result;
}

// 2. Trampolining — simulates TCO in user space
const trampoline = fn => (...args) => {
  let result = fn(...args);
  while (typeof result === 'function') result = result();
  return result;
};

const factTramp = trampoline(function fact(n, acc = 1) {
  return n <= 1 ? acc : () => fact(n - 1, n * acc); // return fn instead of calling
});
factTramp(100000); // works!
💡 Know the theory for interviews but use iteration in production. Trampolining is the practical way to handle very deep recursion in JS without relying on TCO.
Practice this question →
Q19Advanced

Implementing the Factory Design Pattern in JavaScript Functions

💡 Hint: Think about how you can use a single function to create objects of different classes, and how this can help in managing complexity and improving code reusability.

The Factory design pattern is a creational pattern that provides an interface for creating objects without specifying the exact class of object that will be created. In JavaScript, we can implement the Factory pattern using functions. Here's an example:

function createUser(type, name) { 
if (type === 'admin') {
return new AdminUser(name);
} else if (type === 'moderator') {
return new ModeratorUser(name);
} else {
return new RegularUser(name);
}
}

class AdminUser {
constructor(name) {
this.name = name;
this.role = 'admin';
}
}

class ModeratorUser {
constructor(name) {
this.name = name;
this.role = 'moderator';
}
}

class RegularUser {
constructor(name) {
this.name = name;
this.role = 'regular';
}
}

In this example, the createUser function acts as a factory, creating and returning objects of different classes based on the input type.

Practice this question →

Other Javascript Interview Topics

Core JSAsync JSObjectsArrays'this' KeywordError HandlingModern JSPerformanceDOM & EventsBrowser APIs

Ready to practice Functions?

Get AI feedback on your answers, predict code output, and fix real bugs.

Start Free Practice →