JavaScript Functions Are Values with Behavior
At first, a function looks like a named block of reusable code.
function greet() { console.log("Hello");}Useful, but not very exciting.
Then JavaScript lets us assign that function to a variable, place it inside an object, pass it to another function, return it from a function, and create a new function that remembers old variables.
Suddenly, a function is not merely a block of code. It is a value with behavior.
That one idea connects function declarations, expressions, callbacks, higher-order functions, closures, and arrow functions. Let’s begin with the syntax and build toward the bigger picture.
1. Function declarations
A function declaration uses the function keyword followed by a required name:
function a() { console.log("a called");}
a();Output:
a calledThe declaration creates a function and binds it to the name a. Writing a() calls the function.
The parentheses matter:
console.log(a); // The function valueconsole.log(a()); // Calls a, then logs its return valueBecause a has no explicit return, calling it returns undefined. The second line therefore logs "a called" from inside the function and then logs undefined from the outer console.log.
Function declarations are commonly described as hoisted. This means the declaration is instantiated before the surrounding code begins executing, so this works:
a();
function a() { console.log("a called");}The source code still runs from top to bottom. JavaScript does not physically move the declaration above the call. The surrounding scope creates the function binding during its setup, before evaluating the statements.
We will give hoisting its own full article later. For now, remember that a function declaration can usually be called earlier in its scope than where it appears in the file.
2. Function expressions
A function expression creates a function where JavaScript expects an expression, which means a value.
const b = function () { console.log("b called");};
b();Output:
b calledThe function is created by the expression on the right and assigned to the variable b.
That distinction becomes visible if we try to call it too early:
b();
const b = function () { console.log("b called");};This throws a ReferenceError because b is in the temporal dead zone until its declaration is evaluated.
The original example used var, which behaves differently:
b();
var b = function () { console.log("b called");};The var binding exists and contains undefined before the assignment. Calling undefined throws a TypeError, typically with a message such as b is not a function.
The function expression itself was not available early. Only the var binding was created early.
This is more precise than saying “function expressions are not hoisted.” What matters is the combination of the expression and the variable declaration that stores its result.
3. Declarations and expressions answer different questions
Compare them directly:
a(); // Works.b(); // TypeError: b is not a function.
function a() { console.log("a called");}
var b = function () { console.log("b called");};During setup, JavaScript creates:
- The
abinding with the declared function as its value - The
bbinding withundefinedas its initial value
When execution reaches a(), a callable function is already there.
When execution reaches b(), the assignment has not happened yet, so the value is still undefined.
The difference is not simply “named function versus anonymous function.” A function expression can also have a name, and an anonymous expression assigned to a variable may receive an inferred name.
The reliable distinction is syntactic:
- A declaration introduces a function binding as a declaration in its surrounding scope.
- An expression produces a function value at the point where that expression is evaluated.
Use declarations when a function is a stable operation in the surrounding scope. Use expressions when creating a function as part of a larger expression, such as an assignment, object property, argument, or return value.
4. Anonymous functions
An anonymous function expression has no explicit name after the function keyword:
const announce = function () { console.log("Anonymous function called");};
announce();This syntax is valid because the function appears on the right side of an assignment, where an expression is allowed.
This ordinary standalone code is invalid:
function () { console.log("Where would my declaration name go?");}In that statement position, JavaScript attempts to parse a function declaration, and an ordinary function declaration requires a name. The result is a SyntaxError.
We can force the same syntax into an expression context with parentheses:
(function () { console.log("Now I am an expression");});No call happens here. The parentheses only make the parser treat the function as an expression.
Add a second pair of parentheses to call it immediately:
(function () { console.log("Called immediately");})();This pattern is called an Immediately Invoked Function Expression, or IIFE.
There is one modern detail hidden behind the word “anonymous.” JavaScript can infer a useful name property from the assignment:
const announce = function () {};
console.log(announce.name);Output:
announceThe function expression has no explicit source name, but the created function receives the inferred name announce. This helps stack traces and debugging.
So a function can be syntactically anonymous while still exposing a useful inferred name.
5. Named function expressions
A function expression may include its own name:
const c = function y() { console.log("y function called");};
c();Output:
y function calledBut this fails outside the function body:
y(); // ReferenceError: y is not definedThe name y belongs to the function expression’s own scope. The outer variable is c.
Why give an expression an inner name at all?
One reason is recursion:
const factorial = function calculate(number) { if (number <= 1) { return 1; }
return number * calculate(number - 1);};
console.log(factorial(5));Output:
120Inside the function, calculate refers directly to the current function. The explicit name can also make stack traces clearer.
Names are not decoration. They create bindings with particular scopes.
6. Parameters and arguments
Parameters and arguments are related but not identical.
- A parameter is a binding listed in a function definition.
- An argument is a value supplied in a particular call.
function showMessage(message) { console.log(message);}
showMessage("argument value");message is the parameter. "argument value" is the argument.
JavaScript does not require the number of arguments to match the number of parameters.
Missing arguments become undefined
function describe(name, role) { console.log(name, role);}
describe("Harikesh");Output:
Harikesh undefinedWe can provide a default:
function describe(name, role = "developer") { console.log(`${name} is a ${role}`);}
describe("Harikesh");Extra arguments are allowed
function showFirst(value) { console.log(value);}
showFirst("first", "second", "third");Only the first argument is bound to value. If a function intentionally accepts any number of arguments, a rest parameter is clearer:
function total(...numbers) { return numbers.reduce((sum, number) => sum + number, 0);}
console.log(total(10, 20, 30));Output:
60Functions can be arguments too
function inspect(value) { console.log(value);}
inspect(function greet() { console.log("Hello");});This logs the function value in an environment-specific representation. It does not log "Hello", because inspect never calls the function.
Compare:
function run(callback) { callback();}
run(function greet() { console.log("Hello");});Now run calls the received function, so "Hello" appears.
Passing a function and calling a function are different operations. This distinction becomes the foundation of callbacks.
7. Returning functions from functions
A function can return any JavaScript value, including another function.
function createGreeter() { return function greet() { console.log("Returned function called"); };}Calling createGreeter returns the inner function. It does not automatically call it:
const returnedFunction = createGreeter();returnedFunction();Output:
Returned function calledWe can also call both functions in one expression:
createGreeter()();Read it from left to right:
createGreeter()returns a function.- The second
()calls the returned function.
Returning functions becomes more powerful when the returned function remembers values from its creation scope:
function createGreeting(greeting) { return function greet(name) { return `${greeting}, ${name}`; };}
const sayHello = createGreeting("Hello");const sayNamaste = createGreeting("Namaste");
console.log(sayHello("Harikesh"));console.log(sayNamaste("Binod"));The returned functions close over different greeting bindings. That leads directly into Closures in JavaScript: The Function’s Backpack.
8. First-class functions
JavaScript functions are first-class values. This phrase means the language lets us use functions in the same places where other values can go.
We can assign one:
function greet() { console.log("Hello");}
const anotherName = greet;anotherName();Both variables refer to the same function object:
console.log(anotherName === greet);Output:
trueWe can store functions:
const operations = { add: (first, second) => first + second, subtract: (first, second) => first - second,};
console.log(operations.add(7, 3));We can pass functions:
function execute(operation, first, second) { return operation(first, second);}
console.log(execute(operations.subtract, 7, 3));And, as we just saw, we can return functions.
This is why functions can act as callbacks, strategy choices, event handlers, factories, and units of composition.
“First-class” does not mean “more important than other values.” It means functions are not trapped in declaration syntax. We can move them through the program.
9. Arrow functions
Arrow functions were added in ECMAScript 2015. They provide a shorter function-expression syntax:
const arrowFunction = () => { console.log("Arrow function called");};
arrowFunction();For a single expression, braces and return can be omitted:
const double = (number) => number * 2;
console.log(double(5));Output:
10Be careful when returning an object literal implicitly. Parentheses prevent the braces from being parsed as the function body:
const createUser = (name) => ({ name });Arrow functions are not only shorter regular functions. They have different semantics.
Arrow functions do not create their own this
An arrow function resolves this through its surrounding lexical scope. In simpler words, it looks outward from where it was written. Calling that arrow from somewhere else does not hand it a new this value.
function Person() { this.age = 0;
const timerId = setInterval(() => { this.age += 1; console.log(this.age);
if (this.age === 3) { clearInterval(timerId); } }, 1000);}
const person = new Person();Output over three seconds:
123The arrow callback does not receive a new this from setInterval. It keeps using the this value from the surrounding Person call, which refers to the constructed object.
This is called lexical this.
Ordinary functions play by a different rule. Their call site, the expression that invokes them, usually determines the value of this.
Arrow functions also do not create their own arguments, super, or new.target bindings. They are not constructible, so this fails:
const Person = () => {};new Person(); // TypeError: Person is not a constructorUse arrows when lexical this and concise expression syntax fit the job. Use ordinary functions when the call site should determine this, when you need a constructor, or when the ordinary function form communicates the intent better.
Shorter syntax is not automatically better syntax.
A function is created before it is called
One final distinction ties everything together.
Defining a function creates a function object and makes it reachable through some value or binding:
function declared() {}
const expressed = function () {};const arrow = () => {};None of their bodies has run yet.
Calling a function executes its body:
declared();expressed();arrow();Once functions are values, we can decide who stores them, who calls them, what arguments they receive, and whether another function comes back as the result.
That is why functions sit at the center of JavaScript. They are not merely containers for repeated statements. They are objects we can create, name, pass, return, and call.
What to remember
-
Declarations create early bindings.
A function declaration is usually callable throughout its surrounding scope. -
Expressions create values when evaluated.
Their availability depends on the variable or location receiving that value. -
Anonymous does not mean untraceable.
JavaScript can infer a function’snamefrom an assignment. -
Parameters belong to definitions; arguments belong to calls.
-
Passing is not calling.
greetis the function value;greet()executes it. -
Functions can return functions.
Returned functions may also retain lexical bindings through closures. -
Functions are first-class values.
They can be assigned, stored, passed, and returned. -
Arrow functions have lexical this.
They are not just shorter ordinary functions.
Try it yourself
Predict the output and errors without running this code first:
declared();
function declared() { console.log("declaration");}
const makeMessage = function createMessage(prefix) { return (value) => `${prefix}: ${value}`;};
const showResult = makeMessage("Result");
console.log(makeMessage.name);console.log(showResult("42"));
createMessage("outside");Then answer:
- Why can
declaredrun before its source position? - Is
makeMessagethe function’s explicit name or its outer variable? - Where is
createMessageaccessible? - What value does the returned arrow function remember?
- Which parentheses create a function, and which parentheses call one?