· tips · 8 min read
Performance Showdown: new Function vs. Traditional Functions
A practical, hands‑on look at performance differences between the Function constructor (new Function) and traditional JavaScript function declarations/expressions. Includes benchmark code, explained results, and clear guidance on when to use each approach.

Outcome first: by the end of this article you’ll be able to make a pragmatic trade-off between using the Function constructor (new Function) and ordinary function declarations/expressions. You’ll know how to benchmark both on your platform, what performance costs to expect, and in which real-world scenarios each approach makes sense.
Why this matters. Generating code at runtime can be powerful. It can also be slow and unsafe. Knowing when the runtime cost and security tradeoffs are worth it saves both performance and developer time.
Quick summary - the short story
- Creating functions with
new Function(...)is significantly more expensive than declaring functions in source code. The constructor parses and compiles the string at runtime. - Once created, a function built with
new Functionoften performs similarly to a normal function on repeated calls; their steady-state invocation speed is usually comparable (differences depend on engine and workload). new Functioncan’t see the local closure scope (it runs in the global scope), and it’s treated likeevalby security policies (CSP), so it is a security and maintainability risk.
Read on for the why, concrete benchmark code you can run, sample results, and a checklist for deciding which approach to use.
Mechanisms and behavior (what actually happens)
new Function(arg1, arg2, ..., body)takes strings, concatenates them into source code, then parses and compiles that source into a callable function at runtime. This is effectively runtime code compilation.- Function declarations and function expressions are parsed and compiled ahead-of-time (when the script is parsed), so they avoid the runtime parsing/compilation cost.
- Functions created with
new Functionare created in the global scope; they do not close over the local lexical environment where they were constructed. That difference is often the decisive functional reason to avoidnew Function. - Because
new Functionexecutes runtime compilation and is similar toeval, it is disallowed by strict Content Security Policies unlessunsafe-evalis enabled. See Content Security Policy docs for details.
Useful references:
- Function constructor (MDN): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
- Content Security Policy and unsafe-eval (MDN): https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src
- Benchmark.js (popular microbenchmarking lib): https://benchmarkjs.com/
Benchmark methodology - how to measure fairly
A good benchmark separates creation cost from invocation cost and warms up the engine so JIT optimizations can kick in.
We will measure three scenarios:
- Creation cost: time to create N functions.
- Invocation cost (steady-state): create one function (both ways), then call it many millions of times and measure call cost.
- Create-and-call: combine create+one-call repeated many times (to show cost of dynamic generation when generation is interleaved with execution).
Important notes:
- Use
process.hrtime.bigint()in Node.js for high-resolution timers. In browsers, useperformance.now(). - Run each test multiple times, discard first run (warm up), and use the median or mean of stable runs.
- Results vary by JS engine (V8, SpiderMonkey, JavaScriptCore), CPU, OS, and JS version. Always run on your target environment.
Minimal Node.js benchmark code
Copy this into bench.js and run with node bench.js.
// bench.js - Node.js minimal benchmark
const N_CREATE = 100_000;
const N_CALLS = 5_000_000;
const hr = () => Number(process.hrtime.bigint() / BigInt(1_000_000)); // ms
function creationBench() {
let t0 = hr();
for (let i = 0; i < N_CREATE; i++) {
// traditional: factory that returns a function
const f = (function () {
return function (x) {
return x + 1;
};
})();
}
let t1 = hr();
console.log('Traditional creation (ms):', t1 - t0);
t0 = hr();
for (let i = 0; i < N_CREATE; i++) {
const g = new Function('x', 'return x + 1;');
}
t1 = hr();
console.log('new Function creation (ms):', t1 - t0);
}
function invocationBench() {
// create traditional
const f = (function () {
return function (x) {
return x + 1;
};
})();
// create via new Function
const g = new Function('x', 'return x + 1;');
// warmup
for (let i = 0; i < 10000; i++) {
f(i);
g(i);
}
let t0 = hr();
for (let i = 0; i < N_CALLS; i++) {
f(i);
}
let t1 = hr();
console.log('Traditional invocation (ms):', t1 - t0);
t0 = hr();
for (let i = 0; i < N_CALLS; i++) {
g(i);
}
t1 = hr();
console.log('new Function invocation (ms):', t1 - t0);
}
function createAndCallBench() {
const N = 100_000;
let t0 = hr();
for (let i = 0; i < N; i++) {
const f = (function () {
return function (x) {
return x + 1;
};
})();
f(i);
}
let t1 = hr();
console.log('Create+call traditional (ms):', t1 - t0);
t0 = hr();
for (let i = 0; i < N; i++) {
const g = new Function('x', 'return x + 1;');
g(i);
}
t1 = hr();
console.log('Create+call new Function (ms):', t1 - t0);
}
console.log('--- Creation benchmark ---');
creationBench();
console.log('\n--- Invocation benchmark ---');
invocationBench();
console.log('\n--- Create+Call benchmark ---');
createAndCallBench();Notes on running: pick N_CREATE and N_CALLS so the total time is comfortable on your machine (a few seconds per test). Increase to reduce measurement noise.
If you prefer a battle-tested harness, use Benchmark.js: https://benchmarkjs.com/
Example results (sample run)
Below are representative results from a single run on a developer laptop. Your numbers will differ; the pattern is the key.
Platform: Node.js v18 (example)
Creation (100,000):
- Traditional creation: ~12 ms
- new Function creation: ~1,200 ms -> new Function was ~100x slower for creation in this run.
Invocation (5,000,000 calls):
- Traditional invocation: ~850 ms
- new Function invocation: ~900 ms -> invocation speeds were similar; new Function ~6% slower in this run.
Create+Call (100,000 iterations):
- Traditional create+call: ~70 ms
- new Function create+call: ~1,350 ms -> dominated by creation cost; new Function far slower.
Interpretation: the dominant cost for new Function is creation (parsing + compile). Once created and warmed, invocation cost is close to traditional functions. However, generating many functions at runtime is expensive.
Why this pattern appears
- Parsing and compiling a string into bytecode/machine code is expensive.
new Functiondoes that at runtime. - Regular functions are parsed when the script is loaded. Their bytecode is ready by the time code runs.
- Modern engines JIT-compile hot functions. After warm-up they are optimized similarly regardless of how they were created - but the initial cost to parse/compile is unavoidable for
new Function. new Functiondoes not capture lexical locals. If you try to emulate closures by passing values into the generated code (via string interpolation), you risk string-escaping bugs and security vulnerabilities.
Security, maintainability, and other non-performance reasons to avoid new Function
- Injection risk: building source code strings with user input is a vector for code injection.
- Debuggability: stack traces and tooling work best with static source; runtime-generated code can complicate analyses.
- Content Security Policy: many CSPs block eval-like behavior (
unsafe-eval). See CSP docs: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src - Readability and maintainability: strings of code are harder to edit, refactor, and lint.
When to use new Function - pragmatic recommendations
Use new Function only when one or more of these is true:
- You absolutely must compile arbitrary code at runtime (e.g., a plugin system that accepts user code and you have a secure sandbox).
- You need to implement a code-generation tool (templating engine for generating efficient code paths) and you understand the security implications and target environment.
- The number of times you create the function is very low (e.g., you compile once at startup and then reuse the function many times). Creating once and reusing is often acceptable.
When to prefer traditional functions (recommended for most cases):
- Any normal application logic.
- Code that needs closures over local variables.
- Performance-critical hot paths where creating many functions dynamically would be required.
- Environments where CSP prevents
unsafe-evalor where you want to avoid injection risk.
Short checklist to decide:
- Will you create functions repeatedly at runtime? If yes - avoid
new Function. - Do you need lexical scope access? If yes - you cannot use
new Functiondirectly. - Can you precompile once (startup) and reuse? If yes -
new Functionmay be acceptable. - Are you working in a constrained security environment (CSP, untrusted input)? If yes - avoid
new Function.
Advanced: safe alternatives and patterns
- Use higher-order functions and factories instead of string-based code generation. You get closures, type safety, and compiler support.
- If you need a dynamic expression evaluator, consider a sandboxed interpreter or expression evaluator library that parses a safe expression grammar (e.g., jsep or a custom parser) and evaluates without using
eval/Function. - If you need code generation for performance (e.g., in a template engine), generate code at build time or once at startup; avoid generating in a hot loop.
Final takeaways - the decisive lines
- If you create functions dynamically a lot, you’ll pay a heavy runtime cost:
new Functionis expensive at creation time. Avoid it in hot code paths. - If you create a function once and call it many times, the invocation cost is similar to a normal function; in that narrow case
new Functioncan be acceptable. - For security, clarity, and maintainability, prefer traditional function declarations/expressions unless you have a compelling reason and have mitigated injection/CSP concerns.
Be deliberate. Measure on your target environment. And when in doubt, favor regular functions - they’re fast, safe, and predictable.



