skip to content

In JavaScript, why does `function () { console.log('hi'); }();` throw a SyntaxError while `(function () { console.log('hi'); })();` runs, and what other forms make an immediately invoked function expression work?

level: juniorimportance: must knowfreq 62%

answer

  1. the parser decides at the first token
  2. statement position versus expression position
  3. declarations need a name, produce no value
  4. parentheses, bang, plus or void all work
  5. leading semicolon guards against ASI

basics

~20 s

A statement that begins with the keyword function is parsed as a function declaration, which needs a name and cannot be invoked in place. Wrapping it in parentheses forces the parser to read it as an expression, and expressions are callable.

solid answer

~50 s

The parser decides what it is reading from the first token of the statement. When a statement starts with `function`, the grammar commits to a function *declaration*: declarations require a name and are not values, so the trailing `()` has nothing to call and you get a SyntaxError. Putting the function inside parentheses moves it into expression position, where `function () {}` produces a function value that `()` can immediately invoke. Both `(function () {})()` and `(function () {}())` work — the parentheses just need to arrive before the `function` keyword. Any operator that forces expression position does the same job: `!function () {}()`, `+function () {}()`, `void function () {}()`. Arrow functions have the same requirement — `(() => {})()` is fine, `() => {}()` is a SyntaxError. And because a line ending without a semicolon followed by `(` is read as a call, defensive code writes a leading `;(function () {})()`.

code

javascript · 14 lines
javascript
// Fails: the statement starts with `function`, so it is parsed as a declaration.
// function () { console.log('hi'); }();  // SyntaxError

// Works: parentheses put the function in expression position.
(function () { console.log('paren-wrapped call'); })();
(function () { console.log('paren-wrapped whole thing'); }());

// Works: any operator forces expression position (but changes the value).
console.log(!function () { return 1; }());   // false
console.log(void function () { return 1; }()); // undefined

// Arrows must be wrapped too.
(() => console.log('arrow iife'))();
(async () => { await Promise.resolve(); console.log('async iife'); })();

go deeper

for a junior

Be ready to write a working IIFE from memory and say plainly that the parentheses turn a declaration into an expression so it can be called.

for a middle

Explain the parse-position rule precisely, show two or three equivalent forms including the arrow and async variants, and describe why a stray leading semicolon appears in concatenated files.

for a senior

Demonstrate the ASI failure mode as a debugging story: the error surfaces at runtime on the IIFE line while the real cause is the missing semicolon on the line above.

for a principal

Own the standards angle: decide whether the codebase's linter enforces semicolons or bans them, and note that once every file is a module the parenthesised-scope trick mostly stops earning its keep.

## The rule the parser is applying JavaScript's grammar is ambiguous about the `function` keyword: it can begin a *function declaration* (a statement) or a *function expression* (a value). The specification resolves the ambiguity by position. If `function` is the first token of a statement, the parser commits to a declaration before it has seen anything else on the line. A declaration has two properties that break an immediate call. It must have a binding identifier, so an anonymous `function () {}` at statement position is already invalid. And a declaration is not an expression, so it produces no value — the `()` that follows it is a separate, empty pair of parentheses, which is itself a syntax error because there is no expression inside. Even a *named* declaration fails: ```js function () {}(); // SyntaxError: function statement requires a name function go() {}(); // SyntaxError: the trailing () is not a call ``` ## Making it an expression Anything that puts the function into expression position fixes it. The conventional fix is a wrapping pair of parentheses, which come in two placements that are exactly equivalent in effect: ```js (function () { /* ... */ })(); // call the parenthesized function (function () { /* ... */ }()); // parenthesize the whole call ``` The first is the more common style; the second is Crockford's preferred form, on the argument that the parentheses then wrap the complete unit that produces the value. Neither is more correct. Operators work too, because an operand is by definition an expression: ```js !function () { console.log('runs'); }(); // evaluates to false +function () { console.log('runs'); }(); // evaluates to NaN void function () { return 1; }(); // evaluates to undefined ``` These are terser by one character and were popular in minified code. They are worth recognising when you read them, but they change the value of the whole expression (`!` gives `false`, `void` gives `undefined`), so use them only where the value is discarded. An assignment or a `return` also supplies expression position, which is why the module pattern needs no extra wrapping punctuation beyond the parentheses convention: ```js const api = function () { return { ok: true }; }(); // legal, if unidiomatic ``` ## Arrow functions Arrow functions are always expressions, never declarations, so the *statement* rule does not apply to them — but the call still needs parentheses for a different, grammatical reason. A call expression is built on a member expression, and an arrow function is not one, so you cannot append `()` directly to an arrow: ```js (() => { console.log('hi'); })(); // fine () => { console.log('hi'); }(); // SyntaxError (async () => { await setup(); })(); // the async form, same shape ``` The async arrow IIFE is the modern reason people still reach for this shape: it gives you a scope in which `await` is legal inside code that cannot use a top-level await. ## The ASI trap and the leading semicolon Automatic semicolon insertion does *not* insert a semicolon before a line that begins with `(` — an open parenthesis can continue the previous expression as a call. So a semicolon-free line followed by an IIFE silently becomes one expression: ```js const y = makeThing() (function () { console.log('iife'); })() // parsed as makeThing()(function () {...})() -> TypeError at runtime ``` This fails at runtime, not parse time, and the error message points at the IIFE rather than at the missing semicolon, which is why it is confusing to debug. The defensive idiom is a leading semicolon, `;(function () {})()`, and it is the reason you see that stray semicolon at the top of older library files that were designed to be concatenated with other files. The same hazard applies to lines starting with `[` or a template literal backtick. ## Naming the function An IIFE can carry a name even though nothing calls it by that name: ```js (function setup() { throw new Error('boom'); })(); ``` The name is bound only inside the function's own scope, and it shows up in the stack trace, which is worth the four extra characters when the IIFE does real work at startup. The name of a *function expression* does not leak into the enclosing scope, which is the whole point. ## Passing arguments Because it is a call, an IIFE takes arguments, and older code exploited that to make globals local and short: `(function (window, document, undefined) { ... })(window, document);`. The `undefined` parameter is a historical guard from an era when the global `undefined` was writable; it is pointless in modern engines, where `undefined` is a non-writable global. Passing a namespace object in is still a live technique for script-level code.

  • Is there any difference between `(function () {})()` and `(function () {}())`?
    No behavioural difference at all — both parse the function as an expression and call it, and both evaluate to the same value. The choice is purely stylistic: the second places the parentheses around the complete value-producing unit, which Crockford argued reads better. Linters may enforce one or the other, so follow the codebase's rule.
  • Why does a named IIFE like `(function setup() {})()` not create a `setup` binding in the surrounding scope?
    Because it is a function *expression*, not a declaration. The name of a function expression is bound only inside that function's own scope, so recursion and stack traces can use it while the enclosing scope stays clean. Only declarations create a binding in the scope that contains them.
  • When would you still write an async IIFE rather than just awaiting at the top level?
    When the surrounding code is not a context where `await` is legal — a classic non-module script, a CommonJS file, or inside a synchronous callback. `(async () => { ... })()` buys you a function body in which `await` parses. Attach a `.catch()` to it, because nothing else is holding the returned promise.

saying these in an interview costs you the question

  • Claims the parentheses are just a style convention
  • Says you need parentheses because the function is anonymous
  • Thinks `!function(){}()` behaves differently from a wrapped call
  • Believes arrow functions can be called without wrapping
  • Says ASI always inserts a semicolon at a line break

context