JavaScript specials

You’ve now got a working toolkit: variables, types, operators, branches, loops, and functions. This chapter walks back over the whole set, but not as a flat list. The goal is to lock in the subtle moments — the places where JavaScript does something you wouldn’t guess — and to give you a single page you can skim before moving on.

Each section ends with a link to the full lesson, so treat this as a map. If a topic feels shaky, follow the link.

Code structure

Statements are separated by a semicolon:

alert('Hello'); alert('World');

A line break usually counts as a separator too, so this behaves the same way:

alert('Hello')
alert('World')

That convenience is called automatic semicolon insertion (ASI): the engine looks at the line break and, in most cases, acts as if you’d typed a semicolon. The trouble is that word most. ASI has rules, and when a line ends in a way that could continue onto the next line, the engine keeps reading instead of inserting a semicolon.

Here’s the classic trap:

alert("There will be an error after this message")

[1, 2].forEach(alert)

You wrote two statements. The engine sees one. Because a value followed by [...] is valid syntax — that’s property access — it glues the lines together into alert("...")[1, 2].forEach(alert) and then blows up at runtime.

what you meant
alert(“…”)
[1, 2].forEach(alert)
what the engine read
alert(“…”)[1, 2].forEach(alert)  // runtime error
How ASI reads the two lines above: the opening bracket makes the second line look like a continuation of the first.

The fix is dull and reliable: end every statement with a semicolon. Most style guides insist on it precisely because it removes this whole category of surprise.

Semicolons are not needed after code blocks {...} or the constructs built on them, like loops and function declarations:

function f() {
  // no semicolon needed after a function declaration
}

for (;;) {
  // no semicolon needed after the loop
}

And if you do add an extra semicolon where none is required, nothing breaks — the engine treats it as an empty statement and ignores it.

More in: Code structure.

Strict mode

To switch on all of modern JavaScript, start the script with the directive "use strict":

'use strict';

// ...modern behavior from here on

It has to sit at the very top of the script, or at the very top of a function body — nothing but comments may come before it. Put it lower down and it silently does nothing.

Leave it out and your code still runs, but a handful of features fall back to their old, backward-compatible behavior, some of which papers over mistakes you’d rather see. Strict mode is the setting you want.

One convenience: several modern constructs turn strict mode on for you. Code inside a class, and the body of any JavaScript module, is strict automatically — no directive required.

More in: The modern mode, “use strict”.

Variables

You can declare variables with:

  • let — a normal variable you can reassign.
  • const — a constant; the binding can’t be reassigned after it’s set.
  • var — the old-school declaration, with its own quirks we’ll meet later.

Naming rules for any of them:

  • Use letters, digits, and the symbols $ and _. The $ and _ count as ordinary name characters, on par with letters.
  • The first character can’t be a digit.
  • Non-Latin letters are technically allowed, but real codebases stick to English names.

Variables are dynamically typed — one variable can hold any value, and you can swap the value’s type freely:

let x = 5;
x = "Maya"; // was a number, now a string — perfectly legal

There are 8 data types in the language:

  • number — integers and floating-point numbers alike.
  • bigint — integers of arbitrary size, past the safe range of number.
  • string — text.
  • booleantrue or false.
  • null — a type with the single value null, meaning “empty” or “nothing here”.
  • undefined — a type with the single value undefined, meaning “not assigned yet”.
  • object — for structured collections of data.
  • symbol — for unique identifiers.
number
bigint
string
boolean
null
undefined
symbol
object
Seven of the eight types hold a single primitive value; object holds collections.

The typeof operator reports a value’s type as a string. It’s honest almost everywhere, with two famous exceptions worth memorizing:

typeof null == "object"        // "object" — a long-standing bug in the language, kept for compatibility
typeof function(){} == "function" // functions report "function", not "object"

The first is a mistake baked into JavaScript since the beginning; null is not an object, but typeof says so anyway. The second is a deliberate convenience — functions are technically objects, but typeof singles them out so you can test for callability.

More in: Variables and Data types.

Interaction

We’re learning in the browser, so the three simplest ways to talk to a user are built in:

prompt(question, [default])

Ask a question with a text field. Returns whatever the visitor typed, or null if they hit Cancel.

confirm(question)

Ask a question with OK and Cancel buttons. Returns true for OK, false for Cancel.

alert(message)

Show a message and wait for the visitor to dismiss it.

All three are modal: they freeze the script and block the page until the visitor answers. Nothing else runs, and the user can’t touch the page, until the dialog closes.

let userName = prompt("Your name?", "Nadia");
let isTeaWanted = confirm("Do you want some tea?");

alert( "Visitor: " + userName ); // Nadia
alert( "Tea wanted: " + isTeaWanted ); // true

One detail that trips people up: prompt always hands back a string, even when the visitor types digits. prompt("Age?") for someone typing 30 gives you "30", not 30. Convert before doing math.

More in: Interaction: alert, prompt, confirm.

Operators

JavaScript’s operators, grouped by what they do:

Arithmetic

The usual + - * /, plus % for the remainder and ** for exponentiation.

The binary + does double duty. With numbers it adds; with a string on either side it concatenates, converting the other operand to a string first:

alert( '1' + 2 ); // '12' — string, because '1' is a string
alert( 1 + '2' ); // '12' — same reason

That asymmetry is the source of countless bugs, so keep it in mind whenever a + meets a string.

Assignments

Plain assignment a = b, and the combined forms like a *= 2 (short for a = a * 2). Assignment is itself an expression that returns the assigned value, which is why a = b = c works.

Bitwise

Bitwise operators treat numbers as 32-bit integers and work bit by bit. You’ll rarely need them; reach for the reference when you do.

Conditional (ternary)

The one operator that takes three parts: cond ? resultA : resultB. If cond is truthy the whole thing evaluates to resultA, otherwise resultB. It’s an expression, so it produces a value you can assign or return.

Logical

&& (AND) and || (OR) short-circuit: they stop as soon as the result is decided, and they return the operand where they stopped — not necessarily a boolean. ! (NOT) converts its operand to boolean and flips it.

alert( 1 && 5 );   // 5  — both truthy, returns the last
alert( null || "fallback" ); // "fallback" — first is falsy, returns the second
Nullish coalescing

a ?? b picks the first defined value: it returns a unless a is null or undefined, in which case it returns b. Unlike ||, it doesn’t treat 0 or "" as a reason to fall through — only null/undefined do that.

Comparisons

Loose equality == converts operands of different types to numbers before comparing, which produces some famous surprises:

alert( 0 == false ); // true — false becomes 0
alert( 0 == '' );    // true — '' becomes 0

Other comparisons (<, >, <=, >=) also convert to numbers, with one exception: when both sides are strings, they compare character by character in dictionary order.

Strict equality === skips all conversion. Different types are simply not equal, full stop — which is why it’s the default you should reach for.

null and undefined are a special pair: with == they equal each other and nothing else. Don’t rely on them converting like other values.

alert( null == undefined ); // true
alert( null == 0 );         // false — no conversion here
Others

A few more exist, such as the comma operator, which evaluates several expressions and returns the last one.

More in: Basic operators, maths, Comparisons, Logical operators, Nullish coalescing operator ‘??’.

Loops

We covered three loop shapes. They all repeat a block, but they check the condition at different moments:

// 1 — check first, then maybe run
while (condition) {
  // ...
}

// 2 — run once, then check
do {
  // ...
} while (condition);

// 3 — counter loop: init, check, step
for (let i = 0; i < 10; i++) {
  // ...
}
while
check
body
do..while
body
check
for
init
check
body
step
Where each loop checks its condition. do..while is the only one guaranteed to run its body at least once.

A few things worth remembering:

  • A variable declared in the for (let ...) header lives only inside the loop. You can drop the let and reuse an outer variable instead, though the scoped version is cleaner.
  • break bails out of the whole loop; continue skips to the next iteration.
  • To break out of nested loops in one jump, label the outer loop and write break labelName.

Details in: Loops: while and for.

Later you’ll meet loops built specifically for walking through objects and other collections.

The “switch” construct

switch replaces a stack of if checks against the same value. It compares each case with the switch value using ===strict equality, no type conversion.

That strictness is exactly where the classic mistake lives:

let age = prompt('Your age?', 18);

switch (age) {
  case 18:
    alert("Won't work"); // prompt returns a string "18", not the number 18
    break;

  case "18":
    alert("This works!"); // string matches string
    break;

  default:
    alert("Any value not equal to one above");
}

Because prompt gives back the string "18", and switch uses ===, the numeric case 18 never matches. Only case "18" does. When your switch value comes from user input, either convert it up front or write string cases.

Don’t forget break at the end of each case. Leave it off and execution falls through into the next case — occasionally useful, usually a bug.

Details in: The “switch” statement.

Functions

We’ve written functions three ways. They differ in syntax and in a couple of behaviors, but each one packages code you can call by name.

Declaration   function sum(a, b) { … }

Expression   let sum = function(a, b) { … };

Arrow   let sum = (a, b) => a + b;

The three function forms. Declarations are hoisted; expressions and arrows are created when execution reaches them.

1. Function Declaration — stands on its own in the main flow of code:

function sum(a, b) {
  let result = a + b;

  return result;
}

2. Function Expression — created as part of a larger statement, here assigned to a variable:

let sum = function(a, b) {
  let result = a + b;

  return result;
};

The practical difference: a declaration is hoisted, so you can call it above where it’s written. An expression only exists once execution reaches that line.

3. Arrow functions — a compact expression syntax:

// single expression on the right — its value is returned automatically
let sum = (a, b) => a + b;

// multi-line body with { ... } needs an explicit return
let sum2 = (a, b) => {
  // ...
  return a + b;
};

// no arguments — keep the empty parentheses
let sayHi = () => alert("Hello");

// exactly one argument — parentheses optional
let double = n => n * 2;

A few rules that hold across all three:

  • Local variables — anything declared in the body, plus the parameters, is visible only inside the function.
  • Default parameters — give a parameter a fallback with =, as in function sum(a = 1, b = 2) {...}. The default kicks in when the argument is missing or undefined.
  • Always a return value — a function that never hits a return (or hits a bare return;) still returns something: undefined.

Details: see Functions, Arrow functions, the basics.

More to come

That’s the whole First Steps toolkit on one page. Everything so far is the foundation — the syntax and behavior you’ll lean on constantly. From here the course digs into the parts that make JavaScript genuinely powerful: objects, prototypes, closures, and the async model. The specials and sharp edges keep coming, and now you have the base to make sense of them.