· tips · 7 min read
Unleashing Dynamic Code: Understanding New Function in JavaScript
Explore how the Function constructor (new Function) lets you create JavaScript functions at runtime. Learn syntax, practical use cases, scope implications, performance and security trade-offs, and safer patterns for dynamic code generation.

Why dynamic functions?
JavaScript is a dynamic language - but sometimes you need to generate executable code at runtime. The Function
constructor (commonly used as new Function(...)
) makes that possible by building a function from strings. This can simplify certain problems (generating specialized code, compiling domain-specific expressions, producing fast accessors) - but it also introduces important security, debugging, and performance considerations.
This article walks through the syntax, examples, scope behavior, performance implications, security caveats, and practical patterns to use new Function
safely and efficiently.
Syntax and basic examples
The Function
constructor signature accepts zero or more parameter-name strings followed by a final string that becomes the function body:
// create function f(a, b) { return a + b; }
const f = new Function('a', 'b', 'return a + b;');
console.log(f(2, 3)); // 5
You can also omit parameter names and pass only the body string: new Function('return 42')
.
Examples:
- Simple adder
const add = new Function('x', 'y', 'return x + y;');
console.log(add(1, 2)); // 3
- Using
use strict
inside the generated function
const strictFn = new Function('a', 'b', '"use strict"; return a === b;');
- With modern syntax: rest parameters work because the constructor parses parameter list (engines vary but modern browsers support ES6 syntax here)
const sum = new Function('...args', 'return args.reduce((s,x)=>s+x,0);');
console.log(sum(1, 2, 3)); // 6
(If you rely on newer syntax in generated code, make sure your target JS environment supports it.)
How scope works - no local closures
A critical detail: functions created with new Function
do not close over the local lexical scope where they are created. Their parent scope is the global scope (or module/global lexical environment). That means they cannot access local variables from the outer function.
function outer() {
const secret = 42;
const f = new Function(
'return typeof secret !== "undefined" ? secret : "no access";'
);
return f();
}
console.log(outer()); // "no access"
If you need the generated function to operate on values from the outer scope, you must either:
- interpolate the values into the generated source (carefully) or
- pass them as parameters when you call the generated function or
- generate a closure-producing factory (a compiled factory returns a closure that wraps the generated function).
Example: safe accessor factory (interpolating property name as a string literal):
function makeAccessor(propName) {
// JSON.stringify makes the inserted value a safe string literal
const p = JSON.stringify(propName);
return new Function('obj', `return obj[${p}];`);
}
const getId = makeAccessor('id');
console.log(getId({ id: 7 })); // 7
Common use cases and patterns
- Specialized hot-path functions (code generation for speed)
Generate a specialized function for a frequently-run operation to reduce polymorphism and give the engine a simple, monomorphic target to optimize.
// comparator generator (cached)
const compCache = new Map();
function comparator(property) {
if (compCache.has(property)) return compCache.get(property);
// access property with bracket syntax and a string literal to avoid injection
const fn = new Function(
'a',
'b',
`return a[${JSON.stringify(property)}] - b[${JSON.stringify(property)}];`
);
compCache.set(property, fn);
return fn;
}
- Expression evaluation (careful!)
When you have user-defined formulas or expressions, new Function
can compile them to executable code. But this is only appropriate for trusted or sanitized inputs. Safer approach: parse to an AST and interpret or generate code from a restricted AST.
// quick-and-dirty (only for trusted input!)
function compileExpr(expr) {
return new Function('context', `with (context) { return (${expr}); }`);
}
const fn = compileExpr('x * y + Math.max(z, 0)');
console.log(fn({ x: 2, y: 3, z: -1 })); // 6
- Template engines / code generation
Concise template engines sometimes compile templates to JavaScript functions to produce output quickly. Cache compiled templates and treat user content as data, not code.
- Dynamic accessors, serializers, and mappers
Emit functions to read/write properties quickly (especially when dealing with many objects of known shape). The generated code can be tiny and very fast.
- Building DSL compilers
If you control the DSL, compiling DSL expressions to JavaScript functions and caching compiled forms is a pragmatic approach.
Security and Content Security Policy (CSP)
new Function
is effectively the same family as eval
when it comes to executing string-supplied code. That creates two immediate concerns:
Injection: If you interpolate or execute untrusted input, you open up arbitrary code execution. Avoid interpolating user strings directly into generated function bodies. Use safe serialization (e.g.
JSON.stringify
), whitelisting, or prefer parsing to an AST and running a safe interpreter.CSP: Many Content Security Policies disable
eval
-like features (by disallowingunsafe-eval
). If your web app runs under such CSP,new Function
will be blocked unlessunsafe-eval
is explicitly allowed. See the CSP docs for details.
References:
- MDN on the Function constructor and security considerations: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function#security_considerations
- CSP (Content Security Policy) overview: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
Safer alternatives:
- Use an expression parser library (e.g., [jsep] or [mathjs]) and interpret or compile from a sanitized AST.
- Build a small, opinionated DSL that doesn’t allow arbitrary JavaScript.
Debugging and tooling
Functions created from strings lose their original source filename and line information in stack traces, often appearing as anonymous code. You can improve debugging by appending a sourceURL-like comment to the generated code (supported by some browsers/devtools):
const body = 'return x + y;\n//# sourceURL=generatedAdd.js';
const add = new Function('x', 'y', body);
Not all environments honor sourceURL, and production tooling may minify/obfuscate code - keep this in mind.
Performance considerations
Creation cost: Parsing and compiling a function from a string is more expensive than creating a closure from existing code. Avoid generating functions repeatedly inside hot loops.
Execution cost: Once created, the generated function behaves like any other function and can be JIT-compiled and optimized by the engine. Generating a single specialized function and reusing it can be faster than running a generic but heavily-branching function.
Optimization pitfalls: Engines optimize best when code shapes are consistent (monomorphic). Dynamically generating many slightly different functions can help the engine produce specialized machine code - but generating too many at runtime can blow CPU and memory. Always cache compiled functions when possible.
Practical guidance:
- Cache results of
new Function
keyed by the original expression or parameters. - Avoid generating functions inside innermost loops; if unavoidable, pre-generate or memoize.
- Benchmark for your target environment; VMs (V8, SpiderMonkey, JavaScriptCore) have different heuristics.
Alternatives to new Function
Regular closures: Most tasks are accomplished by closures and higher-order functions.
Template engines or precompiled templates: Compile templates at build time when possible.
AST-based execution: Use a parser to produce an AST and then interpret the AST or convert it to safe code. Libraries: [jsep], [mathjs].
WebAssembly for compute-heavy, performance-critical workloads: compile to WASM where appropriate.
Proxy and Reflect for dynamic property behavior without code generation.
Best practices checklist
- Only use
new Function
with trusted/sanitized input. - Prefer whitelisting/parsing over raw string execution.
- Always cache generated functions where repeated use is expected.
- Use
JSON.stringify
for interpolating literal values into generated source to avoid injection. - Be mindful of CSP -
unsafe-eval
may be required (usually undesirable). - Add
"use strict";
at top of the body if you rely on strict mode semantics. - Test and benchmark under the target JS engine.
Example: robust factory with caching
const factoryCache = new Map();
function compileAccessor(prop) {
if (factoryCache.has(prop)) return factoryCache.get(prop);
const body = `return obj[${JSON.stringify(prop)}];`;
const fn = new Function('obj', body);
factoryCache.set(prop, fn);
return fn;
}
When to reach for new Function
Reach for new Function
when:
- You control the inputs (or they are sanitized/whitelisted) and need runtime compilation for performance or expressiveness.
- You need to compile many different specialized behaviors and caching is possible.
Avoid new Function
when:
- Inputs come directly from untrusted users.
- CSP disallows
unsafe-eval
. - A simpler closure or AST interpreter does the job.
Summary
new Function
is a powerful tool for generating code at runtime. It enables dynamic, specialized functions that can be both expressive and performant - when used carefully. However, it carries clear security and deployment trade-offs (injection risk and CSP incompatibility) and a runtime parsing cost. Use it with caution, prefer safe interpolation and caching, and fall back to safer alternatives (AST interpretation, precompilation) when user input or security policies are a concern.
References
- MDN - Function constructor: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
- MDN - Strict mode: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode
- MDN - Content Security Policy (CSP): https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- mathjs (example of a math expression library): https://mathjs.org/