The "Function is not defined ReferenceError" is one of the most common JavaScript errors, hitting developers at every skill level. You're working through a feature, and suddenly your code throws: ReferenceError: myFunction is not defined. This error occurs when JavaScript tries to execute a function that either doesn't exist, exists in the wrong scope, or hasn't been loaded yet. In production, timing and environment differences can mask the root cause — making proper error tracking and visibility critical.
This guide walks through the main causes and how to fix them, so you can prevent "Function is not defined ReferenceError" errors both locally and in production.
Hoisting and Function Declarations
JavaScript hoisting is the first concept to understand. When JavaScript parses your code, function declarations are hoisted to the top of their scope before any code runs — but function expressions and arrow functions are not.
// This works — functions are hoisted
console.log(greet("Alice")); // "Hello, Alice"
function greet(name) {
return `Hello, ${name}`;
}
But this fails:
// This throws: ReferenceError: sayHi is not defined
console.log(sayHi("Bob")); // Error!
const sayHi = (name) => `Hi, ${name}`;
The distinction matters. Function declarations (the function keyword) are moved to the top of their scope during the parsing phase, so they're available even before the line that defines them. Function expressions (const, let, or var with =) are not hoisted — the variable exists, but its value is undefined until the code actually reaches that line. How to read a stack trace will help you identify exactly where these errors occur in your codebase.
Quick fix: Use function declarations for functions you need early, or reorganize your code so function expressions are defined before they're called.
Scope and Variable Access
JavaScript scope creates invisible boundaries. A function only exists in the scope where it's declared and in any child scopes (closures). Define a function inside an if block or within another function, and it won't be accessible outside that block.
if (true) {
function onlyHereInIf() {
return "scoped";
}
}
// This throws: ReferenceError: onlyHereInIf is not defined
onlyHereInIf();
The same applies to functions defined in modules, iframes, or other execution contexts:
// file1.js
function helperFunc() {
return "help";
}
// file2.js
console.log(helperFunc()); // ReferenceError if file2 doesn't import file1
Scope is a common pain point in modern SPAs — Webpack, TypeScript, and module systems enforce scoping, so a function exported from one module won't be visible in another unless explicitly imported. This is actually a safety feature, but it means you must be intentional about what you expose.
Quick fix: Move the function declaration to the right scope level, or use module imports and exports to make it available where needed.
Script Loading Order
In the browser, if you load scripts in the wrong order, functions defined in later scripts won't exist when earlier scripts run.
<!-- index.html -->
<script src="main.js"></script> <!-- Calls setupUI() -->
<script src="utils.js"></script> <!-- Defines setupUI() -->
When main.js loads, utils.js hasn't run yet, so setupUI() doesn't exist. This problem multiplies when you have inline scripts mixed with external files or when a CSS framework or utility library is expected but doesn't load in time.
Script order matters. Load dependent scripts after the functions they depend on are defined. Using module bundling (Webpack, Vite, esbuild) handles dependency order automatically, which is one reason modern tooling is valuable.
Quick fix: Reorder your <script> tags so dependencies load first, or migrate to a module bundler that handles dependencies for you.
Lazy Loading and Async Code
Modern applications often load code on-demand using dynamic imports. If a function is defined in a lazy-loaded chunk and you call it before the chunk loads, you'll hit this error.
// app.js
let reportPage;
if (showAnalytics) {
import("./analytics.js").then(({ reportPage: rp }) => {
reportPage = rp;
});
}
// If showAnalytics was false, reportPage is still undefined here
// and calling it throws ReferenceError
function onPageView() {
if (reportPage) {
reportPage(); // Safe fallback
}
}
Lazy loading is powerful for performance, but it decouples definition from use. A function can exist in your codebase but not be loaded in the current runtime until a user triggers its import. Library code, route handlers, and analytics modules are common culprits.
Quick fix: Always guard lazy-loaded function calls with checks that the chunk has loaded, or use try-catch blocks with fallbacks to handle missing functions gracefully.
Best Practices to Prevent the Error
Here are patterns that eliminate most "function is not defined" issues:
-
Use function declarations at the module level — keep code readable and hoisting predictable:
function processData(data) { return data.map(x => x * 2); } -
Define functions before you use them — especially for arrow functions and expressions:
const validate = (input) => input.length > 0; console.log(validate("test")); // Always safe -
Use ES modules — let your bundler handle dependency order:
// utils.js export function sum(a, b) { return a + b; } // app.js import { sum } from "./utils.js"; console.log(sum(1, 2)); // Always defined -
Avoid defining functions conditionally — move them to the top level and use conditionals to call them:
// Avoid if (userRole === "admin") { function deleteAll() { /* ... */ } } // Better function deleteAll() { /* ... */ } if (userRole === "admin") { deleteAll(); }
Catching It in Production
These errors are easy to miss locally because your development environment loads code in a predictable, controlled order. In production, network delays, user geography, and browser caching can expose loading-order bugs that never appear on your machine. Error tracking best practices emphasize the importance of seeing errors as they actually occur.
Set up error tracking so you're notified when "Function is not defined" errors spike in production. You'll see the exact line of code, the user's browser, the event sequence leading up to the error, and have real stack context to debug production errors effectively. This transforms a frustrating guessing game into a focused investigation.
When you capture these errors, look for patterns: Do they cluster after a deploy? Do they affect only certain browsers or geographic regions? These clues often point to lazy-loading timing, script-ordering, or CDN issues — and they're invisible without production error tracking.
Fixing "Function is not defined ReferenceError" comes down to understanding hoisting, scope, and load order. Master these concepts, follow the prevention patterns above, and you'll eliminate the error from most of your codebase — and catch what slips through with real monitoring.
Start tracking errors in minutes
See errors as they happen in production — trace hoisting, scope, and loading issues before they affect users. Start free with LightTrace.