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:
- A line break separates two tokens that cannot be part of the same statement
- The end of the file is reached
- 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:
| Approach | Style Guides | Argument |
|---|---|---|
| Always use semicolons | Airbnb, Google | Explicit, no ASI surprises, safer |
| Never use semicolons | StandardJS | Cleaner look, ASI handles it, less typing |
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
}
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:
| Style | Used By | Notes |
|---|---|---|
| 2 spaces | Airbnb, Google, Node.js, most JS projects | Industry standard for JavaScript |
| 4 spaces | Some teams, other language backgrounds | Common in Python and Java communities |
| Tabs | StandardJS (partially), personal preference | Width 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;
*/
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:
- Must start with a letter (
a-z,A-Z), underscore (_), or dollar sign ($) - Subsequent characters can also include digits (
0-9) - 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 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:
break | case | catch | class | const |
continue | debugger | default | delete | do |
else | export | extends | finally | for |
function | if | import | in | instanceof |
let | new | return | static | super |
switch | this | throw | try | typeof |
var | void | while | with | yield |
Words Reserved for Future Use
These are reserved even though they are not currently used as keywords:
enum | implements | interface | package |
private | protected | public | await (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'
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:
| Character | Example | Risk |
|---|---|---|
( | (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 - | +someValue | Previous line treated as arithmetic |
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.
myVarandmyvarare different identifiers. - Follow naming conventions:
camelCasefor variables and functions,PascalCasefor classes,UPPER_SNAKE_CASEfor 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
returnstatements 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.