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.
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.
const name = "Ada
console.log(name);
// Missing closing quote.
// The parser throws before
// any code executes.
// SyntaxError: Invalid or
// unexpected tokenconst name = "Ada";
console.log(name);
console.log(age);
// Valid syntax. Runs fine
// until line 3, where `age`
// does not exist.
// ReferenceError: age is
// not definedStatements and expressions
Almost everything in JavaScript is either a statement or an expression. The distinction matters more than it first appears.
Produces a value
2 + 3 produces 5.
"hello" produces a string.
() => 42 produces a function.
An expression can be assigned, passed, or returned.
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.
[, (, `, +, 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.
if (isReady) {
start();
finish();
}
// The braces contain statements.
// No value is produced.
function run() {
doWork();
}
// Same thing — a block.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.
// 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.
| 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_.2ndPlaceis invalid;secondPlaceis fine. - Case matters.
userNameandusernameare two different variables. - Reserved words cannot be used.
let if = 5is 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();
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.
Text. Written with single quotes, double quotes, or backticks. 'hello'
Integers and decimals, all one type. 42, 3.14, -7, NaN
Exactly two values. true or false.
Deliberately empty. You assign it when you mean “no value”.
Not yet assigned. What JavaScript gives an uninitialized variable.
A unique, immutable key. Rarely used outside libraries.
Integers larger than 253−1. Written with a trailing n: 9007199254740993n
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.
const msg = 'Hello, ' + name +
'! You have ' + count +
' new ' + (count === 1
? 'message'
: 'messages') + '.';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"
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
| Operator | Name | Example | Result |
|---|---|---|---|
+ | Addition | 5 + 3 | 8 |
- | Subtraction | 5 - 3 | 2 |
* | Multiplication | 5 * 3 | 15 |
/ | Division | 10 / 4 | 2.5 |
% | Remainder | 10 % 3 | 1 |
** | Exponent | 2 ** 3 | 8 |
++ | Increment | let x = 1; x++ | 2 |
-- | Decrement | let 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.
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.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
!=.
| Operator | Meaning | Example |
|---|---|---|
=== | Strict equal | 1 === 1 → true |
!== | Strict not equal | 1 !== 2 → true |
> | Greater than | 5 > 3 → true |
< | Less than | 5 < 3 → false |
>= | Greater or equal | 5 >= 5 → true |
<= | Less or equal | 5 <= 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
|| 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
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.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
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 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.
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]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.
const timer = {
seconds: 0,
start: function () {
setInterval(function () {
this.seconds++; // `this` is not `timer`
// TypeError or NaN
}, 1000);
}
};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;
{ ...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>
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
| Error | Thrown when |
|---|---|
SyntaxError | The code cannot be parsed — a missing bracket, an unclosed string |
ReferenceError | A variable does not exist |
TypeError | A value is not the type the operation expects |
RangeError | A number is outside the allowed range, or the call stack overflowed |
URIError | A URI function received a malformed argument |
EvalError | Rarely seen — related to the eval function |
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
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
function greet(name) {
if (name) {
return 'Hello, ' + name;
// missing closing brace for the if
}
greet('Ada');2. A missing closing parenthesis in a call
console.log('hello';
// or
setTimeout(function () {
doWork();
}, 1000;3. An unclosed string
const name = "Ada;
console.log(name);
// or
const msg = 'It's fine';
// The apostrophe ends the string early.
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
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.if (x === 5) {
// Runs only when x is 5
}
// Strict equality, as intended.5. A missing comma between object properties
const user = {
name: 'Ada'
age: 36
};
// Missing comma after 'Ada'6. Using a reserved word as a variable name
const class = 'beginner';
let return = 5;
var if = true;7. Duplicate const declarations in the same scope
const name = 'Ada';
const name = 'Grace'; // SyntaxErrorconst and let cannot be redeclared in the same scope.
8. Accessing a variable before its let/const declaration
console.log(count);
let count = 5;
// ReferenceError: Cannot access
// 'count' before initialization9. Missing parentheses on an arrow function
const double = n => { n * 2 };
double(5); // undefined
// With braces, you need an
// explicit return.const double = n => n * 2;
// or
const double2 = n => { return n * 2; };
double(5); // 1010. Confusing a block with an object literal
{
name: 'Ada',
age: 36
}
// The parser sees a block containing
// a labelled statement and an
// expression. Nothing is assigned.const user = {
name: 'Ada',
age: 36
};
// Or wrap in parentheses when
// returning an object from an
// arrow function:
const make = () => ({ a: 1 });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.
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
constby default,letwhen reassigning, avoidvar. - There are seven primitive types plus objects.
- Template literals use backticks and support
${ }interpolation. - Use
===and!==, never==and!=. ??falls back only onnullandundefined;||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.
- Create
index.htmlandapp.js, and load the script with<script src="app.js" defer></script>. - In
app.js, declare aconstfor your name, aletfor a counter starting at zero, and a template literal that combines them. Log the result. - Try to reassign the
const. Read the error in the console and note the exact wording. - Write an object with three properties. Access one with dot notation and one with bracket notation using a variable key.
- Destructure two properties from that object into new variables and log them.
- Write an array of five numbers. Use
mapto double them andfilterto keep only the ones above five. - Use the spread operator to combine that array with a second array into a new one.
- Write a function declaration, a function expression, and an arrow function that all do the same thing. Call each one.
- Write an object method that uses
this, then pass it as a callback tosetTimeout. Watchthisbreak. Fix it with an arrow function. - 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.
- Now break the syntax deliberately, one at a time: remove a closing brace, remove a closing parenthesis, unclose a string, and use
=in anifcondition. Read each error message and note which ones are caught at parse time versus runtime. - 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.