Skip to main content

Command Palette

Search for a command to run...

JavaScript Statements: Unlocking the Power of Code Control

Updated
15 min readView as Markdown
JavaScript Statements: Unlocking the Power of Code Control
G

Hello! I'm a dedicated software developer with a passion for coding and a belief in technology's impact on our world. My programming journey started a few years ago and has reshaped my career and mindset. I love tackling complex problems and creating efficient code. My skills cover various languages and technologies like JavaScript, Angular, ReactJS, NodeJs, and Go Lang. I stay updated on industry trends and enjoy learning new tools. Outside of coding, I cherish small routines that enhance my workday, like sipping tea, which fuels my creativity and concentration. Whether debugging or brainstorming, it helps me focus. When I'm not coding, I engage with fellow developers. I value teamwork and enjoy mentoring newcomers and sharing my knowledge to help them grow. Additionally, I explore the blend of technology and creativity through projects that incorporate art and data visualization. This keeps my perspective fresh and my passion alive. I'm always seeking new challenges, from open-source contributions to hackathons and exploring AI. Software development is more than a job for me—it's a passion that drives continuous learning and innovation.

Last updated: September 2026 — added for...in, labeled statements, throw and error types, optional catch binding; fixed corrupted code blocks.

JavaScript statements are the instructions that make up a program. Expressions produce values; statements make things happen — they declare variables, choose between paths, repeat work, and jump out of it.

This article covers every statement type in the language, what each one is for, and the ones that catch people out.

Expression Statement: The Building Blocks of JavaScript

An expression statement is an expression used on its own as a statement. It performs an action or calculation, and the resulting value is either assigned somewhere or discarded.

let greeting = "Hello, World!";

Here we declare a variable named greeting and assign it the string "Hello, World!". Function calls, assignments and increments are all expression statements:

count++;
doSomething();
user.name = "Ganesh";

Compound and Empty Statements: The Pioneers of Code Composition

Two statement types shape the structure of your code rather than doing work themselves.

Compound Statements: The Art of Encapsulation

A compound statement — also called a block — is a group of statements enclosed in curly braces {}. It lets you use several statements anywhere the syntax allows one, which is what makes functions, loops and conditionals possible.

if (condition) {
    // This is a compound statement
    console.log("Condition is true.");
    // Additional statements go here
}

Blocks also create scope for let and const — a variable declared inside a block does not exist outside it. var ignores blocks entirely, which is one of the reasons to avoid it.

Empty Statements: The Puzzling Semicolon

The empty statement is a single semicolon ; and does nothing. It exists because the grammar sometimes requires a statement where you have no work to do.

It shows up when a loop's entire job is done in its header:

let sum = 0;
const numbers = [1, 2, 3];

for (let i = 0; i < numbers.length; sum += numbers[i++]);   // <-- the ; IS the body

console.log(sum);   // 6

That trailing semicolon is the loop body. Write it on its own line, or add a comment, because an accidental one is a nasty bug:

for (let i = 0; i < 5; i++);   // oops — the loop body is empty
    console.log(i);            // runs once, and i is not defined here

Most style guides ban the empty statement for exactly this reason.

Conditional Statements: Navigating the Path of Decision-Making

Conditional statements let code choose between paths. JavaScript gives you four ways to do it.

1. if Statement: The Gatekeeper of Conditions

The if statement evaluates a condition and runs a block if it is truthy.

if (age >= 18) {
    console.log("You are eligible to vote.");
} else {
    console.log("You are not eligible to vote.");
}

The condition does not have to be a boolean. Any value works, and JavaScript converts it — which means only the eight falsy values (false, 0, -0, 0n, "", null, undefined, NaN) take the else branch. Everything else, including an empty array and an empty object, is truthy.

2. else Statement: The Alternating Path

The else clause offers an alternative route when the condition is false. It has no condition of its own — it catches everything the if did not.

3. else if Statement: The Multi-Pronged Decision-Maker

When multiple conditions must be assessed, else if evaluates them in order and stops at the first match.

if (score >= 90) {
    console.log("You got an A.");
} else if (score >= 80) {
    console.log("You got a B.");
} else {
    console.log("You got a C or below.");
}

Order matters. If the >= 80 check came first, a score of 95 would report a B — the chain stops at the first true condition, not the best one.

4. switch Statement: Simplifying Complex Choices

switch selects one of many blocks based on the value of an expression.

let day = "Monday";

switch (day) {
    case "Monday":
        console.log("It's the start of the week.");
        break;
    case "Friday":
        console.log("Weekend is approaching.");
        break;
    default:
        console.log("It's a regular day.");
}

Two things to know about switch.

It compares with ===. No type conversion happens, so a string "1" will not match case 1:

switch ("1") {
    case 1: console.log("number 1"); break;
    default: console.log("no match — switch uses ===");
}
// logs: no match — switch uses ===

Cases fall through without break. A forgotten break is one of the classic JavaScript bugs — but it is also useful on purpose, for grouping cases:

function describe(n) {
    switch (n) {
        case 1:
        case 2:
            return "one or two";   // both 1 and 2 land here
        case 3:
            return "three";
        default:
            return "other";
    }
}

describe(1);   // "one or two"
describe(2);   // "one or two"

A return ends the function, so it works the same way break does here.

Loops: The Art of Repetition and Iteration

Loops repeat actions and iterate over data. JavaScript has five.

1. for Loop: The Workhorse of Iteration

The for loop runs a block a specified number of times, controlled by its three-part header.

for (let i = 0; i < 5; i++) {
    console.log("Iteration " + (i + 1));
}

2. for/of Loop

The for...of loop iterates over the values of an iterable — arrays, strings, Map, Set, and anything implementing the iterable protocol.

Using for...of with Arrays:

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

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

Each iteration assigns the current element to fruit.

Using for...of with Objects:

Plain object literals are not iterable, so for...of does not work on them directly. Map and Set are iterable, and so are the results of Object.keys(), Object.values() and Object.entries().

Using for...of with Maps:

const myMap = new Map([
  ['name', 'John'],
  ['age', 30],
  ['city', 'New York']
]);

for (const [key, value] of myMap) {
  console.log(`${key}: ${value}`);
}

Each entry is a [key, value] array, destructured in the loop header.

For a plain object, use Object.entries():

const user = { name: "Ganesh", role: "dev" };

for (const [key, value] of Object.entries(user)) {
  console.log(`${key}: ${value}`);
}

3. for/in Loop: The One to Be Careful With

The for...in loop iterates over the keys of an object. It looks almost identical to for...of and behaves very differently.

const user = { name: "Ganesh", role: "dev" };

for (const key in user) {
  console.log(key, "=", user[key]);
}
// name = Ganesh
// role = dev

It works on arrays too, and that is where the trouble starts. The indexes come back as strings:

const arr = ["a", "b", "c"];

for (const i in arr) {
  console.log(i, typeof i);   // "0" string, "1" string, "2" string
  console.log(i + 1);         // "01", "11", "21"  <-- concatenation, not addition
}

There is a second problem: for...in walks the prototype chain, so it picks up inherited properties that were never on your object.

const base = { inherited: true };
const obj = Object.create(base);
obj.own = 1;

for (const key in obj) console.log(key);   // "own", then "inherited"

The rule:

  • Objectsfor...of Object.entries(obj), or for...in with an Object.hasOwn(obj, key) guard.

  • Arraysfor...of, .forEach(), or .entries() when you need a real numeric index:

for (const [i, value] of arr.entries()) {
  console.log(i, value);   // 0 'a' — i is a number
}

4. while Loop: The Sentinel of Conditional Repetition

The while loop continues as long as its condition remains truthy.

let count = 0;
while (count < 5) {
    console.log("Count: " + count);
    count++;
}

5. do...while Loop: The Guarantor of At-Least-Once Execution

The do...while loop checks its condition after running the body, so the body always executes at least once.

let number;
do {
    number = Number(prompt("Enter a positive number: "));
} while (Number.isNaN(number) || number <= 0);

Note the Number() conversion. prompt() returns a string, and comparing a string with <= triggers a type conversion that works by accident more often than by design.

Jumps: Controlling Code Flow with Precision

Jump statements move execution somewhere other than the next line.

1. break Statement: The Escape Artist

break exits a loop or a switch immediately.

for (let i = 0; i < 10; i++) {
    if (i === 5) {
        break;
    }
    console.log("Iteration " + i);
}

2. continue Statement: The Skipper of Iterations

continue skips the rest of the current iteration and moves to the next.

for (let i = 0; i < 5; i++) {
    if (i === 2) {
        continue;
    }
    console.log("Iteration " + i);
}

3. Labeled Statements: Breaking Out of Nested Loops

break and continue only affect the innermost loop. A label lets you name an outer loop and jump out of that one directly.

outer:
for (const row of [[1, 2], [3, 4]]) {
    for (const cell of row) {
        if (cell === 3) break outer;   // leaves BOTH loops
        console.log(cell);             // 1, 2
    }
}

Without the label you would need a flag variable checked after the inner loop — more code, and easier to get wrong. continue label works the same way, skipping to the next iteration of the labeled loop.

Use this sparingly. If you reach for it often, the loop body probably wants to be a function with an early return.

4. return Statement: The Exit Door of Functions

return exits a function and hands a value back to the caller.

function add(a, b) {
    return a + b;
}

A function with no return, or a bare return;, gives back undefined. And return outside a function is a SyntaxError — it belongs to functions only.

5. throw Statement: Raising an Error

throw stops normal execution and hands an error to the nearest enclosing catch.

function withdraw(balance, amount) {
    if (amount > balance) {
        throw new Error("Insufficient funds");
    }
    return balance - amount;
}

You can throw any value, but throwing anything other than an Error loses the stack trace:

throw "Something went wrong";              // works, but no stack — hard to debug
throw new Error("Something went wrong");   // do this

Miscellaneous Statements: Navigating the Quirks of JavaScript

1. try...catch...finally: Taming Errors with Grace

The try, catch and finally blocks handle exceptions that occur while code runs.

The try block wraps code that might throw. If an error occurs inside it, control transfers to the catch block.

The catch block receives the error object and decides what to do with it. If no error was thrown, it is skipped entirely.

The finally block is optional and runs either way — error or no error. It is for cleanup that must happen regardless: closing a file, releasing a lock, hiding a loading spinner.

try {
  // Code that may throw an exception
} catch (error) {
  // Code to handle the exception
} finally {
  // Code that always executes, whether there was an error or not
}

Optional catch binding

If you do not need the error object, ES2019 lets you leave the parameter out entirely:

try {
  JSON.parse(input);
} catch {
  return null;
}

Before this, catch (e) was required even when e was never used — an unused variable in every linted codebase.

Use it only when you genuinely do not need the error. Swallowing errors silently is how a bug becomes invisible.

The built-in error types

Knowing which error type you are looking at narrows the cause before you read the message:

Type What it means
TypeError A value is the wrong type — calling a non-function, reading a property of undefined
ReferenceError A variable does not exist, or is in the temporal dead zone
SyntaxError Code could not be parsed — often from JSON.parse
RangeError A number outside its allowed range — new Array(-1), infinite recursion
Error The generic base, and what you extend for your own

You can subclass Error for your own domain errors, so callers branch on the type rather than matching message strings:

class ValidationError extends Error {
  constructor(field, message) {
    super(message);
    this.name = "ValidationError";
    this.field = field;
  }
}

try {
  throw new ValidationError("email", "Email is not valid");
} catch (err) {
  if (err instanceof ValidationError) {
    console.log(err.name, err.field, err.message);
    // ValidationError email Email is not valid
  }
}

Setting this.name matters — without it the error prints as Error and the type is invisible in your logs.

There is also Error.cause (ES2022), for wrapping a low-level error in a meaningful one without losing the original:

try {
  JSON.parse(rawConfig);
} catch (err) {
  throw new Error("Could not load user config", { cause: err });
}

2. with Statement: Best Left Alone

The with statement was meant to let you access an object's properties without repeating its name.

with (object) {
  // Code that refers to object's properties and methods
}

Do not use it. It makes it impossible for a reader — or the engine — to tell whether a name refers to a property of the object or a variable from an outer scope, which blocks optimisation and produces genuinely confusing bugs.

It is a SyntaxError in strict mode, not merely discouraged. Since ES modules and class bodies are always strict, with cannot appear in most modern code at all. It is here so you recognise it in old code, nothing more.

3. debugger Statement

The debugger statement acts as a breakpoint written into your code. When devtools are open, execution pauses there and you can inspect variables, objects and the call stack.

// Your JavaScript code here
// ...
debugger; // execution pauses here when devtools are open
// ...
// More code here

With devtools closed it does nothing. Still, remove them before shipping — a stray debugger will freeze the page for anyone who happens to have devtools open.

4. "use strict" Statement

The "use strict" directive enables a stricter set of language rules. It catches common mistakes that otherwise fail silently:

"use strict";
x = 10;   // ReferenceError: x is not defined

Without strict mode, x would quietly become a global variable.

Strict mode also makes this undefined in a plain function call rather than the global object, turns duplicate parameter names into a syntax error, and makes writing to a frozen or read-only property throw instead of failing silently.

You are probably already in strict mode. ES modules and class bodies are strict automatically, with no way to opt out. If you are writing import/export, or working in almost any modern build setup, the directive is redundant. It still matters in plain <script> tags without type="module", and in CommonJS files in Node.

Declaration Statements: Laying the Foundation

Declaration statements create variables, functions and classes.

1. Variable Declarations (var, let, const)

var is function-scoped. It ignores blocks entirely:

function demo() {
  if (true) {
    var y = 2;   // function-scoped — still visible below
    let z = 3;   // block-scoped — gone at the closing brace
  }
  console.log(y);   // 2
  console.log(z);   // ReferenceError: z is not defined
}

A var at the top level of a script does become a global, which is where the "var is global" idea comes from — but inside a function it is not.

let and const (ES6) are block-scoped and better behaved:

let age = 30;
const PI = 3.14;

All three are hoisted, but differently. var is hoisted and initialised to undefined, so reading it early is legal and useless. let and const are hoisted but not initialised — the gap before the declaration line is the temporal dead zone, and touching them there throws:

console.log(a);   // undefined
var a = 1;

console.log(b);   // ReferenceError: Cannot access 'b' before initialization
let b = 1;

Read that error carefully — "cannot access before initialization", not "is not defined". JavaScript knows b exists and is refusing to let you read it yet, which is exactly the bug you wanted caught.

One more thing about const: it prevents rebinding, not mutation.

const user = { name: "Ganesh" };
user.name = "Jaiwal";        // allowed — changing what is inside
user = { name: "Someone" };  // TypeError: Assignment to constant variable

Default to const, use let when you actually reassign, and avoid var.

2. Function Declarations

Function declarations define named functions, hoisted to the top of their scope — which means you can call one before the line that defines it.

greet("Ganesh");   // works, thanks to hoisting

function greet(name) {
  return "Hello, " + name + "!";
}

Function expressions assigned to let or const are not usable early, because the variable is in the temporal dead zone.

3. Class Declarations

Class declarations (ES6) define classes. Unlike function declarations, they are not usable before their definition — classes sit in the temporal dead zone the same way let does.

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

Statements are how a program moves: choosing a path, repeating work, jumping out of it, and recovering when something breaks. Expressions produce the values; statements decide what happens to them.

Next in the series: Mastering JavaScript Objects.

Related reading: Why Should We Use for...of Instead of forEach?

Hope you like it, if yes ❤️ like & 📤share.

Thanks for your time.

Happy Coding...

← JavaScript Operators Mastering JavaScript Objects →

JavaScript - Basics to Advance

Part 8 of 12

This series will cover everything you need to know about JavaScript from basics to advanced. Please stick with us and take your JS code quality and productivity to another level #JavaScript #JS

Up next

JavaScript Objects: A Comprehensive Guide with ES6 Syntax

Last updated: September 2026 — fixed two broken code samples and two wrong outputs; added optional chaining, the shallow-copy trap, structuredClone, Object.freeze, accessors, descriptors, fromEntries

More from this blog

H

Hacker's Haven

28 posts

I'm a Senior Software Developer with expertise in programming languages and tech. Follow me for detailed articles and tutorials on software development tools and frameworks.