VS Vikesh Singh

Courses JavaScript Lesson 4

JAVASCRIPT · LESSON 4 OF 18

Functions: Declarations, Expressions, and Arrow Functions

· 4 min read

So far we've stored single values.

So far we've stored single values. Functions let you package up a block of code so you can run it again and again without retyping it, and so you can give a sequence of steps a clear name. JavaScript gives you three main ways to write a function, and knowing the differences matters for real projects, not just trivia.

Function Declarations And Hoisting

The first is a function declaration.

function greet(name) {
  return "Hello, " + name;
}
console.log(greet("Tony Stark"));

A function declaration is "hoisted," meaning JavaScript makes it available throughout its scope even before the line where it's written in your file. That's different from let and const, which exist in a sort of locked state before their line runs (you may see this called the "temporal dead zone" in other material, but the practical effect is: you can call a function declaration before you define it in the file, but you cannot do that with a variable holding a function expression).

sayHi();
function sayHi() {
  console.log("Hi there");
}

This works fine, printing "Hi there," because the whole function declaration is hoisted. Now compare that to a function expression — a function stored in a variable.

const sayBye = function () {
  console.log("Bye");
};
sayBye();

Function Expressions

Here the function is the value assigned to the const sayBye. If you tried to call sayBye() before this line, you'd get an error, because only the variable declaration is hoisted, not its value. This is one of the most common sources of confusing "function is not defined" or "cannot access before initialization" errors for beginners — the fix is simply to define things before you use them, which is good practice anyway.

Arrow Functions

The third style, and the one you'll see most in modern code, is the arrow function.

const add = (a, b) => a + b;
console.log(add(2, 3));

const square = x => x * x;
console.log(square(4));

const logMessage = () => {
  console.log("No parameters needed");
};

Arrow functions are shorter, and when the function body is a single expression, you can skip the curly braces and the return keyword entirely — the result of the expression is returned automatically. With one parameter you can skip the parentheses too (though many style guides keep them for consistency, which is a reasonable habit).

This Inside Arrow Functions

There's one real, functional difference between arrow functions and regular functions, not just a style preference: arrow functions do not have their own this. They inherit this from whatever scope they were written in. This matters most once you start working with objects and event listeners.

const car = {
  brand: "Stark Industries",
  showBrandRegular: function () {
    console.log(this.brand);
  },
  showBrandArrow: () => {
    console.log(this.brand);
  }
};
car.showBrandRegular();
car.showBrandArrow();

showBrandRegular() correctly prints "Stark Industries," because a regular function's this is determined by how it's called — here, called as car.showBrandRegular(), so this is car. showBrandArrow() prints undefined, because the arrow function grabbed this from the surrounding scope (outside the object) at the moment it was defined, not from car. This is a genuinely common bug for beginners who use arrow functions everywhere without knowing this distinction — the fix is simple: use regular functions for object methods, and arrow functions for standalone logic or callbacks.

Naming Functions Well

A final practical habit: give your functions one clear job and a name that describes it, like calculateTotal or isValidEmail, rather than vague names like doStuff. You will read function names far more often than you write them, so clarity there pays off constantly.

Default parameters, briefly previewed here, pair naturally with all three function styles above and get a full treatment in the ES6 features lesson later in this course. For now, just notice that a function's parameter list is really just local variables that get assigned whatever was passed in — if nothing was passed in, the parameter is simply undefined, which is exactly why calling a function with too few arguments doesn't throw an error in JavaScript the way it does in many other languages. This flexibility is convenient, but it also means a missing argument fails silently rather than loudly, so get comfortable checking for undefined on anything optional.

A final practical habit: when you find yourself writing the same few lines of logic in more than one place in your code, that's usually a signal to turn it into its own named function. Beyond saving typing, a well-named function documents intent on its own — calculateShippingCost(order) tells a reader what it does without them needing to read its body at all, while repeated inline logic forces every reader to re-derive that meaning each time they see it.

Try it yourself: write the same small function — say, one that doubles a number — as a function declaration, a function expression, and an arrow function. Call all three and confirm they give the same result, then try calling the function expression one line before it's declared to see the error for yourself.