Function Declarations vs. Function Expressions vs. Arrow Functions

Hero image for Function Declarations vs. Function Expressions vs. Arrow Functions. Image by Pawel Czerwinski.
Hero image for 'Function Declarations vs. Function Expressions vs. Arrow Functions.' Image by Pawel Czerwinski.

JavaScript gives developers several ways to define a function, and that flexibility is both useful and slightly awkward for beginners.

You can write a function declaration:

function greet(): void {  console.log('Hello');}

or a function expression:

const greet = function (): void {  console.log('Hello');};

or an arrow function:

const greet = (): void => {  console.log('Hello');};

All three define callable behaviour, but they are not identical. The differences show up in hoisting, naming, this, and in how naturally they fit certain kinds of code.


Function Declarations Stand on Their Own

A function declaration creates a named function directly in the current scope.

function formatPrice(value: number): string {  return `£${value.toFixed(2)}`;}

This style is often easy to scan because the function's identity is explicit and visible immediately.

It also behaves differently with hoisting. Declarations are hoisted in a way that lets you call them before their position in the file.

console.log(formatPrice(10));function formatPrice(value: number): string {  return `£${value.toFixed(2)}`;}

That works.


Function Expressions are Values Assigned to Variables

A function expression creates a function as part of an expression, often by assigning it to a variable.

const formatPrice = function (value: number): string {  return `£${value.toFixed(2)}`;};

The variable itself is hoisted, but not in the same fully callable way as a declaration. If you try to call it before the assignment runs, you will run into trouble.

That is one of the first meaningful differences between declarations and expressions: they may look similar in final form, but their initial timing behaviour differs.


Arrow Functions are a Special Kind of Function Expression

Arrow functions are not a completely separate universe. They are really a more modern shorthand for a function expression, with some important behavioural differences.

const formatPrice = (value: number): string => {  return `£${value.toFixed(2)}`;};

They are especially common in modern frontend code because they:

  • are concise
  • work neatly with callbacks
  • fit well with array methods and component logic

But their biggest difference is not syntax. It is how they handle this.


Hoisting is One of the Practical Differences

Function declarations can be called before they appear in the file because of how hoisting works:

announce();function announce(): void {  console.log('Ready');}

A function expression assigned to a const or let cannot be used before the assignment line has executed:

announce();const announce = (): void => {  console.log('Ready');};

That fails.

This does not automatically make declarations better. It just means the styles make different promises about when they become available.


Arrow functions do not have their own this

This is probably the single most important behavioural difference.

Regular functions get their own this based on how they are called.

Arrow functions do not create a new this; they close over the surrounding one.

That makes arrow functions very useful for callbacks inside methods:

const timer = {  seconds: 0,  start(): void {    setTimeout(() => {      this.seconds += 1;    }, 1000);  },};

Because the arrow function inherits this from start, it keeps the surrounding method context rather than creating a new one.

That is helpful in modern frontend code, but it also means arrow functions are not the right fit for every job. If you need a function with its own dynamic this, a regular function is more appropriate.


Naming Can Be Clearer with Declarations

One quiet advantage of function declarations is that they present a stable name naturally:

function validateCheckoutForm(): boolean {  return true;}

That can read very well in larger files.

Function expressions can be just as readable when assigned to wellnamed constants:

const validateCheckoutForm = (): boolean => {  return true;};

So the readability question is often less about which form is objectively superior and more about how the surrounding code is structured.


Arrow Functions Shine in Callbacks

Callbacks are where arrow functions often feel most natural.

const prices = [10, 20, 30];const doubled = prices.map((price) => {  return price * 2;});

That is a very natural fit because the function is small, local, and expressionoriented.

You could write the same thing with a function expression:

const doubled = prices.map(function (price) {  return price * 2;});

but the arrow version tends to feel cleaner in contemporary code.


Declarations are Still Good for Top‑Level Named Helpers

This is the part sometimes lost in "modern JavaScript" conversations.

Function declarations are not outdated. They are still a perfectly sensible choice for named toplevel helpers, especially when you want the function to read like a stable unit in the file.

For example:

function isValidEmail(value: string): boolean {  return value.includes('@');}

That is direct and readable. There is nothing secondrate about it.


There is No Universal Winner

This is the real lesson.

Use function declarations when:

  • you want a clear named helper
  • hoisting behaviour is acceptable or helpful
  • the function is a toplevel unit in the file

Use function expressions when:

  • you want a function treated as a value explicitly
  • the code style of the module prefers assigned helpers

Use arrow functions when:

  • you are writing short callbacks
  • you want lexical this
  • the function is naturally expressionoriented

The mistake is expecting one form to replace the others entirely.


A Practical Style Rule

If you want a simple rule that works well in modern frontend code, it is often this:

  • declarations for named standalone functions
  • arrow functions for callbacks and many local helpers

That is not the only workable style, but it is a sensible default because it aligns the syntax with the job the function is doing.


Wrapping up

Function declarations, function expressions, and arrow functions all define callable logic, but they differ in timing, naming style, and how they handle this. Declarations are strong for named toplevel helpers, expressions treat functions clearly as values, and arrow functions fit especially well with callbacks and lexical this. The best choice is usually the one that matches the role the function plays in the code.

Key Takeaways

  • Function declarations are hoisted differently and can be called before their position in the file.
  • Function expressions are assigned to variables and are not available in the same way before assignment.
  • Arrow functions are a form of function expression with lexical this.
  • Arrow functions are especially useful in callbacks and local expressionstyle logic.
  • None of the three forms is always correct; the best choice depends on what the function is doing.

Once you understand the tradeoffs, these three styles stop looking like arbitrary syntax choices and start feeling like tools for different kinds of jobs.


Categories:

  1. Development
  2. Front‑End Development
  3. Guides
  4. JavaScript