Skip to content
Nate Comics cover art for issue #06: The missing semicolon: JavaScript's return trap and automatic semicolon insertion

Issue #06

The missing semicolon: JavaScript's return trap and automatic semicolon insertion

Published
Tags
JavaScript, Engineering
Read time
2 min

Why `return` followed by a line break returns undefined, which lines need a leading semicolon, and the lint and format settings that end the argument.

Here's a function that looks like it returns an object:

function config() {
  return
  {
    retries: 3
  }
}

config(); // undefined

It returns undefined. No error, no warning, just a quietly wrong value. The culprit is automatic semicolon insertion (ASI), a set of rules in the ECMAScript specification that adds semicolons where your code doesn't have them.

What ASI actually does

The spec inserts a semicolon in three situations, roughly:

  1. The next token can't legally continue the statement, and there's a line break before it.
  2. The next token is a closing brace }.
  3. The end of the input is reached.

On top of that, some grammar rules forbid a line break in a specific spot. These are called restricted productions. If you put a line break there, a semicolon is inserted. The ones that bite are:

  • return, break and continue followed by a line break.
  • ++ and -- used after a value (postfix) on the next line.

So return followed by a new line becomes return;, and the object on the next line becomes an unreachable block.

The fix: put the start of the value on the same line as return.

return {
  retries: 3,
};

The opposite trap: lines that join

ASI only inserts a semicolon when the next line can't continue the statement. If it can, the lines join. That catches code written without semicolons when a line starts with (, [ or a template literal:

const total = price * quantity
[1, 2, 3].forEach(log)

That reads as price * quantity[1, 2, 3].forEach(log), which is nonsense at runtime. Likewise, a line starting with ( turns the previous line into a function call.

Developers who skip semicolons put one at the start of these lines:

;[1, 2, 3].forEach(log)

Semicolons or not?

Both styles are fine if a tool enforces them. Arguing about it in code review is the expensive option. Pick one, let a formatter like Prettier apply it on save, add a linter rule for unreachable code, and the tricky cases above get caught automatically.

The real lesson is bigger than semicolons: small details cause big bugs when they rely on people remembering rules. Move the rule into a tool, and nobody has to remember.

Questions readers ask

What is automatic semicolon insertion in JavaScript?
A set of rules in the ECMAScript specification that inserts semicolons where a statement is missing one, for example before a closing brace, at the end of the input, or where a line break makes the next token illegal.
Why does my JavaScript function return undefined?
If the value starts on the line after return, automatic semicolon insertion ends the statement at return. Put the opening brace or first part of the value on the same line as return.
Should I use semicolons in JavaScript?
Either style works if it's enforced by a tool. With semicolons, the rules matter less. Without them, start any line beginning with ( [ or ` with a semicolon, and let a formatter like Prettier apply the rule.

Sources

  1. Lexical grammar: automatic semicolon insertion (opens in a new tab), MDN Web Docs
  2. ECMAScript Language Specification (opens in a new tab), Ecma TC39

Open to senior roles · Remote, hybrid or on-site

Hiring a senior engineer who writes like this?

I'm Nathan Mabasa, a senior software engineer in Johannesburg. I build React and Next.js applications in TypeScript, plus the APIs, payments and security underneath them. Seven years across agencies and in-house teams.

One quick thing

Who's asking?

Leave your details and the CV downloads straight away. It helps me follow up about the role.

Your details go only to me, to follow up about roles. No mailing lists, nothing shared.