Why Check If a Function Exists
Runtime errors from calling undefined functions are among the most common JavaScript errors. When you attempt to invoke a function that doesn't exist or hasn't been loaded yet, JavaScript throws a "TypeError: undefined is not a function" error that can crash your application or break critical user interactions.
In modern web development, where applications often load features conditionally, integrate third-party libraries, or run in varied environments, defensive coding patterns become essential. The typeof operator provides a safe, cross-browser compatible way to verify function existence before invocation, preventing these errors and improving overall code reliability. This approach is fundamental to building robust applications as part of a comprehensive web development strategy.
This guide covers the recommended patterns for function existence checking, from basic techniques to advanced scenarios, helping you write resilient JavaScript that gracefully handles different runtime conditions.
The typeof Operator for Function Checking
The typeof operator is the recommended and most widely-supported method for checking if a function exists in JavaScript. Unlike direct property access or variable references that can throw ReferenceError exceptions, typeof safely returns a string indicating the type of its operand, even when that operand hasn't been declared.
The fundamental pattern is straightforward:
if (typeof myFunction === "function") {
myFunction();
}
This approach works consistently across all modern browsers and JavaScript environments, making it the gold standard for function existence checking. The operator returns "function" for callable functions, "undefined" for undefined values, and other type strings for different data types.
One key advantage of typeof is its behavior with undeclared variables. In JavaScript, referencing an undeclared variable normally throws a ReferenceError, but typeof operates safely because it's one of the few operators with temporal dead zone exemption. This makes it ideal for feature detection in code that might run before certain variables are declared.
As documented in the MDN Web Docs on the typeof operator, this operator provides reliable type checking across all JavaScript environments without throwing errors on undefined values.
| Type | Result |
|---|---|
| Undefined | "undefined" |
| Null | "object" |
| Boolean | "boolean" |
| Number | "number" |
| String | "string" |
| Symbol | "symbol" |
| Function | "function" |
| Object | "object" |
Code Examples and Patterns
Basic Function Existence Check
The fundamental pattern for checking if a standalone function exists involves using typeof in a conditional statement before calling the function. This prevents errors when the function hasn't been defined, loaded, or is conditionally available.
// Basic function existence check
if (typeof initializeApp === "function") {
initializeApp();
}
// Checking method existence on objects
if (typeof user.onUpdate === "function") {
user.onUpdate(data);
}
Modern Alternative with Optional Chaining
ES2020 introduced optional chaining (?.), which provides a more concise syntax for the same purpose. When the property before ?. is null or undefined, the entire expression short-circuits to undefined rather than throwing an error.
// Optional chaining approach - clean and modern
myObject.myMethod?.();
// With conditional execution
if (myObject.myMethod?.()) {
console.log("Method executed successfully");
}
// Combining with logical OR for fallback
const handler = myObject.callback?.() || defaultHandler;
Reusable Utility Pattern
For projects that perform function checking frequently, creating a reusable utility function improves consistency and reduces code duplication. This approach also centralizes the logic, making it easier to modify or extend in the future.
// Utility function for function checking
function isFunction(fn) {
return typeof fn === "function";
}
// Usage throughout your codebase
if (isFunction(myCallback)) {
myCallback(result);
}
// Advanced utility with null check
function safeCall(fn, ...args) {
return isFunction(fn) ? fn(...args) : undefined;
}
The Stack Overflow community solution for this pattern has received over 1,600 votes, confirming its status as the widely-accepted best practice for function existence checking in JavaScript.
Common Use Cases
Event Handlers and Callbacks
Event handlers and callbacks are frequently conditionally defined based on user actions, feature flags, or application state. Checking for their existence before invocation ensures graceful handling when handlers aren't set.
// Form submission with optional validation
function handleFormSubmit(event) {
if (typeof beforeSubmit === "function") {
const shouldProceed = beforeSubmit(event);
if (shouldProceed === false) return;
}
// Continue with form submission
}
// Animation completion with optional callback
function animateElement(element, duration) {
element.style.transition = `all ${duration}ms`;
const completeAnimation = () => {
if (typeof onAnimationComplete === "function") {
onAnimationComplete(element);
}
};
element.addEventListener('transitionend', completeAnimation);
}
Plugin and Library Integration
When integrating third-party libraries or plugins, the availability of specific methods can vary based on version, configuration, or feature flags. Function checking provides a safe way to use optional features without causing errors when features are unavailable.
// Using analytics with optional methods
function trackEvent(eventName, properties) {
if (typeof analytics.track === "function") {
analytics.track(eventName, properties);
}
// Fallback tracking
if (typeof console.log === "function") {
console.log("[Analytics]", eventName, properties);
}
}
// jQuery plugin with graceful degradation
function initializeCarousel(container) {
if (typeof jQuery.fn.slick === "function") {
container.slick({
dots: true,
arrows: true
});
} else {
// Fallback to simple scroll
container.classList.add("basic-scroll");
}
}
Conditional Feature Loading
Progressive enhancement patterns rely on feature detection before usage. This approach allows applications to provide rich functionality for capable browsers while maintaining compatibility with older environments. When implementing progressive enhancement strategies, function checking ensures broad compatibility across different browsers and devices.
// Feature detection for modern APIs
function enableRealTimeSync() {
if (typeof BroadcastChannel === "function") {
const channel = new BroadcastChannel('sync');
channel.onmessage = handleSyncMessage;
return channel;
}
// Fallback for older browsers
if (typeof localStorage !== "undefined") {
window.addEventListener('storage', handleStorageEvent);
return null;
}
console.warn("Real-time sync not available");
return null;
}
// Web Workers with existence check
function startBackgroundProcessing() {
if (typeof Worker === "function") {
const worker = new Worker('processor.js');
worker.onmessage = handleWorkerMessage;
return worker;
}
// Fallback to main thread processing
return processOnMainThread();
}
As demonstrated in GeeksforGeeks educational resources, these patterns are essential for building robust JavaScript applications that gracefully handle varying runtime conditions.
Best Practices
Performance Considerations
The typeof operator is highly optimized in modern JavaScript engines, with negligible performance overhead in most scenarios. However, in performance-critical code paths such as animation loops or high-frequency events, repeated type checks can accumulate.
For hot paths, consider caching function references after the initial check:
// Optimized pattern - cache the function reference
let cachedHandler = null;
function getEventHandler() {
if (cachedHandler === null) {
cachedHandler = typeof window.specialHandler === "function"
? window.specialHandler
: defaultHandler;
}
return cachedHandler;
}
// Usage in loop - no repeated typeof check
function handleFrame(frame) {
const handler = getEventHandler();
handler(frame);
}
Code Organization
Establish consistent patterns for function checking across your codebase. Centralized feature detection modules help maintain consistency and make the code easier to understand and maintain.
// feature-detection.js - centralized feature checks
export const features = {
hasWebGL: typeof WebGLRenderingContext === "function",
hasIntersectionObserver: typeof IntersectionObserver === "function",
hasServiceWorker: typeof navigator !== "undefined" && typeof navigator.serviceWorker !== "undefined",
hasPushNotifications: typeof Notification === "function"
};
export function useFeature(featureName, fallback) {
return features[featureName] || fallback;
}
// Usage in application code
import { features, useFeature } from './feature-detection.js';
const processor = useFeature('hasWebGL', cpuProcessor);
Combining with Error Handling
Function checking and try-catch blocks serve complementary purposes. Use function checking for expected missing functionality, and try-catch for unexpected errors during execution.
// Hybrid approach - check first, then wrap execution
async function executeWithLogging(asyncFn, context) {
if (typeof asyncFn !== "function") {
throw new TypeError("Provided argument is not a function");
}
try {
const result = await asyncFn.call(context);
if (typeof onSuccess === "function") {
onSuccess(result);
}
return result;
} catch (error) {
if (typeof onError === "function") {
onError(error);
} else {
console.error("Execution failed:", error);
}
throw error;
}
}
By applying these defensive coding patterns consistently, you can prevent runtime errors and build more resilient web applications that handle varying browser capabilities and feature availability.
Advanced Scenarios
Detecting Specific Function Types
Beyond basic function checking, JavaScript provides ways to identify specific types of callable objects. This is useful when working with async functions, generators, or class constructors.
// Detect async functions
function isAsyncFunction(fn) {
return fn.constructor.name === "AsyncFunction";
}
// Alternative using toString signature
function isAsyncFunctionAlt(fn) {
return Object.prototype.toString.call(fn) === "[object AsyncFunction]";
}
// Detect generator functions
function isGeneratorFunction(fn) {
return fn.constructor.name === "GeneratorFunction";
}
// Comprehensive callable check
function isCallable(fn) {
return typeof fn === "function";
}
// Usage examples
console.log(isAsyncFunction(async function(){})); // true
console.log(isGeneratorFunction(function*(){})); // true
console.log(isCallable(() => {})); // true
Handling Bound Functions
Bound functions created with Function.prototype.bind() maintain their function type after binding. This is important when checking callbacks that may have been pre-bound.
const originalFn = function() { console.log("original"); };
const boundFn = originalFn.bind(null, "preset");
console.log(typeof boundFn === "function"); // true - bound functions remain callable
console.log(boundFn.name); // "bound original"
// Practical example with event handlers
function createHandler(fn) {
if (typeof fn !== "function") {
return function() { console.warn("Handler not defined"); };
}
return fn.bind(null);
}
Checking Constructor Availability
When working with JavaScript classes or constructor functions, you may need to verify their availability before instantiation.
// Checking if a class is available
function createWidget(WidgetClass, container) {
if (typeof WidgetClass === "function") {
return new WidgetClass({ container });
}
console.warn("Widget class not available");
return null;
}
// Feature detection for Web APIs
function isApiAvailable(apiName, constructorName) {
if (typeof apiName === "undefined") {
return false;
}
if (constructorName && typeof apiName[constructorName] !== "function") {
return false;
}
return true;
}
// Example usage
const webGLAvailable = isApiAvailable("WebGLRenderingContext", "prototype");
These advanced patterns extend the basic function checking concepts to handle more complex scenarios in modern JavaScript applications, from detecting specific function types to safely instantiating classes based on availability.
Summary
Checking if a function exists before calling it is a fundamental defensive programming pattern in JavaScript that prevents runtime errors and improves application reliability. The typeof func === "function" pattern remains the gold standard for function existence checking due to its cross-browser compatibility, safety with undeclared variables, and straightforward semantics.
For modern JavaScript projects, optional chaining (?.) provides a more concise alternative that achieves the same result with cleaner syntax. Both approaches have their place depending on your browser support requirements and coding style preferences.
Key takeaways from this guide:
-
Use typeof for maximum compatibility - The
typeofoperator works reliably across all JavaScript environments and safely handles undeclared variables. -
Consider optional chaining for modern code - The
?.operator offers cleaner syntax when targeting ES2020+ environments. -
Create utility functions for consistency - Centralized function checking utilities improve code maintainability and reduce duplication.
-
Combine with error handling strategically - Function checking and try-catch serve complementary purposes in robust error handling.
-
Cache function references in hot paths - For frequently called code, caching function references after the initial check avoids repeated type checks.
By consistently applying these patterns throughout your codebase, you'll prevent common runtime errors and build more resilient JavaScript applications that gracefully handle varying runtime conditions. For more insights on building robust web applications, explore our web development services and related resources like our guide on CSS Grid layout techniques.