Eval: run a code string

Sometimes you have JavaScript sitting in a string, and you want to actually run it. The built-in eval function does exactly that: hand it source code as text, and it executes that text as if you had typed it into your program.

let result = eval(code);

A tiny example:

let snippet = 'alert("Ready")';
eval(snippet); // Ready

The string isn’t limited to one expression. It can span multiple lines and hold variable declarations, function declarations, loops — whatever you could write in a normal script.

a string
‘3 * 4’
eval() →
parsed & executed
3 * 4
→ returns
12
eval takes text and feeds it to the JavaScript engine as real code.

What eval returns

The value of an eval call is the result of the last statement it ran.

let value = eval('3 * 4');
alert(value); // 12

That “last statement” rule matters when the string does several things. Here the string declares a variable and then increments it; the value of the whole call is the value of that final ++n expression:

let value = eval('let n = 7; ++n');
alert(value); // 8

eval sees the surrounding scope

Here’s where eval stops being a harmless “string runner” and becomes something you have to think carefully about. The code you pass in runs inside the current lexical environment. It can read variables that are visible at the call site:

let a = 1;

function f() {
  let a = 2;

  eval('alert(a)'); // 2
}

f();

The eval’ed alert(a) prints 2, not 1, because at the point where eval is called the closest a in scope is the one inside f.

And reading isn’t the limit — it can write to those variables too:

let score = 5;
eval("score = 10");
alert(score); // 10, value modified
outer scope
score =5→ overwritten →10
▲ same binding
eval("score = 10")
score = 10
Non-strict eval reaches straight into the scope where it was called.

Strict mode changes this

Under strict mode, eval gets its own lexical environment. Variables and functions declared inside the string stay trapped there — they don’t leak out into the calling code:

// reminder: 'use strict' is enabled in runnable examples by default

eval("let msg = 'hi'; function greet() {}");

alert(typeof msg); // undefined (no such variable)
// function greet is also not visible

Without use strict, eval shares the outer lexical environment, so that same string would create a visible msg and greet on the outside. Modern code runs strict by default (every module and every class body is strict), so you’ll usually get the isolated behavior — but the distinction is worth knowing when you read older scripts.

Should you use eval?

Short answer: almost never. The phrase you’ll hear is “eval is evil,” and while that’s a slogan, the caution behind it is real.

A long time ago JavaScript was a far weaker language, and some tasks genuinely required eval. That era ended more than a decade back. Today, whatever you’re reaching for eval to do can usually be expressed with a normal language feature, a data structure, or a JavaScript Module. If you find eval in a codebase, there’s a strong chance it can be rewritten to be clearer and safer.

Beyond style, its access to outer variables has concrete side-effects.

It fights your minifier. Minifiers shrink code before it ships to production, and one of their tricks is renaming local variables to short names like a or b. That’s normally safe. But if eval is present, the eval’ed string might refer to any of those locals by their original names — so the minifier has to leave every variable that eval could reach untouched. Your compression ratio suffers.

no eval — safe to rename
before: userCount
after:  a
eval in scope — must keep name
before: userCount
after:  userCount (unchanged)
Minifiers rename locals to save bytes — but they can't touch anything eval might read by name.

It makes code harder to follow. When a string can quietly touch and mutate outer locals, you lose the ability to reason about a function by reading it top to bottom. Relying on that is considered poor practice.

There are two clean ways to sidestep the whole problem.

1. Run in global scope with window.eval

If the eval’ed code doesn’t need any of your local variables, call it as window.eval(...). That evaluates the string in the global scope, so it can’t see your locals at all:

let total = 1;
{
  let total = 5;
  window.eval('alert(total)'); // 1 (global variable)
}

Even though the block-scoped total = 5 is closer, window.eval ignores it and reads the global total, which is 1.

2. Need local data? Use new Function

If the code genuinely needs values from the outer scope, switch from eval to new Function and pass those values in as arguments:

let g = new Function('n', 'alert(n * 2)');

g(5); // 10

new Function — covered in The “new Function” syntax — also builds a function from a string, and it too runs in the global scope, so it can’t reach your locals by accident. The difference is that you hand it exactly the data it needs, explicitly and by name. That’s far easier to read and reason about than letting a string dip into whatever happens to be in scope.

Summary

eval(code) runs a string of JavaScript and returns the value of its last statement.

  • It’s rarely needed in modern JavaScript — a language feature or module almost always fits better.
  • It can read and modify outer local variables, which hurts minification and readability, so it’s considered bad practice.
  • To run code in the global scope with no access to locals, use window.eval(code).
  • If the code needs data from the outer scope, use new Function and pass that data as arguments.