Wrapper function
A wrapper function is a function that wraps another function or a piece of code in order to add extra behaviour, control, or a simpler call. Put simply, it is a layer around a function that breaks nothing but adds something: logging, validation, error handling, caching and so on.
Theory
TL;DR
- A wrapper takes a function and returns a new function that calls the original inside itself.
- It is a higher-order function, the classic way to add cross-cutting behaviour.
- The original function is not modified, so it can still be used without the wrapper.
- Typical jobs: logging, metrics,
try/catch, permission checks, caching, throttling. - The same pattern underlies decorators and middleware.
Quick example
function greet(name) {
return `Hello, ${name}!`;
}
function withLogging(fn) {
return function (...args) {
console.log(`Calling ${fn.name} with arguments:`, args);
const result = fn(...args);
console.log('Result:', result);
return result;
};
}
const loggedGreet = withLogging(greet);
loggedGreet('Maria');Output:
Calling greet with arguments: [ 'Maria' ]
Result: Hello, Maria!Here withLogging is the wrapper function that adds extra behaviour on top of the plain greet function.
The core idea
You take an existing function or operation and create a new one that calls it inside itself, but with something extra around the call. For example:
- add logging before the call;
- measure execution time;
- check the arguments;
- handle errors with
try/catch.
To stay transparent, the wrapper should accept ...args, return the original's result and, when a correct this matters, call the function through fn.apply(this, args).
Error handling
function riskyOperation() {
throw new Error('Something went wrong!');
}
function safeWrapper(fn) {
return function (...args) {
try {
return fn(...args);
} catch (err) {
console.error('Error while calling the function:', err.message);
return null;
}
};
}
const safeOp = safeWrapper(riskyOperation);
safeOp(); // Error while calling the function: Something went wrong!Now even if the inner function throws, the surrounding code keeps working.
Measuring execution time
function heavyTask() {
for (let i = 0; i < 1e7; i++) {}
}
function measureTime(fn) {
return function (...args) {
const start = performance.now();
fn(...args);
const end = performance.now();
console.log(`Execution time: ${(end - start).toFixed(2)}ms`);
};
}
const measuredTask = measureTime(heavyTask);
measuredTask(); // Execution time: 12.34msAn asynchronous wrapper
For an async function the wrapper has to be asynchronous too, otherwise try/catch will not catch a rejected promise.
async function fetchData() {
const res = await fetch('https://api.example.com/data');
return res.json();
}
function withErrorHandling(fn) {
return async function (...args) {
try {
return await fn(...args);
} catch (e) {
console.error('Request failed:', e);
return null;
}
};
}
const safeFetch = withErrorHandling(fetchData);
safeFetch();A wrapper as a decorator: permission checks
function authWrapper(fn) {
return function (user, ...args) {
if (!user.isAdmin) {
throw new Error('Access denied');
}
return fn(user, ...args);
};
}
function deletePost(user, postId) {
console.log(`Post ${postId} deleted`);
}
const deletePostWithAuth = authWrapper(deletePost);
deletePostWithAuth({ isAdmin: true }, 42); // Post 42 deleted
deletePostWithAuth({ isAdmin: false }, 42); // Error: Access deniedWhy wrapper functions are useful
| Goal | Example |
|---|---|
| Extending functionality | Add logging, metrics, caching |
| Security | Check access rights or a token |
| Error handling | Wrap risky operations in try/catch |
| Reuse | Build one behaviour template for many functions |
| Functional programming | Use higher-order functions |
Summary table
| Property | Description |
|---|---|
| What it is | A function that calls another one inside itself |
| Goal | Add or change behaviour without touching the original |
| Type | Higher-order function |
| Typical examples | Logging, caching, validation, try/catch, metrics |
| Benefits | Reusable, safe, readable |
Common mistakes
- Not returning the original function's result: the wrapper swallows the value and the call suddenly yields
undefined. - Wrapping an
asyncfunction with a synchronous wrapper: the error becomes an unhandled promise rejection andtry/catchstays silent. - Losing
this: when wrapping an object's method, callfn.apply(this, args)instead offn(...args). - Forgetting that the new function has a different
fn.nameandfn.length, so code that reads the name or the arity breaks. - Stacking wrappers without restraint: five layers around one function make the stack trace unreadable and hide where the error really came from.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.