skip to content

In JavaScript, why is putting the thrown value on the line after `throw` a syntax error, and how do you throw from an expression position such as an arrow function's concise body or the right-hand side of `??`?

level: middleimportance: nice to knowfreq 27%

answer

  1. the grammar forbids a line break
  2. no valid argument-less form
  3. compare with return and ASI
  4. statement, so no value position
  5. wrap it in a function call

basics

~20 s

The grammar forbids a line terminator between throw and its expression, and there is no valid argument-less throw, so a newline is a hard syntax error rather than silent semicolon insertion. throw is a statement, not an expression, so expression positions need a helper function or an immediately-invoked arrow.

solid answer

~50 s

`throw` is defined as a restricted production: the grammar spells it `throw [no LineTerminator here] Expression ;`. Automatic semicolon insertion would have to terminate the statement right after the keyword, but `throw;` is not a valid statement at all, so engines report a hard syntax error — V8 says "Illegal newline after throw". That is the opposite of `return`, where a newline silently produces `return;` and the function returns `undefined`. Separately, `throw` is a **statement**, so it cannot appear where a value is expected: `const x = cond ? a : throw new Error()` and `value ?? throw new Error()` are both syntax errors, and an arrow needs a block body — `() => { throw new Error('x') }` — because the concise body is an expression. To throw from an expression position, call something: either an immediately-invoked arrow, `value ?? (() => { throw new Error('missing') })()`, or, more readably, a small `fail()` helper whose body throws, since a function call *is* an expression.

code

javascript · 14 lines
javascript
// A newline between `throw` and its value is a SyntaxError at parse time:
//   throw
//     new Error('nope');   // SyntaxError: Illegal newline after throw

function fail(message) {
  throw new Error(message);
}

const config = { port: 0 };

// A call IS an expression, so it works where `throw` cannot appear:
const port = config.port || fail('port is required');

console.log(port);

go deeper

for a junior

Know that the thrown value must sit on the same line as the keyword, and that an arrow needs a block body — () => { throw new Error('x'); } — because a concise body holds an expression.

for a middle

Explain the restricted production and why automatic semicolon insertion turns the newline into a hard syntax error here but a silent undefined for return. Show the helper-function workaround for ternaries, ?? and default parameters.

for a senior

Judge when the fail() pattern earns its place — required parameters and configuration reads — versus when an explicit guard clause is clearer to the next reader. Avoid clever inline IIFEs in code others maintain.

for a principal

Treat it as a codebase convention question: one shared helper for required-value failures, consistent messages, and no reliance on unshipped proposals such as throw expressions in code that must run everywhere.

## Two separate facts, often confused This question bundles two independent pieces of the grammar: a restricted production about newlines, and the statement-vs-expression distinction. They bite in different places. ## Restricted production: no newline after `throw` The ECMAScript grammar for a throw statement is written as: ``` throw [no LineTerminator here] Expression ; ``` That bracketed annotation is a **restricted production**. It means the parser refuses to accept a line break between the keyword and its operand. So this fails to parse: ```js throw new Error('nope'); // SyntaxError: Illegal newline after throw ``` Why an error rather than something weird-but-legal? Automatic semicolon insertion (ASI) reacts to a restricted production by terminating the statement at the line break. That would yield `throw;`, and `throw;` is not a valid statement — the grammar has no argument-less form. With no valid parse available, the engine reports a syntax error at parse time, before anything runs. The contrast with `return` is the memorable part. `return` is *also* a restricted production, but `return;` **is** a valid statement. So: ```js function f() { return { ok: true }; // returns undefined; the object literal is dead code } ``` silently returns `undefined`. Two identically-shaped grammar rules produce a loud failure in one case and a silent bug in the other, purely because of whether the bare form is legal. `throw` is the friendlier of the two: it cannot fail quietly. The practical rule is simply to keep the value on the same line as the keyword. If the expression is long, break *inside* it: ```js throw new Error( `unsupported format: ${format}` ); ``` That is fine — the line break is inside the argument list, not between `throw` and `new`. ## `throw` is a statement, not an expression A statement produces no value, so `throw` cannot go where the language expects one. All of these are syntax errors: ```js const x = flag ? compute() : throw new Error('no'); // ternary branch const y = value ?? throw new Error('missing'); // ?? right operand const f = () => throw new Error('boom'); // concise arrow body const z = (throw new Error('boom')); // parentheses do not help ``` Parenthesising does not help, because parentheses group *expressions*, and there is no throw-expression to group. (A TC39 proposal to add throw expressions has been floated but is not part of the language, so do not write code that assumes it.) ### Getting a throw into an expression position The trick is that a **function call is an expression**, and a function body may contain any statement. So wrap the throw in a function and call it. With an arrow that has a block body: ```js const f = () => { throw new Error('boom'); }; ``` Immediately invoked, inline: ```js const port = process.env.PORT ?? (() => { throw new Error('PORT is required'); })(); ``` That works but reads badly at every call site. The usual production shape is a named helper: ```js function fail(message) { throw new Error(message); } const port = config.port ?? fail('port is required'); const mode = flag ? 'fast' : fail('mode not set'); const { id } = payload ?? fail('payload missing'); ``` `fail(...)` is an ordinary call expression, so it is legal in a ternary branch, as the right operand of `??` or `||`, in a default parameter value, and anywhere else a value belongs. It never returns — control leaves via the throw — which is exactly the semantics you wanted. ### Default parameters are a natural home for it ```js function connect(url = fail('url is required')) { return url; } ``` The default expression is only evaluated when the argument is omitted, so this gives a required-parameter check with no `if` statement in the body. ## Why interviewers ask this It separates people who have merely used `throw` from people who have read what it *is*. The newline rule shows you know restricted productions and how ASI behaves at them; the expression question shows you understand the statement/expression split well enough to work around it deliberately, instead of concluding that "you can't throw there".

  • Why does a newline after `return` behave differently from a newline after `throw`?
    Both are restricted productions, so automatic semicolon insertion terminates the statement at the line break. The difference is what that leaves behind: `return;` is a valid statement, so the function silently returns undefined and the next line becomes dead code. `throw;` is not valid at all, so the parser has no legal interpretation and reports a syntax error instead.
  • Does wrapping the throw in parentheses make it usable as an expression?
    No. Parentheses group an expression, and `throw` never forms one, so `(throw new Error('x'))` is still a syntax error. You need an actual function boundary: an arrow with a block body, an immediately-invoked arrow, or a named helper. The call to that function is what supplies the expression the grammar is asking for.
  • How would you enforce a required function parameter using this?
    Put the helper in the default value: `function connect(url = fail('url is required')) { ... }`. Default parameter expressions are evaluated only when the argument is missing or undefined, so the call throws exactly in that case and the body stays free of guard clauses. It is one of the few places the pattern reads better than an explicit `if`.

saying these in an interview costs you the question

  • Thinks a newline after throw silently throws undefined
  • Believes parentheses turn throw into an expression
  • Claims throw expressions are already standard JavaScript
  • Says a concise arrow body can contain a throw statement
  • Assumes the newline rule is a linter preference, not grammar

context