Skip to main content

Understanding JavaScript Code Structure and Syntax Rules

Every programming language has grammar rules, just like human languages. JavaScript's syntax rules define how you write statements, where you place semicolons, how you organize code into blocks, and what names you can use for your variables. Getting these fundamentals right from the start prevents a whole category of frustrating bugs and makes your code readable to other developers.

This guide covers the structural building blocks of JavaScript code. You will learn how the language interprets your instructions, why semicolons are a source of endless debate, how to write effective comments, and the naming conventions used across the JavaScript ecosystem. Pay close attention to the section on Automatic Semicolon Insertion. It is one of the most common sources of subtle, hard-to-find bugs.

Statements, Semicolons, and Automatic Semicolon Insertion (ASI)

What Is a Statement?

A statement is a single instruction that tells JavaScript to do something. Think of statements as sentences in a language. Each one expresses a complete action.

let name = "Alice";              // variable declaration statement
console.log(name); // function call statement
let sum = 10 + 20; // assignment statement
if (sum > 20) { alert("Big"); } // conditional statement

A JavaScript program is simply a sequence of statements executed one after another, from top to bottom.

Semicolons: The Statement Terminator

In JavaScript, a semicolon (;) marks the end of a statement. It is the equivalent of a period at the end of a sentence.

let firstName = "Alice";
let lastName = "Smith";
console.log(firstName + " " + lastName);

You can write multiple statements on the same line by separating them with semicolons, although this is not recommended because it hurts readability:

// Technically valid, but hard to read
let a = 1; let b = 2; let c = a + b; console.log(c);

// Much better: one statement per line
let a = 1;
let b = 2;
let c = a + b;
console.log(c);

Automatic Semicolon Insertion (ASI)

JavaScript has a feature called Automatic Semicolon Insertion (ASI) that allows you to omit semicolons in many cases. When the JavaScript engine encounters a line break where a semicolon would make sense, it automatically inserts one for you.

// Without semicolons: ASI adds them for you
let x = 5
let y = 10
console.log(x + y)

The engine interprets this as:

let x = 5;
let y = 10;
console.log(x + y);

This feature makes JavaScript more forgiving, and some developers and style guides (like StandardJS) prefer writing code without semicolons entirely. However, ASI follows a set of rules that are not always intuitive, and relying on it without understanding those rules is dangerous.

When ASI Inserts a Semicolon

ASI kicks in when the JavaScript parser encounters a situation where:

  1. A line break separates two tokens that cannot be part of the same statement
  2. The end of the file is reached
  3. A closing brace } is encountered
// ASI inserts semicolons at each line break
let greeting = "Hello" // ; inserted
console.log(greeting) // ; inserted

// ASI does NOT insert a semicolon when the next line
// can be interpreted as a continuation
let total = 10
+ 20
+ 30
console.log(total) // 60 (all three lines are treated as one statement)

In that last example, ASI does not insert a semicolon after 10 because the + on the next line makes it a valid continuation of the expression.

The Semicolon Debate

The JavaScript community is divided on whether to use semicolons explicitly:

ApproachStyle GuidesArgument
Always use semicolonsAirbnb, GoogleExplicit, no ASI surprises, safer
Never use semicolonsStandardJSCleaner look, ASI handles it, less typing
tip

Recommendation for learners: always use semicolons. Until you have a thorough understanding of ASI rules, explicit semicolons protect you from subtle bugs. Once you are experienced, you can make an informed choice. Most professional teams use ESLint to enforce whichever style they choose.

Code Blocks and Indentation Best Practices

What Is a Code Block?

A code block is a group of statements wrapped in curly braces { }. Code blocks are used with control structures like if, for, while, and function to group multiple statements together.

// Single statement: braces are optional (but recommended)
if (true) console.log("Hello");

// Multiple statements: braces are required
if (true) {
let greeting = "Hello";
console.log(greeting);
console.log("World");
}

Even when a code block contains only one statement, always use curly braces. Omitting them is a frequent source of bugs:

// DANGEROUS: no braces
if (loggedIn)
showDashboard();
loadUserData(); // This ALWAYS runs! It's not inside the if.

// SAFE: always use braces
if (loggedIn) {
showDashboard();
loadUserData(); // Now both lines are conditional
}
caution

Without braces, only the first line after if is conditional. The second line runs regardless. The indentation is misleading because JavaScript does not care about whitespace.

Indentation

JavaScript does not enforce indentation (unlike Python). Indentation is purely for human readability. But readable code is maintainable code, so consistent indentation is essential.

Two spaces vs. four spaces vs. tabs:

StyleUsed ByNotes
2 spacesAirbnb, Google, Node.js, most JS projectsIndustry standard for JavaScript
4 spacesSome teams, other language backgroundsCommon in Python and Java communities
TabsStandardJS (partially), personal preferenceWidth is configurable per editor

The JavaScript community overwhelmingly uses 2 spaces. This is a convention, not a technical requirement, but following it makes your code consistent with the vast majority of JavaScript codebases.

// 2-space indentation (JavaScript standard)
function processUser(user) {
if (user.isActive) {
let greeting = `Hello, ${user.name}`;
console.log(greeting);
if (user.isAdmin) {
console.log("Admin access granted");
}
}
}

Nesting and Readability

Deeply nested code is hard to read. If you find yourself nesting more than 3 levels deep, consider refactoring:

// Hard to read: deep nesting
function processOrder(order) {
if (order) {
if (order.items) {
if (order.items.length > 0) {
if (order.isPaid) {
shipOrder(order);
}
}
}
}
}

// Much cleaner: early returns (guard clauses)
function processOrder(order) {
if (!order) return;
if (!order.items) return;
if (order.items.length === 0) return;
if (!order.isPaid) return;

shipOrder(order);
}

Both functions do exactly the same thing, but the second version is dramatically easier to read and maintain.

Single-Line and Multi-Line Comments

Comments are text in your code that JavaScript ignores completely. They exist solely to explain your code to other developers (including your future self).

Single-Line Comments

Use // to create a comment that extends to the end of the line:

// This is a single-line comment
let price = 99.99;

let tax = price * 0.2; // Calculate 20% tax

// You can also use comments to temporarily disable code:
// console.log("This line won't execute");

Multi-Line Comments

Use /* */ to create comments that span multiple lines:

/* 
This is a multi-line comment.
It can span as many lines as you need.
Everything between the opening and closing markers is ignored.
*/

let total = price + tax;

/*
You can also use multi-line comments to disable
blocks of code during debugging:

let discount = total * 0.1;
total = total - discount;
*/
caution

Multi-line comments cannot be nested. This will cause a syntax error:

/* outer comment
/* inner comment */ // This closes the OUTER comment
still part of outer // SyntaxError! This is now regular code
*/

The first */ the engine encounters closes the comment, regardless of how many /* preceded it.

When to Comment and When Not to Comment

Good comments explain why something is done, not what is done. The code itself should be clear enough to show what is happening.

// BAD COMMENTS: stating the obvious
let age = 25; // set age to 25
age = age + 1; // add 1 to age
if (age > 18) { // check if age is greater than 18

// GOOD COMMENTS: explaining why
let age = 25;

// Age incremented because this runs on the user's birthday
age = age + 1;

// Users under 18 are redirected to a restricted version per COPPA compliance
if (age > 18) {

Comment when:

  • The logic is complex or non-obvious
  • There is a business reason behind a technical decision
  • You are working around a known bug or limitation
  • A regular expression or algorithm needs explanation

Do not comment when:

  • The code is self-explanatory
  • You are restating what the code already says
  • You can make the code clearer by renaming variables instead
// Instead of a comment, use a better variable name:

// BAD: comment explains a cryptic name
let d = 7; // number of days until expiration

// GOOD: name explains itself, no comment needed
let daysUntilExpiration = 7;

JSDoc Comments

JSDoc is a special comment format used to document functions. It starts with /** and follows a structured syntax:

/**
* Calculates the total price including tax.
* @param {number} price - The base price of the item.
* @param {number} taxRate - The tax rate as a decimal (e.g., 0.2 for 20%).
* @returns {number} The total price including tax.
*/
function calculateTotal(price, taxRate) {
return price + (price * taxRate);
}

JSDoc comments are not just for documentation. Many code editors (including VS Code) read them and provide intelligent auto-completion and hover information based on the types you specify. You will learn more about JSDoc in the code quality module.

Case Sensitivity and Naming Conventions

JavaScript Is Case-Sensitive

JavaScript treats uppercase and lowercase letters as completely different characters. This applies to variable names, function names, keywords, and everything else.

let myVariable = "hello";
let myvariable = "world";
let MYVARIABLE = "!";

// These are THREE separate variables
console.log(myVariable); // "hello"
console.log(myvariable); // "world"
console.log(MYVARIABLE); // "!"

Keywords are also case-sensitive:

let x = 10;    // correct ()'let' is a keyword)
Let x = 10; // SyntaxError ('Let' is not a keyword)
LET x = 10; // SyntaxError ('LET' is not a keyword)

Naming Rules (What JavaScript Allows)

Variable and function names in JavaScript must follow these rules:

  1. Must start with a letter (a-z, A-Z), underscore (_), or dollar sign ($)
  2. Subsequent characters can also include digits (0-9)
  3. Cannot be a reserved word
// VALID names
let userName = "Alice";
let _privateVar = true;
let $element = document.body;
let camelCaseIsGreat = true;
let item1 = "first";
let café = "latte"; // Unicode letters are valid (but avoid this)
let π = 3.14159; // Also valid (but don't do this either)

// INVALID names
let 1stItem = "nope"; // Cannot start with a digit
let my-variable = "nope"; // Hyphens are not allowed
let my variable = "nope"; // Spaces are not allowed
let let = "nope"; // 'let' is a reserved word

Naming Conventions (What the Community Expects)

While JavaScript allows many naming patterns, the community has strong conventions:

camelCase for variables and functions:

let firstName = "Alice";
let totalItemCount = 42;
let isLoggedIn = true;

function calculateTotalPrice(items) { }
function getUserById(id) { }

PascalCase for classes and constructor functions:

class UserAccount { }
class ShoppingCart { }
function DatabaseConnection() { } // constructor function

UPPER_SNAKE_CASE for true constants (values that never change):

const MAX_RETRY_COUNT = 3;
const API_BASE_URL = "https://api.example.com";
const SECONDS_PER_DAY = 86400;
note

Note on const: Not every const variable should be UPPER_SNAKE_CASE. Use uppercase only for values that are truly fixed and known before runtime:

// UPPER_CASE: value is fixed and known before the program runs
const MAX_FILE_SIZE = 1048576;
const DEFAULT_LANGUAGE = "en";

// camelCase: value is constant but determined at runtime
const currentUser = getUserFromSession();
const startTime = Date.now();
const filteredItems = items.filter(item => item.active);

Prefixes for boolean variables:

// Start with is, has, can, should, was
let isVisible = true;
let hasPermission = false;
let canEdit = true;
let shouldRedirect = false;

Underscore prefix for "private" properties (convention, not enforced):

class User {
constructor(name) {
this._name = name; // convention: treat as private
this.#secret = "hidden"; // true private field (ES2022)
}
}

Descriptive Names Matter

Choose names that describe what a variable holds or what a function does:

// BAD: cryptic, meaningless names
let x = users.filter(u => u.a > 18);
let tmp = x.length;
function proc(d) { return d * 0.2; }

// GOOD: descriptive, self-documenting names
let adultUsers = users.filter(user => user.age > 18);
let adultCount = adultUsers.length;
function calculateTax(amount) { return amount * 0.2; }

Reserved Words in JavaScript

Reserved words are identifiers that JavaScript reserves for its own use. You cannot use them as variable names, function names, or any other identifier.

Complete Reserved Words Table

These words are reserved in all contexts:

breakcasecatchclassconst
continuedebuggerdefaultdeletedo
elseexportextendsfinallyfor
functionifimportininstanceof
letnewreturnstaticsuper
switchthisthrowtrytypeof
varvoidwhilewithyield

Words Reserved for Future Use

These are reserved even though they are not currently used as keywords:

enumimplementsinterfacepackage
privateprotectedpublicawait (in modules)

Technically Not Reserved But Avoid

These words have special meaning in certain contexts. While you can use them as variable names, doing so creates confusion and potential bugs:

// These "work" but are terrible ideas
let undefined = "surprise"; // Overwrites undefined in non-strict mode!
let NaN = 42; // Confusing beyond measure
let Infinity = 0; // Please don't
let arguments = []; // Breaks function arguments access

// These global objects/functions should never be shadowed
// String, Number, Boolean, Array, Object, Function
// parseInt, parseFloat, isNaN, isFinite
// console, window, document
// Attempting to use a reserved word as a variable name
let class = "Math"; // SyntaxError: Unexpected token 'class'
let return = 5; // SyntaxError: Unexpected token 'return'
let for = "loop"; // SyntaxError: Unexpected token 'for'
tip

If your editor shows a syntax error on a variable name, check whether it is a reserved word. VS Code highlights reserved words with a different color, making them easy to spot.

Common Mistake: How ASI Can Break Your Code

Automatic Semicolon Insertion works well most of the time, but there are specific patterns where it produces behavior you absolutely did not intend. These are not theoretical edge cases. They appear regularly in real code.

Trap 1: The Return Statement

This is the most dangerous ASI pitfall:

// What you wrote:
function getUser() {
return
{
name: "Alice",
age: 30
}
}

console.log(getUser()); // undefined (NOT the object!)

ASI inserts a semicolon immediately after return because the line break makes return; a valid statement. The engine sees:

function getUser() {
return; // ASI inserts semicolon here. Function returns undefined
{ // This block is unreachable code
name: "Alice",
age: 30
}
}

The fix: Start the return value on the same line as return:

// CORRECT: opening brace on the same line as return
function getUser() {
return {
name: "Alice",
age: 30
};
}

console.log(getUser()); // { name: "Alice", age: 30 }

This applies to return, throw, break, and continue. Never put a line break immediately after these keywords if they are followed by a value.

Trap 2: Lines Starting with Parentheses

// What you wrote:
let x = 10
(function() {
console.log("IIFE")
})()

ASI does not insert a semicolon after 10 because the ( on the next line makes the engine think you are calling 10 as a function:

// What the engine sees:
let x = 10(function() { console.log("IIFE") })()
// TypeError: 10 is not a function

The fix: Add a semicolon after the first statement, or start the IIFE line with a semicolon:

// Fix 1: explicit semicolon
let x = 10;
(function() {
console.log("IIFE");
})();

// Fix 2: defensive semicolon at the start of the line
let x = 10
;(function() {
console.log("IIFE");
})()

Trap 3: Lines Starting with Square Brackets

// What you wrote:
let message = "Hello"
[1, 2, 3].forEach(n => console.log(n))

ASI does not insert a semicolon because "Hello"[1, 2, 3] is valid syntax (bracket access on a string):

// What the engine sees:
let message = "Hello"[1, 2, 3].forEach(n => console.log(n))
// This tries to access "Hello"[3], which is "l"
// Then calls "l".forEach() - TypeError: "l".forEach is not a function

The fix:

let message = "Hello";
[1, 2, 3].forEach(n => console.log(n));

Trap 4: Lines Starting with Template Literals

// What you wrote:
let a = "Hello"
`World`.toUpperCase()

The engine interprets "Hello" followed by a backtick as a tagged template literal:

// What the engine sees:
let a = "Hello"`World`.toUpperCase()
// TypeError: "Hello" is not a function

The Five Dangerous Characters

ASI problems occur when a new line starts with one of these five characters:

CharacterExampleRisk
((function() {})()Previous line treated as function call
[[1, 2].map(...)Previous line treated as bracket access
``template`Previous line treated as tagged template
//regex/.test(s)Previous line treated as division
+ or -+someValuePrevious line treated as arithmetic
warning

If you choose to write code without semicolons, you must memorize these five danger characters and always add a semicolon before lines that start with (, [, `, /, +, or -. Most linters (ESLint with the no-unexpected-multiline rule) catch these cases automatically.

The Safest Approach

// Always use semicolons + a linter = zero ASI issues

let name = "Alice";
let scores = [90, 85, 92];
let average = scores.reduce((a, b) => a + b, 0) / scores.length;
console.log(`${name}'s average: ${average}`);

Configure ESLint to enforce your preference and catch any ASI-related issues automatically. Then you never have to think about these edge cases again.

Summary

JavaScript's syntax rules form the foundation of every program you will write:

  • Statements are individual instructions, terminated by semicolons. Write one statement per line for readability.
  • Automatic Semicolon Insertion lets you omit semicolons in many cases, but its rules are complex. Using explicit semicolons is the safest approach for beginners.
  • Code blocks use curly braces { } to group statements. Always use braces, even for single-statement blocks, to avoid bugs.
  • Indentation does not affect execution but is critical for readability. The JavaScript standard is 2 spaces.
  • Comments explain why code exists, not what it does. Use // for single-line and /* */ for multi-line comments.
  • JavaScript is case-sensitive. myVar and myvar are different identifiers.
  • Follow naming conventions: camelCase for variables and functions, PascalCase for classes, UPPER_SNAKE_CASE for fixed constants.
  • Reserved words cannot be used as identifiers. Check the complete list if you encounter unexpected syntax errors.
  • ASI has specific traps, especially with return statements and lines starting with (, [, or backticks. Explicit semicolons and a linter eliminate these risks entirely.

With these syntax rules internalized, you are ready to explore strict mode, which adds an extra layer of safety by turning silent errors into explicit exceptions.