cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

eval & new Function

Learn what eval and new Function do with code strings, how direct and indirect eval choose scope, why CSP can block them, and safer patterns to reach for instead.

By the end you can
  • 01
    Explain the scope ruleTell direct eval, indirect eval, and new Function apart.
  • 02
    Spot the dangerDescribe why code strings are a security and CSP problem.
  • 03
    Choose safer toolsReplace data, property, and parsing eval patterns with ordinary APIs.

Strings are not programs until you compile them

JavaScript normally runs source code that came from a file, a module, or a script tag. eval and new Function are different: they take text while the program is already running and ask the engine to parse that text as JavaScript.

That sounds flexible, but it changes the rules around scope, closures, strict mode, browser security, and performance. This lesson shows the real behavior, then spends most of its time on the more useful skill: replacing code strings with ordinary data and functions.

The short rule

Treat code strings like hazardous material. If you only need data, parse data. If you need to choose behavior, choose from functions you already wrote.

Real-life analogyeval is a stranger's note in your room

Direct eval is like handing a stranger a note and saying, “do whatever this says, with full access to this room.” Indirect eval is the same note read in the building lobby: it can use lobby signs, but not the private labels on your desk.

In real life: The room you are standing in
In JavaScript: The current function scope
In real life: A note that says what to do
In JavaScript: The string passed to eval
In real life: Reading it inside the room
In JavaScript: Direct eval(...) can see local variables
In real life: Reading it in the lobby
In JavaScript: Indirect eval sees only global names

Where the analogy stops: A real note cannot magically create a variable or read a closure. JavaScript code can, depending on the eval form, so the analogy is only about trust and location.

Direct vs indirect eval

STEP THROUGH

A call is direct eval only when the source is exactly eval(...) and the name still refers to the original global eval function. Direct eval runs in the caller’s scope, so the string can read local variables. Anything else is indirect eval: an alias, a comma expression, optional chaining, or a property access. Indirect eval runs as global code.

Replay scope lookup for eval
Step 0 of 4Ready
Your turn: follow the blue line

Pick a mode, predict which scope supplies x, then step through the model. This is a guided replay, not an engine debugger.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  const x = "local x";  return eval("x");}console.log(readX());
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the call shape
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

The distinction is intentionally narrow. (0, eval)("x") looks silly, but it is a common way to force indirect eval because the comma expression returns the function value instead of making a direct call through the identifier eval.

Real browser lab: predefined eval snippets
Sandbox runnerPop out in the code editor (opens in a new tab)JavaScript
const cases = {  direct: 'function read(){ const x = "local x"; return eval("x"); } read();',  indirect: 'globalThis.x = "global x"; function read(){ const x = "local x"; return (0, eval)("x"); } read();',  sloppyVar: 'function read(){ eval("var leaked = 1"); return typeof leaked; } read();',  strictVar: 'function read(){ "use strict"; eval("var hidden = 1"); return typeof hidden; } read();'};addEventListener("message", (event) => {  if (!event.data || event.data.lesson !== "eval" || !(event.data.caseId in cases)) return;  try {    const value = eval(cases[event.data.caseId]);    parent.postMessage({ lesson: "eval", caseId: event.data.caseId, status: "ok", value: String(value) }, "*");  } catch (error) {    parent.postMessage({ lesson: "eval", caseId: event.data.caseId, status: "error", value: error.name + ": " + error.message }, "*");  }});
Sandboxed iframepredefined cases

Preparing the sandboxed frame…

casenot run yet
statuswaiting
valuechoose a snippet
Try it yourself

The parent sends only a case ID. The sandbox contains the fixed snippets and reports back after the parent validates event.source. There is no learner-typed code path.

The iframe is sandboxed with allow-scripts and no allow-same-origin, so its origin is opaque.

Declarations inside eval

BROWSER LAB

Reading variables is only half the story. A sloppy direct eval can also create var bindings in the surrounding variable environment. Strict direct eval is safer: it gets its own variable environment, so var declarations inside the string do not leak out.

The two var cases in the labPop out in the code editor (opens in a new tab)JavaScript
function sloppy() {  eval("var leaked = 1");  return typeof leaked;}function strict() {  "use strict";  eval("var hidden = 1");  return typeof hidden;}

In the iframe lab above, the sloppy case reports "number" because leaked exists after eval. The strict case reports "undefined" because hidden stayed inside eval. Modern code should be strict, but the best answer is still to avoid the code string.

A tiny edge case

eval only compiles strings. eval(42) returns 42, and eval(object) returns the same object unchanged.

new Function builds a fresh global-scope function

INTERACTIVE
Real-life analogynew Function is a vending machine in the lobby

The Function constructor builds a brand-new function from text. It can use the parameters you feed it and any global names in the lobby, but it never knows the local variables in the room where you created it.

In real life: Buttons for ingredients
In JavaScript: Parameter names like a and b
In real life: A recipe printed by the machine
In JavaScript: The function body string
In real life: The building lobby
In JavaScript: Global scope
In real life: Locked offices upstairs
In JavaScript: Local variables and closures it cannot see

Where the analogy stops: A vending machine cannot execute arbitrary instructions; new Function can. The analogy is about receiving inputs and knowing only the lobby.

The scope rule for new FunctionPop out in the code editor (opens in a new tab)JavaScript
globalThis.shared = 7;const add = new Function("a", "b", "return a + b");function makeSecretReader() {  const secret = 10;  return new Function("return secret");}const readSecret = makeSecretReader();const readShared = new Function("return shared");console.log(add(2, 3));try { console.log(readSecret()); }catch (error) { console.log(error.name); }console.log(readShared());
new Function lab: arguments, closures, globals
Function constructor casesPop out in the code editor (opens in a new tab)JavaScript
const cases = {  add: () => new Function("a", "b", "return a + b")(2, 3),  closure: () => { const secret = 10; return new Function("return secret")(); },  global: () => { globalThis.shared = 7; return new Function("return shared")(); }};addEventListener("message", (event) => {  if (!event.data || event.data.lesson !== "eval-new-function" || !(event.data.caseId in cases)) return;  try { parent.postMessage({ lesson: "eval-new-function", caseId: event.data.caseId, status: "ok", value: String(cases[event.data.caseId]()) }, "*"); }  catch (error) { parent.postMessage({ lesson: "eval-new-function", caseId: event.data.caseId, status: "error", value: error.name + ": " + error.message }, "*"); }});
Sandbox resultglobal-only body

Preparing the sandboxed frame…

casenot run yet
statuswaiting
valuechoose a case
Try it yourself

new Function can use its parameters and globals. It cannot see the local secret where the function was constructed.

Again, the generated functions live only inside the sandboxed iframe.

The body is sloppy unless the body string starts with "use strict". Like eval, new Function is blocked by a browser CSP that omits unsafe-eval.

Security and Content Security Policy

BROWSER LAB

The biggest practical problem is security. If untrusted text reaches eval, the attacker is no longer choosing a value; they are choosing instructions. That is why many production sites use a Content Security Policy that does not include unsafe-eval.

Real-life analogyCSP is the building rule

A CSP without unsafe-eval is like a building rule that forbids reading instructions from notes at all. It blocks friendly notes and hostile notes the same way.

In real life: A rule at the front desk
In JavaScript: Content Security Policy
In real life: No reading instruction notes
In JavaScript: No unsafe-eval
In real life: A guard refusing the note
In JavaScript: Browser throws EvalError

Where the analogy stops: CSP is not a human guard and it does not understand your intent. It applies mechanical rules to script execution and string compilation.

CSP lab: unsafe-eval on and off
Same eval call, different CSPPop out in the code editor (opens in a new tab)HTML
<meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-inline'"><script>try {  const value = eval("21 * 2");  parent.postMessage({ lesson: "eval-csp", status: "ok", value: String(value) }, "*");} catch (error) {  parent.postMessage({ lesson: "eval-csp", status: "error", value: error.name + ": " + error.message }, "*");}</script>
Two sandboxed framesCSP controls eval

Preparing the sandboxed frame…

frame policyeval result
with unsafe-evalwaiting
without unsafe-evalwaiting
Try it yourself
Reset rebuilds both frames with the same code.

Both frames allow their inline script to start. Only the policy with unsafe-eval lets the eval call compile its string. Chrome throws an EvalError when the other frame tries.

Message wording is browser-specific; rely on the error type and the fact that string compilation was blocked.

In Chrome, the blocked frame throws an EvalError. The exact message text is browser-specific, so production code should not depend on the wording. The same policy also blocks new Function and string arguments to setTimeout or setInterval.

Safer alternatives

SORTER

Most eval requests are really one of three ordinary tasks: parse data, choose a known operation, or convert text into a simple value. None of those needs JavaScript source code.

Data and choices, not code stringsPop out in the code editor (opens in a new tab)JavaScript
const data = JSON.parse('{"score":42}');const formatters = { upper: (text) => text.toUpperCase() };const key = "upper";const page = new URLSearchParams("?page=2");console.log(data.score);console.log(formatters[key]("safe"));console.log(Number(page.get("page")));
  • Use JSON with JSON.parse for data from APIs or storage.
  • Use an object, Map, or switch statement when a key chooses known behavior.
  • Use URLSearchParams, Number(), and validation when parsing URL or form values.
Is eval needed here?
  • Turn an API string like {"ok":true} into data
  • Read user[name] when the key comes from a menu
  • Let visitors type arbitrary formulas for a calculator
  • A lesson frame runs one of three predefined snippets
  • A trusted build tool compiles a small generated helper
  • Read ?page=2 and turn it into a number
Try it yourself
0 of 6 correct

Sort each situation by the safest approach.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Performance cost

Engines optimize ordinary code by studying which variables it can read and write. Direct eval breaks that confidence: the string might mention a local name, create a sloppy var, or rely on a name that only appears in one branch. The engine has to be conservative.

There is also a runtime cost. A normal function’s source is parsed and compiled as part of loading the program. An eval string or Function-constructor body is parsed when that line runs. Timings vary by browser, device, warm-up, and the exact code, so the lesson does not pretend a single number is universal.

The performance advice

Do not replace eval because a microbenchmark said one snippet was slower. Replace it because normal functions and data structures are clearer, safer, easier to optimize, and compatible with stricter CSPs.

Where you will use this

You are unlikely to write eval in application code, but you will review code that tries to. Search for string-to-code patterns during security reviews, CSP migrations, and bug hunts around surprising scope behavior.

String-to-code tools compared
ToolScope it can readStrictnessCommon result
Direct eval(code)Caller scope plus outer scopesInherits caller strictness; strict direct eval keeps its own varsMost powerful and most dangerous
Indirect evalGlobal scope onlyString decides whether it is strictCannot see locals
new Function(...)Global scope onlySloppy unless the body says "use strict"Creates a reusable function from text
JSON / lookup / parser APIsOnly the values you passNormal codePreferred for data and choices

If you build a teaching tool, rules engine, or formula editor, prefer a small parser for the exact language you need. If you truly must execute JavaScript, isolate it like the Windows, iframes & postMessage lesson: a sandbox, predefined or trusted input, timeouts where possible, and validated messages.

Common misconceptions

“Every eval call is direct eval.”

No. Only exact eval(...) is direct. Aliases and expressions are indirect.

“new Function is a safer closure eval.”

It does not see closures. It still compiles strings and is still blocked by CSP.

“JSON is JavaScript, so eval is fine.”

JSON is data. JSON.parse rejects programs; eval runs programs.

“CSP only blocks malicious strings.”

CSP cannot read intent. Without unsafe-eval, even friendly eval strings fail.

“Small eval strings are cheap.”

The engine still has to parse them at runtime and limit assumptions around scope.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upPredict direct eval

Type the exact console output.

Starter codePop out in the code editor (opens in a new tab)JavaScript
function demo() {
  const x = "nearby";
  return eval("x");
}
console.log(demo());

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upPredict indirect eval

    Type the exact console output.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    globalThis.x = "global";
    function demo() {
      const x = "nearby";
      return (0, eval)("x");
    }
    console.log(demo());

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeNon-string eval

      Predict the boolean output.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const value = { ok: true };
      console.log(eval(value) === value);

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeFunction constructor scope

        Predict the number.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        globalThis.tax = 5;
        function make() {
          const tax = 99;
          return new Function("price", "return price + tax");
        }
        console.log(make()(10));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeReplace eval for data

          Which API should you name when data arrives as JSON text?

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          const user = JSON.parse('{"name":"Ada","admin":false}');
          console.log(user.name + ":" + user.admin);

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz

            7 QUESTIONS
            Check your understanding · 7 questionsScore: first tries count
            1. Question 1 of 7Which call is direct eval?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const run = eval;
              eval("1 + 1");
              run("1 + 1");
              (0, eval)("1 + 1");

              Choose an answer to see the explanation.

            2. Question 2 of 7What does direct eval read here?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function demo() {
                const x = "local";
                return eval("x");
              }
              console.log(demo());

              Choose an answer to see the explanation.

            3. Question 3 of 7What does indirect eval read here?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              globalThis.x = "global";
              function demo() {
                const x = "local";
                return (0, eval)("x");
              }
              console.log(demo());

              Choose an answer to see the explanation.

            4. Question 4 of 7What does eval do with a non-string?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const value = 42;
              console.log(eval(value));

              Choose an answer to see the explanation.

            5. Question 5 of 7What can a function made with new Function see?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              globalThis.shared = 3;
              function make() {
                const shared = 9;
                return new Function("return shared");
              }
              console.log(make()());

              Choose an answer to see the explanation.

            6. Question 6 of 7What does a CSP without unsafe-eval block in browsers?

              Choose an answer to see the explanation.

            7. Question 7 of 7Which replacement is best for reading a chosen object property?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              const formatters = { upper: (text) => text.toUpperCase() };
              const key = "upper";

              Choose an answer to see the explanation.

            Key takeaways

            • Direct eval is exactly eval(...) and can see the caller’s local scope.
            • Indirect eval and new Function run in global scope; new Function does not close over locals.
            • Strict direct eval keeps its var declarations inside eval; sloppy direct eval can leak them.
            • CSP without unsafe-eval blocks eval, new Function, and string timers in browsers.
            • Prefer JSON.parse, lookup tables, URL parsing, validation, and ordinary functions.

            Final definition.
            eval and new Function turn strings into JavaScript code at runtime, with different scope rules and a security cost that usually outweighs the flexibility.

            Up next: Higher-order functions.

            CompleteFrontend Clear concepts. Working examples.