Description and basic syntax
Description and Basic Syntax
JavaScript is the programming language of the web, transforming static HTML documents into dynamic, interactive applications. Along with HTML (structure) and CSS (presentation), JavaScript provides the behavior layer that brings web pages to life.
Core Characteristics:
High-level Language: Abstracts away memory management and low-level details
Just-in-Time Compiled: Modern engines compile JavaScript for performance
Dynamic Typing: Variable types are determined at runtime
Single-threaded: Executes one operation at a time
Non-blocking & Asynchronous: Can handle multiple operations through callbacks and promises
Multi-paradigm: Supports procedural, object-oriented, and functional programming styles
The evolution of JavaScript
JavaScript was created in 1995 by Brendan Eich at Netscape to add interactivity to web pages. Since then, it has evolved dramatically:
ECMAScript: The standardized specification that JavaScript implements
ES6 (2015): Revolutionary update that modernized the language (covered in Advanced JavaScript)
Modern JavaScript: Continuous yearly updates adding powerful features
Today, JavaScript runs not just in browsers but also on servers (Node.js), mobile devices, desktop applications, and even embedded systems.
Adding JavaScript to a web page
JavaScript can be integrated into HTML in three ways, each with specific use cases:
1. Inline JavaScript (avoid in production)
<!-- Inline event handler -->
<button type="button" onclick="document.getElementById('demo').innerHTML = Date()">
Click me to show the date
</button>
<p id="demo"></p>
Inline JavaScript mixes behavior with structure, making code difficult to maintain and debug. It's acceptable for quick prototypes or learning, but production code should use external files. Inline scripts also can't be cached by browsers and pose security risks.
2. Internal JavaScript (script tags)
<!DOCTYPE html>
<html>
<head>
<title>Internal JavaScript Example</title>
</head>
<body>
<h1>Welcome!</h1>
<p id="demo"></p>
<!-- JavaScript before closing body tag for better performance -->
<script>
// This code runs when the browser parses this script tag
document.getElementById("demo").innerHTML = "Hello JavaScript!";
console.log("Page loaded successfully");
</script>
</body>
</html>
Internal JavaScript is useful for page-specific scripts that won't be reused. Placing script tags before the closing </body>
tag ensures the DOM is fully loaded before JavaScript executes. This prevents errors from trying to access elements that don't exist yet.
3. External JavaScript files (recommended)
<!DOCTYPE html>
<html>
<head>
<title>External JavaScript Example</title>
<meta charset="UTF-8">
</head>
<body>
<h1>Welcome!</h1>
<p id="demo"></p>
<!-- Load external JavaScript file -->
<script src="js/script.js"></script>
<!-- Modern approach: defer attribute -->
<script src="js/script.js" defer></script>
<!-- For non-critical scripts: async attribute -->
<script src="js/analytics.js" async></script>
</body>
</html>
// js/script.js
document.getElementById("demo").innerHTML = "Hello from external file!";
console.log("External JavaScript loaded");
// You can organize multiple functions in external files
function greetUser(name) {
return `Hello, ${name}!`;
}
External JavaScript files are the professional standard for several reasons: code can be cached by browsers for better performance, the same file can be reused across multiple pages, separation of concerns keeps HTML clean, and team collaboration is easier. The defer
attribute loads scripts asynchronously without blocking page rendering, executing them in order after the DOM is ready. The async
attribute loads scripts asynchronously and executes them immediately, useful for independent scripts like analytics.
JavaScript syntax fundamentals
Statements and Comments
JavaScript code consists of statements that perform actions. Each statement typically ends with a semicolon (optional but recommended).
// Single-line comment explaining the next line
var myNumber = 3; // Variable declaration and assignment
/*
Multi-line comment
for longer explanations
or temporarily disabling code
*/
counter = counter + 1; // Increment operation
myFunction(); // Function call
// Function declaration
function myFunction() {
console.log('Hello World!');
return true; // Optional return value
}
// Semicolons are technically optional due to Automatic Semicolon Insertion (ASI)
// but using them explicitly prevents edge-case bugs
let x = 5
let y = 10 // This works, but explicit semicolons are safer
Case sensitivity and naming conventions
// JavaScript is case-sensitive
let myVariable = 10;
let myvariable = 20; // Different variable!
let MYVARIABLE = 30; // Also different!
// Naming conventions (camelCase for variables and functions)
let firstName = "John";
let lastName = "Doe";
let userAge = 25;
function calculateTotal() { }
function getUserName() { }
// Constants use UPPER_SNAKE_CASE
const MAX_USERS = 100;
const API_URL = "https://api.example.com";
// Classes use PascalCase (covered later)
class UserAccount { }
class ShoppingCart { }
// Private variables often start with underscore (convention, not enforced)
let _privateVariable = "internal use";
Variables and data types
Variables are containers that store data values. JavaScript has three ways to declare variables, each with different behaviors:
Variable Declaration Keywords
// var (old way, function-scoped, avoid in modern code)
var oldStyle = "function-scoped";
var oldStyle = "can be redeclared"; // No error, but problematic
// let (modern, block-scoped, can be reassigned)
let modernStyle = "block-scoped";
modernStyle = "value can change"; // Reassignment allowed
// let modernStyle = "error"; // SyntaxError: Cannot redeclare
// const (modern, block-scoped, cannot be reassigned)
const constant = "cannot change";
// constant = "new value"; // TypeError: Assignment to constant variable
// But object/array contents CAN be modified with const
const user = { name: "John" };
user.name = "Jane"; // This works! Only the reference is constant
user.age = 30; // Can add properties
const numbers = [1, 2, 3];
numbers.push(4); // This works! Only the reference is constant
// numbers = [5, 6]; // TypeError: Cannot reassign
Use const
by default for all variables. Only use let
when you need to reassign the variable. Avoid var
entirely in modern JavaScript—it has confusing scoping rules (function-scoped rather than block-scoped) and can be redeclared, leading to bugs. The const
keyword prevents reassignment of the variable reference but doesn't make objects or arrays immutable; their contents can still be modified.
JavaScript Data Types
JavaScript has eight data types: seven primitive types and objects.

Primitive Data Types
// 1. String - Text data enclosed in quotes
let singleQuotes = 'Hello';
let doubleQuotes = "World";
let templateLiteral = `Hello ${singleQuotes}`; // Template literals with interpolation
let multiline = `This string
spans multiple
lines`;
// 2. Number - Both integers and decimals
let integer = 42;
let decimal = 3.14159;
let negative = -10;
let scientific = 2.5e6; // 2,500,000
let infinity = Infinity;
let notANumber = NaN; // Result of invalid math operations
// 3. Boolean - true or false
let isActive = true;
let isCompleted = false;
let comparison = 5 > 3; // true
// 4. Undefined - Variable declared but not assigned
let notYetAssigned; // undefined
let explicitUndefined = undefined;
// 5. Null - Intentional absence of value
let emptyValue = null; // Explicitly "no value"
// 6. Symbol - Unique identifier (ES6+)
let uniqueId = Symbol('id');
let anotherId = Symbol('id');
// uniqueId === anotherId; // false, each symbol is unique
// 7. BigInt - Large integers beyond Number.MAX_SAFE_INTEGER (ES2020+)
let bigNumber = 1234567890123456789012345678901234567890n;
let anotherBig = BigInt("9007199254740991");
Primitive types are immutable—their values cannot be changed after creation. When you "modify" a string or number, you're actually creating a new value. Strings can use single quotes, double quotes, or backticks (template literals). Template literals support multi-line strings and variable interpolation using ${expression}
syntax. Numbers include special values like Infinity
and NaN
(Not a Number). Use null
to explicitly represent "no value" and let undefined
naturally occur for uninitialized variables.
Object data type
// Object - Collection of key-value pairs
const person = {
firstName: "John",
lastName: "Doe",
age: 30,
isEmployed: true,
address: {
city: "New York",
country: "USA"
},
hobbies: ["reading", "coding", "gaming"]
};
// Accessing object properties
console.log(person.firstName); // "John" (dot notation)
console.log(person["lastName"]); // "Doe" (bracket notation)
console.log(person.address.city); // "New York" (nested access)
// Arrays - Special type of object for ordered lists
const numbers = [1, 2, 3, 4, 5];
const mixed = [1, "two", true, null, {key: "value"}];
// Accessing array elements (zero-indexed)
console.log(numbers[0]); // 1
console.log(numbers[4]); // 5
console.log(numbers.length); // 5
// Arrays are objects
console.log(typeof numbers); // "object"
console.log(Array.isArray(numbers)); // true
Type checking and conversion
// Checking types with typeof
console.log(typeof "hello"); // "string"
console.log(typeof 42); // "number"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof null); // "object" (historical bug!)
console.log(typeof {}); // "object"
console.log(typeof []); // "object" (arrays are objects)
console.log(typeof function(){}); // "function"
// Type conversion (coercion)
// Explicit conversion
let numString = "123";
let num = Number(numString); // 123
let str = String(456); // "456"
let bool = Boolean(1); // true
// Implicit conversion (coercion)
let result = "5" + 2; // "52" (number coerced to string)
let math = "5" - 2; // 3 (string coerced to number)
let weird = "5" * "2"; // 10 (both coerced to numbers)
// Truthy and falsy values
// Falsy: false, 0, "", null, undefined, NaN
// Everything else is truthy
if ("hello") { // truthy
console.log("Strings are truthy");
}
if (0) { // falsy, won't execute
console.log("Never runs");
}
The typeof
operator returns "object"
for null
, which is a historical JavaScript bug that can't be fixed without breaking legacy code. To check for null
specifically, use value === null
. Arrays also return "object"
, so use Array.isArray()
to properly detect arrays. JavaScript performs automatic type coercion, which can cause unexpected results: addition (+
) favors strings while other math operators favor numbers. Understanding truthy and falsy values is crucial for conditionals—empty strings, 0
, null
, undefined
, NaN
, and false
are falsy; everything else is truthy.
Functions
Functions are reusable blocks of code that perform specific tasks. They are fundamental building blocks in JavaScript and are themselves values that can be passed around.
Function declaration
// Basic function declaration
function greet(name) {
console.log("Hello, " + name + "!");
}
greet("Alice"); // "Hello, Alice!"
// Function with return value
function add(a, b) {
return a + b;
}
let sum = add(5, 3); // 8
// Function with multiple parameters and default values
function introduce(name, age = 18, city = "Unknown") {
return `${name} is ${age} years old and lives in ${city}`;
}
console.log(introduce("John")); // "John is 18 years old and lives in Unknown"
console.log(introduce("Jane", 25)); // "Jane is 25 years old and lives in Unknown"
console.log(introduce("Bob", 30, "NYC")); // "Bob is 30 years old and lives in NYC"
// Functions without return statement return undefined
function noReturn() {
console.log("This function returns nothing explicit");
}
let result = noReturn(); // undefined
Function declarations define reusable code blocks with a name, parameters (inputs), and an optional return value (output). Parameters act as placeholders for values passed when calling the function. Default parameters (ES6+) provide fallback values if arguments aren't supplied. If a function doesn't explicitly return a value, it automatically returns undefined
. The return statement immediately exits the function—any code after it won't execute.
Function Scope
// Global scope
const globalVar = "I'm global";
function demonstrateScope() {
// Function scope
const localVar = "I'm local";
console.log(globalVar); // Accessible: "I'm global"
console.log(localVar); // Accessible: "I'm local"
if (true) {
// Block scope (let/const)
const blockVar = "I'm block-scoped";
var functionVar = "I'm function-scoped";
console.log(blockVar); // Accessible: "I'm block-scoped"
console.log(functionVar); // Accessible: "I'm function-scoped"
}
// console.log(blockVar); // Error: blockVar is not defined
console.log(functionVar); // Accessible: "I'm function-scoped"
}
demonstrateScope();
// console.log(localVar); // Error: localVar is not defined
// Function parameters are locally scoped
function calculate(x, y) {
const result = x + y; // result is local to this function
return result;
}
// console.log(x); // Error: x is not defined
// console.log(result); // Error: result is not defined
JavaScript uses lexical (static) scoping where variables are accessible based on where they're defined in the code. Variables declared outside functions are global and accessible everywhere. Variables declared inside functions are local and only accessible within that function. Modern let
and const
use block scope (within {}
), while old var
uses function scope, which is another reason to avoid var
. Function parameters and locally declared variables are not accessible outside the function, preventing naming conflicts and improving code organization.
Function expressions and arrow Functions
// Function expression (function as a value)
const multiply = function(a, b) {
return a * b;
};
console.log(multiply(4, 5)); // 20
// Arrow function (ES6+ - concise syntax)
const subtract = (a, b) => {
return a - b;
};
// Arrow function with implicit return (one expression)
const divide = (a, b) => a / b;
// Arrow function with single parameter (parentheses optional)
const square = x => x * x;
// Arrow function with no parameters
const getRandom = () => Math.random();
console.log(divide(10, 2)); // 5
console.log(square(5)); // 25
console.log(getRandom()); // Random number between 0 and 1
Function expressions assign functions to variables, making functions first-class values that can be passed as arguments or returned from other functions. Arrow functions (=>
) provide concise syntax, especially useful for short functions and callbacks. If the function body is a single expression, you can omit the braces and return
keyword—the expression's value is automatically returned. Single parameters don't need parentheses, but multiple parameters or no parameters require them. Arrow functions also have different this
binding behavior (covered in Advanced JavaScript).
Operators
JavaScript provides various operators for performing operations on values and variables.

Arithmetic operators
// Basic math operations
let a = 10;
let b = 3;
console.log(a + b); // 13 - Addition
console.log(a - b); // 7 - Subtraction
console.log(a * b); // 30 - Multiplication
console.log(a / b); // 3.333... - Division
console.log(a % b); // 1 - Modulus (remainder)
console.log(a ** b); // 1000 - Exponentiation (ES7+)
// Increment and decrement
let counter = 5;
counter++; // Post-increment: 6
++counter; // Pre-increment: 7
counter--; // Post-decrement: 6
--counter; // Pre-decrement: 5
// Difference between pre and post increment
let x = 5;
let y = x++; // y = 5, x = 6 (assign then increment)
let z = ++x; // z = 7, x = 7 (increment then assign)
Arithmetic operators perform mathematical calculations. The modulus operator (%
) returns the remainder after division, useful for checking even/odd numbers or cycling through values. Exponentiation (**
) raises a number to a power. Increment (++
) and decrement (--
) operators have two forms: postfix (x++
) assigns the current value then increments, while prefix (++x
) increments then assigns the new value.
Assignment operators
// Basic assignment
let num = 10;
// Compound assignment operators (shorthand)
num += 5; // num = num + 5; Result: 15
num -= 3; // num = num - 3; Result: 12
num *= 2; // num = num * 2; Result: 24
num /= 4; // num = num / 4; Result: 6
num %= 4; // num = num % 4; Result: 2
num **= 3; // num = num ** 3; Result: 8
// String concatenation with +=
let message = "Hello";
message += " World"; // "Hello World"
message += "!"; // "Hello World!"
Compound assignment operators combine an operation with assignment, providing shorter syntax. x += 5
is equivalent to x = x + 5
. These operators work with all arithmetic operations and are more concise and often more performant than writing out the full expression. The +=
operator is particularly useful for string concatenation and building messages incrementally.
Comparison operators
// Equality operators
console.log(5 == "5"); // true - Loose equality (type coercion)
console.log(5 === "5"); // false - Strict equality (no coercion)
console.log(5 != "5"); // false - Loose inequality
console.log(5 !== "5"); // true - Strict inequality
// Relational operators
console.log(10 > 5); // true
console.log(10 < 5); // false
console.log(10 >= 10); // true
console.log(10 <= 5); // false
// Comparing strings (lexicographic order)
console.log("apple" < "banana"); // true
console.log("Z" < "a"); // true (uppercase comes before lowercase in Unicode)
// Type coercion pitfalls
console.log(0 == false); // true (both coerced to falsy)
console.log(0 === false); // false (different types)
console.log("" == false); // true (both coerced to falsy)
console.log("" === false); // false (different types)
console.log(null == undefined); // true (special case)
console.log(null === undefined); // false (different types)
Always use strict equality (===
) and strict inequality (!==
) instead of loose equality (==
) and loose inequality (!=
). Loose equality performs type coercion, leading to confusing results like 0 == false
being true
. Strict equality checks both value and type without coercion, making code more predictable. The only exception is null == undefined
, which is true
by design—but even then, explicit checks are clearer.
Logical operators
// AND (&&) - Both must be true
console.log(true && true); // true
console.log(true && false); // false
console.log(false && true); // false
// OR (||) - At least one must be true
console.log(true || false); // true
console.log(false || false); // false
console.log(true || true); // true
// NOT (!) - Inverts boolean
console.log(!true); // false
console.log(!false); // true
console.log(!!0); // false (double negation converts to boolean)
// Short-circuit evaluation
const user = null;
const defaultName = user || "Guest"; // "Guest" (user is falsy)
const isLoggedIn = true;
isLoggedIn && console.log("Welcome!"); // Executes (first is truthy)
// Combining logical operators
const age = 25;
const hasLicense = true;
const canDrive = age >= 18 && hasLicense; // true
// Logical assignment operators (ES2021+)
let value = null;
value ||= 10; // value = value || 10; (assigns if falsy)
console.log(value); // 10
let count = 5;
count &&= 20; // count = count && 20; (assigns if truthy)
console.log(count); // 20
Logical operators work with boolean values and use short-circuit evaluation. The AND operator (&&
) returns the first falsy value or the last value if all are truthy.
The OR operator (||
) returns the first truthy value or the last value if all are falsy. This behavior enables common patterns like default values (value || defaultValue
) and conditional execution (condition && executeThis()
).
The NOT operator (!
) converts values to boolean and inverts them—double NOT (!!
) is a trick to convert any value to its boolean equivalent.
Ternary (Conditional) Operator
// Syntax: condition ? expressionIfTrue : expressionIfFalse
// Simple example
const age = 20;
const status = age >= 18 ? "Adult" : "Minor";
console.log(status); // "Adult"
// Function with ternary
function checkAge(person) {
return person.age < 18 ? "Too young" : "Allowed";
}
// Ternary in template literals
const user = { name: "Alice", isAdmin: true };
console.log(`Welcome ${user.isAdmin ? "Admin " : ""}${user.name}`);
// "Welcome Admin Alice"
// Nested ternary (use sparingly, can reduce readability)
const score = 85;
const grade = score >= 90 ? "A" :
score >= 80 ? "B" :
score >= 70 ? "C" : "F";
console.log(grade); // "B"
// Better approach for complex conditions: use if/else
The ternary operator provides concise syntax for simple conditional assignments. It's perfect for straightforward true/false scenarios but can become unreadable when nested. For complex conditions with multiple branches, traditional if/else
statements are clearer and more maintainable. Use ternary operators for simple cases and readability; avoid deeply nested ternaries that require mental gymnastics to parse.
Conditional statements
Conditional statements control program flow based on boolean conditions.
If / Else If / Else
// Basic if statement
const temperature = 25;
if (temperature > 30) {
console.log("It's hot!");
}
// If-else
const age = 16;
if (age >= 18) {
console.log("You can vote");
} else {
console.log("Too young to vote");
}
// If-else if-else (multiple conditions)
const score = 85;
if (score >= 90) {
console.log("Grade: A");
} else if (score >= 80) {
console.log("Grade: B");
} else if (score >= 70) {
console.log("Grade: C");
} else if (score >= 60) {
console.log("Grade: D");
} else {
console.log("Grade: F");
}
// Multiple conditions with logical operators
const user = {
age: 25,
hasLicense: true,
hasInsurance: true
};
if (user.age >= 18 && user.hasLicense && user.hasInsurance) {
console.log("You can rent a car");
} else {
console.log("Requirements not met");
}
// Nested conditionals
const isWeekend = true;
const weather = "sunny";
if (isWeekend) {
if (weather === "sunny") {
console.log("Perfect day for outdoor activities!");
} else {
console.log("Stay indoors and relax");
}
} else {
console.log("It's a work day");
}
Conditional statements execute different code blocks based on boolean conditions. The if
statement runs code only if its condition is true. The else
clause handles the false case. The else if
allows multiple conditions to be checked in sequence—only the first true condition executes. Conditions can use logical operators to combine multiple checks. Avoid deeply nested conditionals when possible by using early returns or restructuring logic for better readability.
Switch statements
// Switch for multiple discrete values
const day = "Monday";
switch (day) {
case "Monday":
console.log("Start of the work week");
break;
case "Tuesday":
case "Wednesday":
case "Thursday":
console.log("Midweek");
break;
case "Friday":
console.log("TGIF!");
break;
case "Saturday":
case "Sunday":
console.log("Weekend!");
break;
default:
console.log("Invalid day");
}
// Switch with fall-through (intentional, use cautiously)
const month = 2;
let days;
switch (month) {
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
days = 31;
break;
case 4: case 6: case 9: case 11:
days = 30;
break;
case 2:
days = 28; // Simplified, not accounting for leap years
break;
default:
days = 0;
}
console.log(`This month has ${days} days`);
Switch statements provide cleaner syntax than multiple if-else
statements when checking a single value against many discrete options. Each case
clause checks for equality (strict ===
). The break
statement prevents fall-through to subsequent cases—without it, execution continues to the next case. Fall-through can be intentional (grouping multiple cases) but should be clearly commented. The default
clause acts like else
, handling values that don't match any case.
Looping statements
Loops execute code repeatedly, either a specific number of times or until a condition is met.
For loop
// Basic for loop structure
// for ([initialization]; [condition]; [increment]) { statements }
// Counting from 0 to 4
for (let i = 0; i < 5; i++) {
console.log(`Iteration ${i}`);
}
// Output: Iteration 0, Iteration 1, ..., Iteration 4
// Counting backwards
for (let i = 10; i > 0; i--) {
console.log(i);
}
// Output: 10, 9, 8, ..., 1
// Iterating through an array
const colors = ["red", "green", "blue", "yellow"];
for (let i = 0; i < colors.length; i++) {
console.log(`Color ${i + 1}: ${colors[i]}`);
}
// Skipping iterations with continue
for (let i = 0; i < 10; i++) {
if (i % 2 === 0) continue; // Skip even numbers
console.log(i); // Only prints odd numbers
}
// Breaking out of loop early
for (let i = 0; i < 100; i++) {
if (i === 5) break; // Exit loop when i reaches 5
console.log(i);
}
// Output: 0, 1, 2, 3, 4
// Nested loops (use cautiously - O(n²) complexity)
for (let i = 1; i <= 3; i++) {
for (let j = 1; j <= 3; j++) {
console.log(`i=${i}, j=${j}`);
}
}
The for
loop is ideal when you know how many iterations you need. It has three parts: initialization (runs once before the loop), condition (checked before each iteration), and increment (runs after each iteration). The loop variable (typically i
) is commonly used to access array elements by index. The break
statement exits the loop immediately, while continue
skips the rest of the current iteration and moves to the next one. Nested loops multiply iteration counts—a loop inside a loop with 10 iterations each runs 100 times total.
For...of and for...in loops
// for...of - Iterates over VALUES (arrays, strings, iterables)
const fruits = ["apple", "banana", "cherry"];
for (const fruit of fruits) {
console.log(fruit);
}
// Output: apple, banana, cherry
// for...of with strings
const message = "Hello";
for (const char of message) {
console.log(char);
}
// Output: H, e, l, l, o
// for...in - Iterates over KEYS/INDICES (object properties, array indices)
const person = {
name: "John",
age: 30,
city: "New York"
};
for (const key in person) {
console.log(`${key}: ${person[key]}`);
}
// Output: name: John, age: 30, city: New York
// for...in with arrays (returns indices, not recommended)
const numbers = [10, 20, 30];
for (const index in numbers) {
console.log(`Index ${index}: ${numbers[index]}`);
}
// Output: Index 0: 10, Index 1: 20, Index 2: 30
// Key difference demonstrated
const arr = [3, 5, 7];
arr.customProperty = "hello";
for (const i in arr) {
console.log(i); // Logs: 0, 1, 2, customProperty
}
for (const value of arr) {
console.log(value); // Logs: 3, 5, 7 (only array values)
}
Use for...of
to iterate over values in arrays, strings, and other iterables—it's cleaner and more intuitive for most use cases.
Use for...in
to iterate over object properties. While for...in
technically works with arrays, it iterates over indices (and any custom properties) rather than values, which is rarely what you want. For arrays, prefer for...of
, array methods like forEach()
, or traditional for
loops.
The for...in
loop is best reserved for objects where you need to access keys dynamically.
While and do...while loops
// While loop - Check condition first, then execute
let count = 0;
while (count < 5) {
console.log(`Count: ${count}`);
count++;
}
// Output: Count: 0, Count: 1, ..., Count: 4
// While loop with condition that may never be true
let found = false;
let attempts = 0;
while (!found && attempts < 10) {
attempts++;
console.log(`Attempt ${attempts}`);
// Simulated condition
if (Math.random() > 0.7) {
found = true;
console.log("Success!");
}
}
// Do...while - Execute first, then check condition (runs at least once)
let userInput;
do {
userInput = prompt("Enter 'quit' to exit:");
console.log(`You entered: ${userInput}`);
} while (userInput !== "quit");
// Comparison: while vs do...while
let x = 10;
while (x < 5) {
console.log("This never runs");
}
do {
console.log("This runs once even though condition is false");
} while (x < 5);
While loops repeat code as long as a condition is true, making them ideal when the number of iterations is unknown. The while
loop checks the condition before executing, so the body may never run. The do...while
loop checks the condition after executing, guaranteeing at least one execution. Use while
for scenarios like processing input until a condition is met, searching for values, or waiting for external events. Always ensure the loop condition will eventually become false to avoid infinite loops.
Avoiding Infinite Loops
// Infinite loop - AVOID! This will freeze your browser
// while (true) {
// console.log("This never stops!");
// }
// Common infinite loop mistakes
// let i = 0;
// while (i < 10) {
// console.log(i);
// // Forgot to increment i - infinite loop!
// }
// Safe patterns
let counter = 0;
const MAX_ITERATIONS = 1000;
while (counter < MAX_ITERATIONS) {
// Do work
counter++; // Always increment/change condition variable
// Safety exit
if (someErrorCondition) {
console.error("Breaking due to error");
break;
}
}
// Set timeout to prevent truly infinite loops in development
let iterations = 0;
while (someCondition) {
iterations++;
if (iterations > 10000) {
console.error("Loop exceeded safety limit");
break;
}
// Loop body
}
Infinite loops occur when the loop condition never becomes false, freezing browsers and crashing applications. Common causes include forgetting to update the loop variable, incorrect comparison operators, or logical errors in the condition. Always ensure your loop has a clear termination condition and that variables affecting the condition are properly updated. In development, consider adding safety counters that break after a maximum number of iterations to catch accidental infinite loops early.
Best practices
1. Use modern variable declarations
// Bad - using var
var oldWay = "avoid this";
// Good - use const by default
const API_URL = "https://api.example.com";
const MAX_RETRIES = 3;
// Good - use let when reassignment is needed
let counter = 0;
counter++;
2. Write clear function names
// Bad - unclear names
function doStuff(x) { }
function process(data) { }
// Good - descriptive names
function calculateTotalPrice(items) { }
function validateUserEmail(email) { }
function fetchUserDataFromAPI(userId) { }
3. Keep functions small and focused
// Bad - function does too much
function processUser(user) {
// Validate user
// Calculate age
// Format data
// Send email
// Update database
}
// Good - single responsibility
function validateUser(user) { }
function calculateAge(birthDate) { }
function formatUserData(user) { }
function sendWelcomeEmail(email) { }
function saveUserToDatabase(user) { }
4. Use strict equality
// Bad - loose equality
if (value == "5") { }
if (result != null) { }
// Good - strict equality
if (value === "5") { }
if (result !== null) { }
5. Comment complex logic, not obvious code
// Bad - obvious comments
let x = 5; // Set x to 5
// Good - explain WHY, not WHAT
// Using 5-second debounce to prevent excessive API calls
const DEBOUNCE_DELAY = 5000;
// Complex regex for email validation per RFC 5322 spec
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
Good JavaScript code is readable, maintainable, and follows established conventions. Use const
by default and let
only when reassignment is necessary—never use var
. Write descriptive function and variable names that explain purpose. Keep functions small and focused on a single task. Always use strict equality (===
) to avoid type coercion bugs. Comment complex logic, algorithms, or business rules, but don't comment obvious code—good code should be self-documenting through clear naming.
Last updated
Was this helpful?