JavaScript Syntax Explained: Complete Beginner’s Guide

JavaScript Syntax Explained: Complete Beginner’s Guide

JavaScript looks forgiving. You can write a working program without understanding most of its syntax — no types to declare, no semicolons strictly required, no compiler to satisfy. And then a script stops working for a reason you cannot see, and the error message points at a line that looks perfectly fine.

The reason is that JavaScript’s syntax is small but precise. Curly braces mean one thing in a function and another in an object literal. Semicolons are optional until the automatic insertion rules guess wrong. A missing comma turns a value into a label. A stray quote ends a string early and produces an error thirty lines later.

This guide covers JavaScript syntax from the ground up: statements and expressions, variables, data types, operators, conditionals, loops, functions, arrays, objects, template literals, destructuring, classes, modules, error handling, and the specific mistakes that break scripts. Every example runs live on the page.

In one sentence: JavaScript syntax is the set of rules that define how a program is written — statements end with semicolons, blocks are wrapped in curly braces, strings live in quotes, and every value has a type.

What “Syntax” Actually Means

Syntax is grammar. It is the set of rules that determines whether a sequence of characters is a valid program. The rules are enforced by the parser, and when a rule is broken the parser refuses to run the script at all.

This is different from a runtime error, which happens while the code runs. A syntax error stops everything before a single line executes.

Syntax error — nothing runs
const name = "Ada console.log(name); // Missing closing quote. // The parser throws before // any code executes. // SyntaxError: Invalid or // unexpected token
Runtime error — the script ran
const name = "Ada"; console.log(name); console.log(age); // Valid syntax. Runs fine // until line 3, where `age` // does not exist. // ReferenceError: age is // not defined

Statements and expressions

Almost everything in JavaScript is either a statement or an expression. The distinction matters more than it first appears.

Expression

Produces a value

2 + 3 produces 5.

"hello" produces a string.

() => 42 produces a function.

An expression can be assigned, passed, or returned.

Statement

Performs an action

let x = 5; declares a variable.

if (x > 3) { } branches.

for (let i = 0; ...) loops.

A statement cannot be assigned to a variable.

// Expression — produces a value
const total = 10 + 5;        // 15

// Statement — performs an action
if (total > 10) {
    console.log('big');
}

// Tricky: a function declaration is a statement
function greet() { return 'hi'; }

// An arrow function assigned to a variable is an expression
const greet2 = () => 'hi';

// This is why you cannot write:
// function() { }    ← syntax error on its own

// But you can write:
const fn = function() { };

Statements, Semicolons, and Braces

A statement is a single instruction. Most end with a semicolon, and blocks of statements are wrapped in curly braces.

; ends a statement { } groups a block ( ) groups an expression [ ] array or index , separates items . property access

Semicolons are optional — but write them anyway

JavaScript has a feature called Automatic Semicolon Insertion, or ASI. The parser inserts semicolons for you in certain situations, so most programs work without typing a single one.

The trouble is that ASI follows specific rules, and occasionally the rule it applies is not the one you intended.

// This works — ASI adds the semicolon
const a = 1
const b = 2

// This does not — ASI joins the lines instead
const c = 1
const d = 2
[1, 2, 3].forEach(n => console.log(n))

// The parser sees:
const d = 2[1, 2, 3].forEach(...)
// Because a line starting with [ continues the previous one.

// With an explicit semicolon:
const e = 2;
[1, 2, 3].forEach(n => console.log(n));   // works

The same trap exists with a line starting with (, `, +, or -. Each one can continue the previous statement instead of starting a new one.

The five characters to watch

[, (, `, +, and - at the start of a line are the ones that most often trip ASI. Ending the previous line with a semicolon removes the ambiguity entirely.

Curly braces mean two different things

This is a genuine source of confusion. A pair of curly braces can be a block (a group of statements) or an object literal (a value). Which one depends entirely on where it appears.

A block — a group of statements
if (isReady) { start(); finish(); } // The braces contain statements. // No value is produced. function run() { doWork(); } // Same thing — a block.
An object literal — a value
const user = { name: 'Ada', age: 36 }; // The braces produce an object. // This is an expression. return { ok: true }; // Also a value, because a // return statement expects one.

When a statement begins with {, the parser always treats it as a block. That is why an object literal on its own line needs to be wrapped in parentheses if you want it treated as a value.

// Parsed as a block, containing a label and an expression
{
    name: 'Ada'
}

// Parsed as an object literal, because of the parentheses
({ name: 'Ada' });

// This is why arrow functions returning an object need parens:
const makeUser = () => ({ name: 'Ada' });

Comments

JavaScript has two comment syntaxes, and unlike CSS both are real. The parser ignores anything inside them.

// A single-line comment

/* A block comment
   that spans
   several lines */

/**
 * A documentation comment.
 * Tools can read these and generate docs.
 * @param {string} name
 * @returns {string}
 */
function greet(name) {
    return 'Hello, ' + name;
}

The third form — starting with /** — is a JSDoc comment. It is not special to the language, but editors and documentation tools recognize it and use it for autocomplete and type hints.

Comment the why, not the what

// increment i by 1 adds nothing. // skip the header row explains a decision. The code already says what it does — the comment should say why.

Declaring Variables

A variable is a named container for a value. JavaScript has three keywords for creating one, and they behave differently in ways that matter.

const Keyword — declares the variable
userName Identifier — the name you chose
= Assignment operator
‘Ada’ The value being stored
; Ends the statement
The three declaration keywords
Keyword Reassignable? Scope Use it when…
const No Block The value will not change — the default choice
let Yes Block The value must be updated later
var Yes Function Almost never in modern code
const name = 'Ada';       // cannot be reassigned
let count = 0;            // can be reassigned

count = count + 1;        // fine
// name = 'Grace';        // TypeError: Assignment to constant variable

// const does not mean the value is frozen —
// only that the binding cannot be reassigned
const list = [1, 2];
list.push(3);             // fine — the array is modified, not replaced
// list = [1, 2, 3];      // TypeError — reassigning the variable

// Declaring without a value
let pending;              // value is undefined
pending = 'ready';        // assign later

Naming rules

  • Start with a letter, $, or _. 2ndPlace is invalid; secondPlace is fine.
  • Case matters. userName and username are two different variables.
  • Reserved words cannot be used. let if = 5 is a syntax error.
  • Unicode letters are allowed. But stick to ASCII — it avoids encoding problems.
// The convention: camelCase for variables and functions
const userName = 'Ada';
const isLoggedIn = true;
let itemCount = 0;

// PascalCase for classes and constructors
class UserAccount { }
function ShoppingCart() { }

// SCREAMING_SNAKE_CASE for true constants
const MAX_RETRIES = 3;
const API_BASE_URL = 'https://api.example.com';

// A leading underscore signals "internal"
const _cache = new Map();
Start every variable with const

If you later find that a value needs to change, switch that one declaration to let. The result is a codebase where it is immediately obvious which values are fixed and which move.

Data Types

Every value in JavaScript has a type. There are eight, and seven of them are primitives — simple, immutable values.

string

Text. Written with single quotes, double quotes, or backticks. 'hello'

number

Integers and decimals, all one type. 42, 3.14, -7, NaN

boolean

Exactly two values. true or false.

null

Deliberately empty. You assign it when you mean “no value”.

undefined

Not yet assigned. What JavaScript gives an uninitialized variable.

symbol

A unique, immutable key. Rarely used outside libraries.

bigint

Integers larger than 253−1. Written with a trailing n: 9007199254740993n

object

Everything else — objects, arrays, functions, dates, maps, sets.

Checking a type

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Data Types</title>
    <style>
        body { font-family: system-ui, sans-serif; padding: 24px; background: #f8fafc; font-size: 14px; margin: 0; }
        .row {
            background: #fff; border: 1px solid #e2e8f0; border-radius: 10px;
            padding: 10px 14px; margin-bottom: 6px;
            display: flex; justify-content: space-between; gap: 12px;
            font-family: ui-monospace, monospace; font-size: 13px;
        }
        .val { color: #0f172a; }
        .type { color: #0891b2; font-weight: 700; }
        .note { color: #64748b; font-size: 12px; margin-top: 14px; line-height: 1.6; }
    </style>
</head>
<body>

    <div id="output"></div>
    <p class="note">Notice that <code>typeof null</code> returns "object" — a bug from 1995 that was never fixed.</p>

    <script>
        const samples = [
            { v: 'hello',       label: "'hello'" },
            { v: 42,            label: '42' },
            { v: 3.14,          label: '3.14' },
            { v: true,          label: 'true' },
            { v: null,          label: 'null' },
            { v: undefined,     label: 'undefined' },
            { v: [1, 2, 3],     label: '[1, 2, 3]' },
            { v: { a: 1 },      label: '{ a: 1 }' },
            { v: function (){}, label: 'function(){}' }
        ];

        const out = document.getElementById('output');
        samples.forEach(function (s) {
            const div = document.createElement('div');
            div.className = 'row';
            div.innerHTML =
                '<span class="val">' + s.label + '</span>' +
                '<span class="type">' + typeof s.v + '</span>';
            out.appendChild(div);
        });
    </script>

</body>
</html>

Strings: three ways to write them

const a = 'single quotes';
const b = "double quotes";
const c = `backticks — a template literal`;

// Single and double quotes are interchangeable.
// Use whichever avoids escaping:
const d = "It's fine";
const e = 'She said "hello"';

// Backticks are special:
const name = 'Ada';
const greeting = `Hello, ${name}!`;   // interpolation

const multi = `Line one
Line two
Line three`;                          // multi-line without \n

Template literals

Template literals are one of the most useful pieces of syntax in modern JavaScript. They replace string concatenation with something readable.

Concatenation — hard to read
const msg = 'Hello, ' + name + '! You have ' + count + ' new ' + (count === 1 ? 'message' : 'messages') + '.';
Template literal — reads naturally
const msg = `Hello, ${name}! You have ${count} new ${count === 1 ? 'message' : 'messages'}.`;

Anything inside ${ } is evaluated as an expression. That includes function calls, arithmetic, ternaries, and property access.

const price = 19.99;
const qty = 3;

const total = `Total: $${(price * qty).toFixed(2)}`;
// "Total: $59.97"

const user = { first: 'Ada', last: 'Lovelace' };
const full = `${user.first} ${user.last}`;
// "Ada Lovelace"
Backticks are not quotes

A backtick and a single quote look similar in some fonts. If a template literal is not interpolating, check whether you typed a backtick or an apostrophe.

Operators

An operator takes one or two values and produces a result. JavaScript has a lot of them, and a handful behave in ways worth knowing.

Arithmetic

The arithmetic operators
Operator Name Example Result
+Addition5 + 38
-Subtraction5 - 32
*Multiplication5 * 315
/Division10 / 42.5
%Remainder10 % 31
**Exponent2 ** 38
++Incrementlet x = 1; x++2
--Decrementlet x = 1; x--0

The plus sign does two jobs

The + operator adds numbers and concatenates strings. Which one it does depends on the types of the operands, and this is the source of a very common bug.

1 + 2           // 3       — number addition
'1' + '2'       // '12'    — string concatenation
1 + '2'         // '12'    — number converted to string
'3' + 4         // '34'    — same
1 + 2 + '3'     // '33'    — (1+2)=3, then 3+'3'='33'
'1' + 2 + 3     // '123'   — left to right, all concatenation

// The fix: convert to numbers explicitly
Number('1') + 2 + 3         // 6
parseInt('1', 10) + 2 + 3   // 6

Comparison: === versus ==

JavaScript has two equality operators, and choosing the wrong one causes some of the most confusing bugs in the language.

Loose equality — converts types
1 == '1' // true 0 == false // true '' == false // true null == undefined // true [] == false // true '0' == false // true // The rule it follows is not // something you want to // remember.
Strict equality — no conversion
1 === '1' // false 0 === false // false '' === false // false null === undefined // false [] === false // false '0' === false // false // Behaves the way you // expect, every time.

The same distinction applies to inequality: use !== instead of !=.

Comparison operators
Operator Meaning Example
===Strict equal1 === 1 → true
!==Strict not equal1 !== 2 → true
>Greater than5 > 3 → true
<Less than5 < 3 → false
>=Greater or equal5 >= 5 → true
<=Less or equal5 <= 3 → false

Logical operators and short-circuiting

true && true      // true  — AND: both must be true
true && false     // false
true || false     // true  — OR: at least one must be true
false || false    // false
!true             // false — NOT: flips the value

// Short-circuiting — the right side may not run at all
const user = null;
user && user.name;        // null — does not throw
user?.name;                // undefined — optional chaining

// || returns the first truthy value
const port = process.env.PORT || 3000;

// ?? returns the right side only if the left is null or undefined
const count = 0;
count || 10;               // 10  — because 0 is falsy
count ?? 10;               // 0   — because 0 is not nullish
|| versus ??

|| falls back on any falsy value, including 0 and ''. ?? falls back only on null and undefined. When a legitimate value could be zero or an empty string, use ??.

The ternary operator

// condition ? valueIfTrue : valueIfFalse
const label = isLoggedIn ? 'Log out' : 'Log in';

// Nesting works but hurts readability
const grade = score >= 90 ? 'A'
            : score >= 80 ? 'B'
            : score >= 70 ? 'C'
            : 'F';

// For anything more complex, use if/else
let grade2;
if (score >= 90) grade2 = 'A';
else if (score >= 80) grade2 = 'B';
else grade2 = 'F';

Assignment shorthands

let x = 5;

x += 3;    // x = x + 3  → 8
x -= 2;    // x = x - 2  → 6
x *= 2;    // x = x * 2  → 12
x /= 4;    // x = x / 4  → 3
x %= 2;    // x = x % 2  → 1
x **= 3;   // x = x ** 3 → 1

Conditionals

A conditional runs different code depending on whether something is true. JavaScript has three forms.

// 1. if — run the block, or skip it
if (score > 90) {
    console.log('Excellent');
}

// 2. if / else — choose one of two
if (isLoggedIn) {
    showDashboard();
} else {
    showLoginForm();
}

// 3. if / else if / else — choose one of several
if (score >= 90) {
    grade = 'A';
} else if (score >= 80) {
    grade = 'B';
} else if (score >= 70) {
    grade = 'C';
} else {
    grade = 'F';
}

Braces are optional — but always use them

Incorrect — no braces
if (isReady) start(); finish(); // runs regardless! // Only the first statement // belongs to the if. // This bug is very easy // to introduce when adding // a line later.
Correct — braces always
if (isReady) { start(); finish(); } // Both statements belong // to the if. Adding a line // later does not break it.

Truthy and falsy

In a condition, JavaScript converts any value to a boolean. There are exactly eight falsy values, and everything else is truthy.

// The complete list of falsy values
false
0
-0
0n            // BigInt zero
''            // empty string
null
undefined
NaN

// Everything else is truthy — including these surprises:
'0'           // true  — non-empty string
'false'       // true  — non-empty string
[]            // true  — empty array is still an object
{}            // true  — empty object
function(){}  // true
The empty array trap

if ([]) runs the block, because an empty array is truthy. To check whether an array has items, use if (list.length > 0).

switch

A switch statement compares one value against several cases. It uses strict equality, and it needs break after each case unless you want the code to fall through.

switch (status) {
    case 'pending':
        console.log('Waiting');
        break;

    case 'shipped':
        console.log('On the way');
        break;

    case 'delivered':
        console.log('Arrived');
        break;

    default:
        console.log('Unknown status');
}

// Omitting break falls through to the next case:
switch (level) {
    case 1:
    case 2:
        console.log('Beginner');   // runs for both 1 and 2
        break;
    case 3:
        console.log('Intermediate');
        break;
}

Loops

A loop repeats a block of code. JavaScript has several, and the right choice depends on what you are iterating.

// 1. for — when you need a counter
for (let i = 0; i < 5; i++) {
    console.log(i);            // 0 1 2 3 4
}

// 2. for...of — iterate over values in an iterable
const fruits = ['apple', 'banana', 'cherry'];

for (const fruit of fruits) {
    console.log(fruit);
}

// 3. for...in — iterate over the keys of an object
const user = { name: 'Ada', age: 36 };

for (const key in user) {
    console.log(key, user[key]);
}

// 4. while — repeat while a condition holds
let count = 0;
while (count < 3) {
    count++;
}

// 5. do...while — always runs at least once
let input;
do {
    input = getInput();
} while (!isValid(input));
for…in on an array

for...in iterates over keys, not values. On an array it gives you the string indices "0", "1", "2" — and it also walks the prototype chain. Use for...of for arrays.

break and continue

for (const n of [1, 2, 3, 4, 5]) {
    if (n === 3) {
        break;       // stop the loop entirely
    }
    console.log(n);  // 1, 2
}

for (const n of [1, 2, 3, 4, 5]) {
    if (n % 2 === 0) {
        continue;    // skip this iteration
    }
    console.log(n);  // 1, 3, 5
}

Array methods instead of loops

Modern JavaScript often uses array methods instead of a loop. They are shorter and they describe the intent.

A loop
const nums = [1, 2, 3, 4, 5]; const doubled = []; for (let i = 0; i < nums.length; i++) { doubled.push(nums[i] * 2); } // [2, 4, 6, 8, 10]
An array method
const nums = [1, 2, 3, 4, 5]; const doubled = nums.map(n => n * 2); // [2, 4, 6, 8, 10] // Same result, clearer intent.
const nums = [1, 2, 3, 4, 5];

nums.map(n => n * 2);           // [2, 4, 6, 8, 10]  — transform
nums.filter(n => n > 2);        // [3, 4, 5]         — keep some
nums.reduce((sum, n) => sum + n, 0);  // 15          — collapse
nums.find(n => n > 3);          // 4                 — first match
nums.some(n => n > 4);          // true              — any match?
nums.every(n => n > 0);         // true              — all match?
nums.includes(3);               // true

Functions

A function is a reusable block of code. It is the most important building block in the language, and there are several ways to write one.

// 1. Function declaration — hoisted, callable before its line
function add(a, b) {
    return a + b;
}

// 2. Function expression — assigned to a variable
const subtract = function (a, b) {
    return a - b;
};

// 3. Arrow function — shorter, no own `this`
const multiply = (a, b) => {
    return a * b;
};

// 4. Arrow with implicit return — one expression, no braces
const divide = (a, b) => a / b;

// 5. Arrow with a single parameter — parens optional
const square = n => n * n;

// All of these are called the same way
add(2, 3);        // 5
square(4);        // 16

Parameters and arguments

// Default parameter values
function greet(name = 'friend') {
    return `Hello, ${name}!`;
}
greet();          // "Hello, friend!"
greet('Ada');     // "Hello, Ada!"

// Rest parameters — collect the rest into an array
function sum(...nums) {
    return nums.reduce((total, n) => total + n, 0);
}
sum(1, 2, 3);     // 6

// Destructured parameters
function display({ name, age }) {
    return `${name} is ${age}`;
}
display({ name: 'Ada', age: 36 });

// Arguments are passed by value for primitives
function change(x) {
    x = 100;
}
let value = 5;
change(value);
console.log(value);   // 5 — unchanged

// But objects are passed by reference
function mutate(obj) {
    obj.name = 'changed';
}
const user = { name: 'Ada' };
mutate(user);
console.log(user.name);  // "changed"

Return values

// A function with no return gives back undefined
function log(msg) {
    console.log(msg);
}
log('hi');        // undefined

// return ends the function immediately
function findFirst(list, target) {
    for (const item of list) {
        if (item === target) {
            return item;      // stops here
        }
    }
    return null;              // reached only if not found
}

// return with nothing gives undefined
function stop() {
    return;
}

// A function can return a function
function makeCounter() {
    let count = 0;
    return function () {
        count++;
        return count;
    };
}

const counter = makeCounter();
counter();   // 1
counter();   // 2

Arrow functions and this

Arrow functions are not just shorter syntax. They also behave differently with this, which is often exactly what you want in a callback.

A regular function loses this
const timer = { seconds: 0, start: function () { setInterval(function () { this.seconds++; // `this` is not `timer` // TypeError or NaN }, 1000); } };
An arrow inherits this
const timer = { seconds: 0, start: function () { setInterval(() => { this.seconds++; // `this` is `timer` }, 1000); } }; // Arrow functions inherit `this` // from where they are defined.

Immediately Invoked Function Expressions

// An IIFE runs as soon as it is defined
(function () {
    const secret = 'only visible here';
    console.log(secret);
})();

// The parentheses around the function are required —
// without them, the parser treats `function` as a
// declaration and expects a name.

// Arrow version
(() => {
    console.log('also runs immediately');
})();

// IIFEs are rarely needed now that modules
// provide their own scope.

Arrays and Objects

These are the two data structures you will use constantly. An array is an ordered list; an object is a labelled collection.

Arrays

const fruits = ['apple', 'banana', 'cherry'];

// Access by index — counting starts at 0
fruits[0];              // 'apple'
fruits[fruits.length - 1];  // 'cherry' — the last item
fruits.at(-1);          // 'cherry' — modern shorthand

// Length
fruits.length;          // 3

// Add and remove
fruits.push('date');       // add to the end
fruits.pop();              // remove from the end
fruits.unshift('avocado'); // add to the start
fruits.shift();            // remove from the start

// Find
fruits.indexOf('banana');  // 1
fruits.includes('apple');  // true
fruits.find(f => f.length > 5);  // 'banana'

// Combine and copy
const more = ['elderberry'];
const all = [...fruits, ...more];    // a new combined array
const copy = [...fruits];            // a shallow copy
const joined = fruits.join(', ');    // 'apple, banana, cherry'

// Nested arrays
const grid = [
    [1, 2, 3],
    [4, 5, 6]
];
grid[1][2];             // 6

Objects

const user = {
    name: 'Ada',
    age: 36,
    email: 'ada@example.com',
    isAdmin: true
};

// Access with dot notation
user.name;              // 'Ada'

// Or with brackets — required when the key is dynamic
const key = 'age';
user[key];              // 36
user['email'];          // 'ada@example.com'

// Add and remove
user.country = 'UK';
delete user.isAdmin;

// Check if a key exists
'name' in user;                    // true
Object.hasOwn(user, 'name');       // true — modern
user.phone === undefined;          // true — but unreliable

// Keys and values
Object.keys(user);      // ['name', 'age', 'email', 'country']
Object.values(user);    // ['Ada', 36, 'ada@example.com', 'UK']
Object.entries(user);   // [['name','Ada'], ['age',36], ...]

// Nested objects
const order = {
    id: 101,
    customer: {
        name: 'Ada',
        address: { city: 'London' }
    }
};

order.customer.address.city;   // 'London'
order.customer?.phone;         // undefined, no error

Shorthand syntax

const name = 'Ada';
const age = 36;

// Old way
const user1 = { name: name, age: age };

// Shorthand — the key and the variable have the same name
const user2 = { name, age };

// Methods can be shortened too
const calculator = {
    // Old
    add: function (a, b) { return a + b; },

    // New
    subtract(a, b) { return a - b; }
};

// Computed keys — the key is an expression
const field = 'email';
const record = {
    [field]: 'ada@example.com'
};
// { email: 'ada@example.com' }

Arrays of objects

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Arrays of Objects</title>
    <style>
        body { font-family: system-ui, sans-serif; padding: 24px; background: #f8fafc; font-size: 14px; margin: 0; }
        .card {
            background: #fff; border: 1px solid #e2e8f0; border-radius: 12px;
            padding: 14px 16px; margin-bottom: 10px;
        }
        .name { font-weight: 700; color: #0f172a; margin-bottom: 4px; }
        .meta { color: #0891b2; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; }
        .email { color: #64748b; font-size: 13px; margin-top: 4px; font-family: ui-monospace, monospace; }
    </style>
</head>
<body>

    <div id="list"></div>

    <script>
        const team = [
            { name: 'Ada Lovelace',  role: 'Engineer',   email: 'ada@example.com' },
            { name: 'Grace Hopper',  role: 'Admiral',    email: 'grace@example.com' },
            { name: 'Alan Turing',   role: 'Researcher', email: 'alan@example.com' }
        ];

        const container = document.getElementById('list');

        team.forEach(function (person) {
            const card = document.createElement('div');
            card.className = 'card';
            card.innerHTML =
                '<div class="name">' + person.name + '</div>' +
                '<div class="meta">' + person.role + '</div>' +
                '<div class="email">' + person.email + '</div>';
            container.appendChild(card);
        });
    </script>

</body>
</html>

Modern Syntax: Destructuring and Spread

Two pieces of syntax added in ES6 make working with objects and arrays dramatically shorter. They appear in almost every modern codebase.

Destructuring

// Object destructuring — extract by key name
const user = { name: 'Ada', age: 36, email: 'ada@example.com' };

const { name, age } = user;
// name = 'Ada', age = 36

// Rename while destructuring
const { name: fullName, email: contact } = user;
// fullName = 'Ada', contact = 'ada@example.com'

// Default values
const { phone = 'none on file' } = user;

// Array destructuring — extract by position
const coords = [10, 20, 30];
const [x, y] = coords;
// x = 10, y = 20

// Skip positions with a comma
const [, , z] = coords;
// z = 30

// Rest in destructuring
const [first, ...others] = coords;
// first = 10, others = [20, 30]

const { name: n, ...rest } = user;
// n = 'Ada', rest = { age: 36, email: '...' }

// In function parameters
function display({ name, role = 'member' }) {
    return `${name} (${role})`;
}
display({ name: 'Ada' });   // "Ada (member)"

// Swapping variables without a temp
let a = 1, b = 2;
[a, b] = [b, a];
// a = 2, b = 1

Spread and rest

Both use ..., but they do opposite things. Spread expands an iterable into individual values. Rest collects individual values into an array.

// Spread — expand
const a = [1, 2, 3];
const b = [4, 5, 6];

const combined = [...a, ...b];       // [1, 2, 3, 4, 5, 6]
const copyOfA  = [...a];             // a shallow copy
const max      = Math.max(...a);     // 3 — spreads into arguments

// Spread in objects — a shallow merge
const defaults = { theme: 'light', lang: 'en' };
const prefs    = { theme: 'dark' };

const settings = { ...defaults, ...prefs };
// { theme: 'dark', lang: 'en' } — later keys win

// Adding a property without mutating the original
const updated = { ...user, age: 37 };

// Rest — collect
function sum(...nums) {
    return nums.reduce((t, n) => t + n, 0);
}
sum(1, 2, 3, 4);      // 10

const { id, ...withoutId } = user;
Spread is shallow

{ ...obj } copies the top-level properties only. A nested object is still shared by reference, so mutating it affects both the copy and the original.

Optional chaining and nullish coalescing

const order = {
    id: 101,
    customer: {
        name: 'Ada'
    }
};

// Without optional chaining
const city1 = order && order.customer && order.customer.address
    && order.customer.address.city;

// With optional chaining
const city2 = order?.customer?.address?.city;   // undefined, no error

// On method calls
obj.method?.();       // calls only if the method exists

// On array indexes
arr?.[0];

// Combined with nullish coalescing
const city = order?.customer?.address?.city ?? 'Unknown';

Classes

A class is a template for creating objects with shared structure and behavior. JavaScript classes are built on prototypes, but the syntax is more familiar.

class User {
    // Runs when you call `new User(...)`
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    // A method — available on every instance
    greet() {
        return `Hello, I'm ${this.name}`;
    }

    // A getter — accessed like a property
    get description() {
        return `${this.name}, ${this.age}`;
    }

    // A setter
    set nickname(value) {
        this.name = value;
    }

    // A static method — belongs to the class, not instances
    static create(data) {
        return new User(data.name, data.age);
    }
}

const ada = new User('Ada', 36);

ada.greet();          // "Hello, I'm Ada"
ada.description;      // "Ada, 36"
ada.name = 'Grace';
User.create({ name: 'Alan', age: 41 });

Inheritance

class Animal {
    constructor(name) {
        this.name = name;
    }

    speak() {
        return `${this.name} makes a sound`;
    }
}

class Dog extends Animal {
    constructor(name, breed) {
        super(name);          // call the parent constructor
        this.breed = breed;
    }

    speak() {
        return `${this.name} barks`;   // override
    }

    describe() {
        return `${super.speak()} — a ${this.breed}`;  // call the parent
    }
}

const rex = new Dog('Rex', 'Labrador');
rex.speak();        // "Rex barks"
rex.describe();     // "Rex makes a sound — a Labrador"
rex instanceof Dog;      // true
rex instanceof Animal;   // true

Private fields

class Counter {
    // A private field — the # prefix makes it inaccessible outside
    #count = 0;

    increment() {
        this.#count++;
        return this.#count;
    }

    get value() {
        return this.#count;
    }
}

const c = new Counter();
c.increment();      // 1
c.increment();      // 2
c.value;            // 2
// c.#count;        // SyntaxError — private

Modules

A module is a file with its own scope that declares what it exports and what it imports. The syntax is built into the language.

// utils.js — exporting

export const TAX_RATE = 0.2;

export function formatPrice(value) {
    return '$' + value.toFixed(2);
}

// Default export — one per module
export default function calculateTotal(items) {
    return items.reduce((sum, item) => sum + item.price, 0);
}
// app.js — importing

// Named imports — must match the exported names
import { TAX_RATE, formatPrice } from './utils.js';

// Default import — any name works
import calculateTotal from './utils.js';

// Both together
import calculateTotal, { TAX_RATE } from './utils.js';

// Renaming
import { formatPrice as fmt } from './utils.js';

// Import everything as a namespace
import * as utils from './utils.js';
utils.formatPrice(9.99);
<script type="module" src="app.js"></script>
Two things that trip people up

The file extension is required — './utils.js', not './utils'. And modules will not load from file://; you need a local server because of CORS.

Error Handling

When something goes wrong at runtime, JavaScript throws an exception. You can catch it and handle it rather than letting it stop the program.

try {
    const data = JSON.parse(malformedString);
    console.log(data);
} catch (error) {
    // Runs only if something in the try block throws
    console.error('Could not parse:', error.message);
} finally {
    // Always runs — with or without an error
    console.log('Done');
}

Throwing your own errors

function divide(a, b) {
    if (b === 0) {
        throw new Error('Cannot divide by zero');
    }
    return a / b;
}

try {
    divide(10, 0);
} catch (error) {
    console.log(error.name);      // "Error"
    console.log(error.message);   // "Cannot divide by zero"
}

// Custom error classes
class ValidationError extends Error {
    constructor(field) {
        super(`Invalid value for ${field}`);
        this.name = 'ValidationError';
        this.field = field;
    }
}

throw new ValidationError('email');

The built-in error types

Common built-in errors
Error Thrown when
SyntaxErrorThe code cannot be parsed — a missing bracket, an unclosed string
ReferenceErrorA variable does not exist
TypeErrorA value is not the type the operation expects
RangeErrorA number is outside the allowed range, or the call stack overflowed
URIErrorA URI function received a malformed argument
EvalErrorRarely seen — related to the eval function
Catch only what you can handle

An empty catch block hides bugs. If you cannot do something useful with the error — log it, retry, show a message — let it propagate.

Strict Mode

Strict mode turns several silent mistakes into errors. It is opt-in for a script, but automatic inside modules and classes.

// Add this at the top of a file or function
'use strict';

// Now these all throw:
x = 10;                    // ReferenceError — undeclared variable

function f(a, a) { }       // SyntaxError — duplicate parameter

const obj = {};
Object.defineProperty(obj, 'x', { value: 1 });
delete obj.x;              // TypeError — cannot delete non-configurable

// And `this` inside a plain function call is undefined
// instead of the global object

function whoAmI() {
    return this;
}
whoAmI();                  // undefined in strict mode
                           // the global object otherwise
Modules are strict by default

Any file loaded with type="module", and any code inside a class body, runs in strict mode automatically. You do not need the directive.

Common Syntax Mistakes

These are the errors that break scripts most often. Each one produces a SyntaxError or a silent bug, and each one is easy to fix once you recognize it.

1. A missing closing bracket

Incorrect
function greet(name) { if (name) { return 'Hello, ' + name; // missing closing brace for the if } greet('Ada');
SyntaxError: missing } after function body The parser reaches the end of the file still expecting a closing brace.

2. A missing closing parenthesis in a call

Incorrect
console.log('hello'; // or setTimeout(function () { doWork(); }, 1000;
SyntaxError: missing ) after argument list Every opening parenthesis needs a matching close.

3. An unclosed string

Incorrect
const name = "Ada; console.log(name); // or const msg = 'It's fine'; // The apostrophe ends the string early.
SyntaxError: Invalid or unexpected token The parser never finds the closing quote on that line.

The second case — an apostrophe inside a single-quoted string — is worth remembering. Use double quotes, or escape it: 'It\'s fine'.

4. Using = where === is meant

Incorrect — assignment in a condition
if (x = 5) { // Always true — 5 is truthy // and x is now 5 } // This is valid syntax and a // silent bug. No error at all.
Correct — comparison
if (x === 5) { // Runs only when x is 5 } // Strict equality, as intended.

5. A missing comma between object properties

Incorrect
const user = { name: 'Ada' age: 36 }; // Missing comma after 'Ada'
SyntaxError: Unexpected identifier ‘age’ The parser reads the next line as part of the same value.

6. Using a reserved word as a variable name

Incorrect
const class = 'beginner'; let return = 5; var if = true;
SyntaxError: Unexpected token ‘class’ Keywords cannot be used as identifiers.

7. Duplicate const declarations in the same scope

Incorrect
const name = 'Ada'; const name = 'Grace'; // SyntaxError
SyntaxError: Identifier ‘name’ has already been declared const and let cannot be redeclared in the same scope.

8. Accessing a variable before its let/const declaration

Incorrect
console.log(count); let count = 5; // ReferenceError: Cannot access // 'count' before initialization
ReferenceError — a runtime error, not a syntax error The code is valid, but it runs in the wrong order.

9. Missing parentheses on an arrow function

Incorrect — returns undefined
const double = n => { n * 2 }; double(5); // undefined // With braces, you need an // explicit return.
Correct
const double = n => n * 2; // or const double2 = n => { return n * 2; }; double(5); // 10

10. Confusing a block with an object literal

Incorrect — parsed as a block
{ name: 'Ada', age: 36 } // The parser sees a block containing // a labelled statement and an // expression. Nothing is assigned.
Correct — assign it
const user = { name: 'Ada', age: 36 }; // Or wrap in parentheses when // returning an object from an // arrow function: const make = () => ({ a: 1 });
Read the first error, not the last

A single syntax error often produces a cascade of messages as the parser tries to recover. The first one is the real problem; the rest are consequences. Fix the first and reload.

Best Practices

These conventions are not enforced by the language, but they are near-universal in modern JavaScript codebases.

🔒

Use const by default

Reach for const first, let when the value must change, and avoid var.

🎯

Always use ===

Strict equality avoids the type conversion surprises of ==.

;️⃣

Write semicolons

ASI usually works, but explicit semicolons remove the ambiguity entirely.

{ }

Always use braces

Wrap every if and loop body in braces, even for one statement.

🏷️

Name things clearly

userAge, not ua. isLoggedIn, not flag.

`

Prefer template literals

Backticks read better than string concatenation with +.

📦

Destructure what you need

const { name, age } = user; is clearer than repeated property access.

❓

Use ?? for defaults

When 0 or '' are valid values, ?? is safer than ||.

📏

Let a formatter decide

Prettier or ESLint removes every argument about spacing, quotes, and line breaks.

The one-line summary

Use const, use ===, write semicolons and braces, prefer template literals, and let a formatter handle the rest.

Frequently Asked Questions

1. What is JavaScript syntax?

JavaScript syntax is the set of rules that defines how a program is written: where semicolons go, how variables are declared, how functions are defined, how blocks are formed with curly braces, and which keywords the language reserves.

2. Are semicolons required in JavaScript?

Semicolons are optional in most cases because JavaScript inserts them automatically in a process called ASI. However, automatic insertion has rules that occasionally guess wrong, so writing semicolons explicitly is the safer and more common convention.

3. What is the difference between let, const, and var?

let declares a block-scoped variable that can be reassigned. const declares a block-scoped variable that cannot be reassigned. var is the older syntax, is function-scoped rather than block-scoped, and has hoisting behavior that causes confusing bugs.

4. When should I use double quotes, single quotes, or backticks?

Single and double quotes are interchangeable for plain strings. Backticks create template literals, which allow embedded expressions with ${} and multi-line strings without escape characters.

5. What is the difference between == and ===?

== compares values after converting them to a common type, which produces surprising results. === compares both value and type without conversion. Almost always use === because it behaves the way you expect.

6. What is a JavaScript statement?

A statement is a single instruction the engine executes, such as declaring a variable, calling a function, or evaluating an expression. Statements are usually terminated by a semicolon, though it is often optional.

7. What is the difference between an expression and a statement?

An expression produces a value, such as 2 + 3 or a function call. A statement performs an action, such as an if block or a variable declaration. Some constructs, like function declarations, are statements; others, like arrow functions, are expressions.

8. What are the primitive data types in JavaScript?

There are seven: string, number, boolean, null, undefined, symbol, and bigint. Everything else, including arrays and functions, is an object.

9. What is a template literal?

A template literal is a string written with backticks. It supports embedded expressions through ${...} and can span multiple lines without escape characters, making it far more readable than string concatenation.

10. What is destructuring?

Destructuring is a syntax for pulling values out of arrays and objects into individual variables. const { name, age } = user extracts the name and age properties in a single statement.

11. What is the spread operator?

The spread operator is written with three dots and expands an iterable into individual values. [...a, ...b] creates a new array combining a and b, and { ...obj, extra: 1 } creates a shallow copy with an added property.

12. What does strict mode do?

Strict mode turns silent mistakes into errors. Assigning to an undeclared variable throws, duplicate parameter names throw, and this inside a plain function call becomes undefined instead of the global object. Add "use strict", or use ES modules, which are strict by default.

Key Takeaways

  • A statement performs an action; an expression produces a value.
  • Semicolons are optional, but ASI can guess wrong — write them.
  • Curly braces are a block in one context and an object literal in another.
  • Use const by default, let when reassigning, avoid var.
  • There are seven primitive types plus objects.
  • Template literals use backticks and support ${ } interpolation.
  • Use === and !==, never == and !=.
  • ?? falls back only on null and undefined; || falls back on any falsy value.
  • Arrow functions have no own this — they inherit it.
  • Destructuring and spread shorten the most common object and array operations.
  • Classes are built on prototypes, with # for private fields.
  • Always use braces, and read the first error message rather than the last.

What to Learn Next

Now that the syntax is clear, the next step is the language underneath it. Read How JavaScript Works for the call stack, the event loop, closures, and this.

From there, explore JavaScript Functions Explained, JavaScript Array Methods, and JavaScript Objects. When you are ready to work with the page, read Working with the DOM and JavaScript Events.

If you are still connecting the pieces, read What Is JavaScript?, JavaScript vs TypeScript, and How JavaScript Runs in a Browser for the surrounding context.

Practice Challenge

The fastest way to internalize JavaScript syntax is to write it, break it, and read the errors. This exercise takes about thirty minutes.

  1. Create index.html and app.js, and load the script with <script src="app.js" defer></script>.
  2. In app.js, declare a const for your name, a let for a counter starting at zero, and a template literal that combines them. Log the result.
  3. Try to reassign the const. Read the error in the console and note the exact wording.
  4. Write an object with three properties. Access one with dot notation and one with bracket notation using a variable key.
  5. Destructure two properties from that object into new variables and log them.
  6. Write an array of five numbers. Use map to double them and filter to keep only the ones above five.
  7. Use the spread operator to combine that array with a second array into a new one.
  8. Write a function declaration, a function expression, and an arrow function that all do the same thing. Call each one.
  9. Write an object method that uses this, then pass it as a callback to setTimeout. Watch this break. Fix it with an arrow function.
  10. Write a class with a constructor, a method, and a private field. Instantiate it and try to read the private field from outside — note the error.
  11. Now break the syntax deliberately, one at a time: remove a closing brace, remove a closing parenthesis, unclose a string, and use = in an if condition. Read each error message and note which ones are caught at parse time versus runtime.
  12. Finally, install Prettier or ESLint and let it reformat the file. Compare the result to what you wrote.

If you complete those steps, you have written every major piece of JavaScript syntax, watched a const refuse reassignment, seen this break in a callback, and produced four different error messages by hand. That last part is the most valuable: once you have caused each error yourself and read its message, you will recognize it instantly the next time it appears in a real project.

Inar Learn
Inar Learnhttps://inarlearn.com
Inar Learn is an innovative online learning platform offering high-quality courses, tutorials, and resources to help learners gain practical skills and grow their knowledge.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here