We are still cooking the magic in the way!
Operators & Expressions
What Are Operators and Expressions?
An operator is a symbol that tells JavaScript to perform a specific operation on one or more values. An expression is any valid unit of code that resolves to a value. Every time you write 5 + 3, you are using the addition operator to create an expression that evaluates to 8. Understanding operators is essential because they are the verbs of your programs -- they describe the actions performed on your data.
JavaScript has a rich set of operators organized into categories: arithmetic, assignment, comparison, logical, bitwise, and several special-purpose operators. In this lesson, we will explore each category in depth, learn how operators interact through precedence and associativity, and discover modern JavaScript operators like nullish coalescing and optional chaining that make your code more concise and robust.
Arithmetic Operators
Arithmetic operators perform mathematical calculations. JavaScript supports the standard set of mathematical operations along with a few additional ones. These operators work primarily with numbers, but as we learned in the previous lesson, JavaScript will attempt to convert non-number operands to numbers (except with the + operator, which may perform string concatenation).
Example: Basic Arithmetic Operators
// Addition (+)
console.log(10 + 5); // 15
console.log(10 + '5'); // "105" (string concatenation!)
// Subtraction (-)
console.log(10 - 5); // 5
console.log(10 - '3'); // 7 (string "3" converts to number)
// Multiplication (*)
console.log(10 * 5); // 50
console.log(10 * '2'); // 20
// Division (/)
console.log(10 / 3); // 3.3333333333333335
console.log(10 / 0); // Infinity
console.log(-10 / 0); // -Infinity
console.log(0 / 0); // NaN
// Modulo/Remainder (%)
console.log(10 % 3); // 1 (remainder of 10 / 3)
console.log(7 % 2); // 1 (useful for checking even/odd)
console.log(-7 % 3); // -1 (sign follows the dividend)
// Exponentiation (**)
console.log(2 ** 10); // 1024
console.log(9 ** 0.5); // 3 (square root)
console.log(2 ** -1); // 0.5 (reciprocal)
Increment and Decrement Operators
The increment (++) and decrement (--) operators add or subtract 1 from a value. They come in two forms: prefix (before the variable) and postfix (after the variable). The difference matters when you use them within expressions: prefix returns the new value, while postfix returns the original value before modification.
Example: Increment and Decrement
let a = 5;
// Postfix: returns value THEN increments
console.log(a++); // 5 (returns original, then a becomes 6)
console.log(a); // 6
// Prefix: increments THEN returns value
console.log(++a); // 7 (increments first, then returns 7)
console.log(a); // 7
// Same concept with decrement
let b = 10;
console.log(b--); // 10 (returns original, then b becomes 9)
console.log(--b); // 8 (decrements first, then returns 8)
// Practical example showing the difference
let x = 5;
let y = x++; // y = 5, x = 6 (postfix: assign then increment)
let z = ++x; // z = 7, x = 7 (prefix: increment then assign)
console.log(x, y, z); // 7, 5, 7
count++; on a separate line is much clearer than embedding count++ inside a larger expression. Many style guides (including Airbnb's) recommend using count += 1 instead of count++ for clarity.Unary Plus and Minus
The unary plus (+) and unary minus (-) operators act on a single operand. The unary plus is the quickest way to convert a value to a number, while the unary minus converts and negates. These are different from the binary addition and subtraction operators, which act on two operands.
Example: Unary Operators
// Unary plus converts to number
console.log(+'42'); // 42
console.log(+true); // 1
console.log(+false); // 0
console.log(+''); // 0
console.log(+null); // 0
console.log(+undefined); // NaN
console.log(+'hello'); // NaN
// Unary minus converts and negates
console.log(-'42'); // -42
console.log(-true); // -1
console.log(-(-5)); // 5 (double negative)
Assignment Operators
Assignment operators store values in variables. The basic assignment operator is =, but JavaScript provides compound assignment operators that combine an arithmetic or bitwise operation with assignment. These are shorthand that makes code more concise and readable.
Example: Assignment Operators
// Basic assignment
let x = 10;
// Addition assignment
x += 5; // x = x + 5 = 15
console.log(x); // 15
// Subtraction assignment
x -= 3; // x = x - 3 = 12
console.log(x); // 12
// Multiplication assignment
x *= 2; // x = x * 2 = 24
console.log(x); // 24
// Division assignment
x /= 4; // x = x / 4 = 6
console.log(x); // 6
// Remainder assignment
x %= 4; // x = x % 4 = 2
console.log(x); // 2
// Exponentiation assignment
x **= 3; // x = x ** 3 = 8
console.log(x); // 8
// Logical assignment operators (ES2021)
let a = null;
a ??= 'default'; // a = a ?? "default"
console.log(a); // "default" (a was null)
let b = 0;
b ||= 42; // b = b || 42
console.log(b); // 42 (b was falsy)
let c = 1;
c &&= 99; // c = c && 99
console.log(c); // 99 (c was truthy)
// String concatenation assignment
let greeting = 'Hello';
greeting += ' World';
console.log(greeting); // "Hello World"
??=, ||=, &&=) were introduced in ES2021. They are shorthand for common patterns: a ??= b only assigns if a is null or undefined, a ||= b assigns if a is falsy, and a &&= b assigns if a is truthy. These are particularly useful for setting default values and conditional updates.Comparison Operators
Comparison operators compare two values and return a boolean (true or false). They are the foundation of all conditional logic in your programs. JavaScript provides both strict and loose comparison operators, as well as relational operators for ordering.
Example: Comparison Operators
// Strict equality and inequality (recommended)
console.log(5 === 5); // true
console.log(5 === '5'); // false (different types)
console.log(5 !== 3); // true
console.log(5 !== '5'); // true (different types)
// Loose equality and inequality (avoid)
console.log(5 == '5'); // true (type coercion!)
console.log(0 == false); // true (type coercion!)
console.log(null == undefined); // true (special rule)
console.log(5 != '5'); // false (type coercion!)
// Greater than, less than
console.log(10 > 5); // true
console.log(10 < 5); // false
console.log(10 >= 10); // true
console.log(10 <= 9); // false
// String comparison (lexicographic, by Unicode code points)
console.log('apple' < 'banana'); // true (a < b)
console.log('Zebra' < 'apple'); // true (Z=90 < a=97)
console.log('100' < '20'); // true (string comparison, "1" < "2")
// Comparing different types
console.log('10' > 9); // true (string "10" converts to number)
console.log(null > 0); // false
console.log(null == 0); // false
console.log(null >= 0); // true (null converts to 0 for comparison)
// Comparing NaN
console.log(NaN > 0); // false
console.log(NaN < 0); // false
console.log(NaN === NaN); // false
console.log(NaN == NaN); // false
'Zebra' < 'apple' is true. When comparing strings alphabetically, convert them to the same case first: 'Zebra'.toLowerCase() < 'apple'.toLowerCase(). For locale-aware sorting, use String.prototype.localeCompare().Logical Operators
Logical operators work with boolean values and are used to combine conditions. However, in JavaScript, logical operators do not always return boolean values -- they return one of their operands. This behavior enables powerful patterns like default values and guard clauses. Understanding how logical operators actually work (not just their truth tables) is crucial for writing idiomatic JavaScript.
Logical AND (&&)
The AND operator returns the first falsy operand it encounters, or the last operand if all are truthy. It evaluates from left to right and stops as soon as it finds a falsy value (short-circuit evaluation). This means the right side is not evaluated at all if the left side is falsy.
Example: Logical AND
// Boolean context
console.log(true && true); // true
console.log(true && false); // false
console.log(false && true); // false (right side not evaluated)
// Returns the actual operand, not just true/false
console.log('hello' && 'world'); // "world" (both truthy, returns last)
console.log(0 && 'hello'); // 0 (first falsy value)
console.log('' && 'hello'); // "" (first falsy value)
console.log(1 && 2 && 3); // 3 (all truthy, returns last)
console.log(1 && 0 && 3); // 0 (first falsy)
// Short-circuit evaluation
let user = null;
// This is safe -- the right side is not evaluated when user is null
let name = user && user.name;
console.log(name); // null (not a TypeError!)
// Guard pattern
function greet(user) {
user && console.log(`Hello, ${user.name}`);
// Only executes console.log if user is truthy
}
greet({ name: 'Alice' }); // "Hello, Alice"
greet(null); // Nothing happens (no error)
Logical OR (||)
The OR operator returns the first truthy operand it encounters, or the last operand if all are falsy. It evaluates from left to right and stops as soon as it finds a truthy value. This makes it perfect for providing fallback or default values.
Example: Logical OR
// Boolean context
console.log(true || false); // true
console.log(false || true); // true
console.log(false || false); // false
// Returns the actual operand
console.log('hello' || 'world'); // "hello" (first truthy)
console.log('' || 'default'); // "default" (first is falsy)
console.log(0 || 42); // 42 (first is falsy)
console.log(null || undefined || 'found'); // "found"
console.log(null || 0 || ''); // "" (all falsy, returns last)
// Default values pattern
function greet(name) {
name = name || 'Anonymous';
console.log(`Hello, ${name}!`);
}
greet('Alice'); // "Hello, Alice!"
greet(''); // "Hello, Anonymous!" (empty string is falsy!)
greet(undefined); // "Hello, Anonymous!"
// Problem: || treats 0, "", and false as falsy
let port = 0;
let serverPort = port || 3000;
console.log(serverPort); // 3000 (but 0 was intentional!)
Logical NOT (!)
The NOT operator converts its operand to a boolean and then negates it. It always returns a boolean value, unlike AND and OR which return an operand. The double NOT (!!) is a common pattern for explicitly converting any value to its boolean equivalent.
Example: Logical NOT
// Basic negation
console.log(!true); // false
console.log(!false); // true
console.log(!0); // true (0 is falsy)
console.log(!''); // true (empty string is falsy)
console.log(!'hello'); // false (non-empty string is truthy)
console.log(!null); // true (null is falsy)
console.log(!undefined); // true
// Double NOT (!!) for boolean conversion
console.log(!!'hello'); // true
console.log(!!0); // false
console.log(!!null); // false
console.log(!!{}); // true
console.log(!!1); // true
// Practical use in conditions
let items = [];
if (!items.length) {
console.log('No items found'); // This runs (0 is falsy, !0 is true)
}
Nullish Coalescing Operator (??)
The nullish coalescing operator (??) was introduced in ES2020 to solve a specific problem with the OR operator. While || returns the right side for any falsy value (including 0, '', and false), ?? only returns the right side when the left side is null or undefined. This makes it perfect for providing default values when you want to preserve intentional falsy values like 0 or empty strings.
Example: Nullish Coalescing (??) vs OR (||)
// The problem with ||
let count = 0;
console.log(count || 10); // 10 (wrong! 0 is a valid count)
console.log(count ?? 10); // 0 (correct! 0 is not null/undefined)
let text = '';
console.log(text || 'default'); // "default" (wrong! "" may be intentional)
console.log(text ?? 'default'); // "" (correct! "" is not null/undefined)
let isActive = false;
console.log(isActive || true); // true (wrong! false was intentional)
console.log(isActive ?? true); // false (correct! false is not null/undefined)
// ?? only triggers for null and undefined
console.log(null ?? 'fallback'); // "fallback"
console.log(undefined ?? 'fallback'); // "fallback"
console.log(0 ?? 'fallback'); // 0
console.log('' ?? 'fallback'); // ""
console.log(false ?? 'fallback'); // false
console.log(NaN ?? 'fallback'); // NaN
// Practical use: configuration with defaults
function createServer(config) {
let port = config.port ?? 3000;
let host = config.host ?? 'localhost';
let debug = config.debug ?? false;
console.log(`Server: ${host}:${port}, debug: ${debug}`);
}
createServer({ port: 0, host: '', debug: false });
// "Server: :0, debug: false" -- all intentional values preserved!
// With || this would be wrong:
// port would be 3000, host would be "localhost", debug would be false
?? with && or || without parentheses. The expression a || b ?? c is a syntax error because the precedence relationship between these operators is ambiguous. You must use parentheses: (a || b) ?? c or a || (b ?? c).Optional Chaining Operator (?.)
The optional chaining operator (?.), introduced in ES2020, allows you to safely access deeply nested object properties without manually checking each level for null or undefined. If any part of the chain is null or undefined, the entire expression short-circuits and returns undefined instead of throwing a TypeError. This operator dramatically simplifies code that works with complex, potentially incomplete data structures.
Example: Optional Chaining
let user = {
name: 'Alice',
address: {
street: '123 Main St',
city: 'Springfield'
},
getEmail() {
return 'alice@example.com';
}
};
// Without optional chaining (the old way)
let zipCode;
if (user && user.address && user.address.zip) {
zipCode = user.address.zip;
}
// With optional chaining (clean and concise)
let zip = user?.address?.zip;
console.log(zip); // undefined (no error!)
// Works with nested properties
console.log(user?.address?.city); // "Springfield"
console.log(user?.phone?.number); // undefined
// Works with methods
console.log(user?.getEmail()); // "alice@example.com"
console.log(user?.getNickname?.()); // undefined (method does not exist)
// Works with array indexing
let users = [{ name: 'Alice' }, { name: 'Bob' }];
console.log(users?.[0]?.name); // "Alice"
console.log(users?.[5]?.name); // undefined
// Combining with nullish coalescing
let displayName = user?.nickname ?? 'Unknown User';
console.log(displayName); // "Unknown User"
// Real-world example: API response handling
function displayUserCity(response) {
let city = response?.data?.user?.address?.city ?? 'City not available';
console.log(city);
}
displayUserCity({ data: { user: { address: { city: 'Austin' } } } });
// "Austin"
displayUserCity({ data: { user: {} } });
// "City not available"
displayUserCity(null);
// "City not available"
user should always have a name property, writing user?.name hides potential bugs. Use optional chaining for genuinely optional data like API responses, configuration objects, or user-provided input where some fields may be missing.The Ternary Operator (? :)
The ternary operator (also called the conditional operator) is the only JavaScript operator that takes three operands. It provides a concise way to write simple if...else statements as expressions. The syntax is: condition ? valueIfTrue : valueIfFalse. Because it is an expression (not a statement), you can use it inside template literals, function arguments, variable assignments, and return statements.
Example: Ternary Operator
// Basic ternary
let age = 20;
let status = age >= 18 ? 'adult' : 'minor';
console.log(status); // "adult"
// Equivalent if...else
let status2;
if (age >= 18) {
status2 = 'adult';
} else {
status2 = 'minor';
}
// Ternary in template literals
let score = 85;
console.log(`You ${score >= 60 ? 'passed' : 'failed'} the exam`);
// "You passed the exam"
// Ternary in function calls
function greet(name) {
console.log(`Hello, ${name ? name : 'stranger'}!`);
}
greet('Alice'); // "Hello, Alice!"
greet(''); // "Hello, stranger!"
// Nested ternaries (use sparingly!)
let grade = score >= 90 ? 'A'
: score >= 80 ? 'B'
: score >= 70 ? 'C'
: score >= 60 ? 'D'
: 'F';
console.log(grade); // "B"
// Ternary with side effects (not recommended)
let x = 5;
x > 3 ? console.log('big') : console.log('small');
// Better: use if...else for side effects
if...else if...else chain or a switch statement. The ternary operator is best used for simple, one-level conditional assignments: let result = condition ? valueA : valueB;Bitwise Operators
Bitwise operators work on the binary (bit-level) representations of numbers. They treat their operands as sequences of 32-bit integers and perform operations on each corresponding bit. While bitwise operators are less common in everyday web development, they appear in certain performance-critical code, flag systems, color manipulation, and low-level algorithms.
Example: Bitwise Operators
// Bitwise AND (&) -- both bits must be 1
console.log(5 & 3); // 1
// 5 = 101
// 3 = 011
// & = 001 = 1
// Bitwise OR (|) -- at least one bit must be 1
console.log(5 | 3); // 7
// 5 = 101
// 3 = 011
// | = 111 = 7
// Bitwise XOR (^) -- exactly one bit must be 1
console.log(5 ^ 3); // 6
// 5 = 101
// 3 = 011
// ^ = 110 = 6
// Bitwise NOT (~) -- inverts all bits
console.log(~5); // -6
// ~n = -(n + 1)
// Left shift (<<) -- shifts bits left, fills with 0
console.log(5 << 1); // 10 (effectively multiplies by 2)
console.log(5 << 2); // 20 (multiplies by 4)
// Right shift (>>) -- shifts bits right, preserves sign
console.log(20 >> 1); // 10 (effectively divides by 2)
console.log(20 >> 2); // 5 (divides by 4)
// Unsigned right shift (>>>) -- shifts right, fills with 0
console.log(-1 >>> 0); // 4294967295 (all 32 bits set to 1)
// Practical use: quick integer conversion
console.log(3.7 | 0); // 3 (truncates decimal)
console.log(-3.7 | 0); // -3
// Practical use: permission flags
const READ = 1; // 001
const WRITE = 2; // 010
const EXECUTE = 4; // 100
let permissions = READ | WRITE; // 011 = 3
console.log(permissions & READ); // 1 (truthy -- has read)
console.log(permissions & EXECUTE); // 0 (falsy -- no execute)
// Add permission
permissions = permissions | EXECUTE; // 111 = 7
// Remove permission
permissions = permissions & ~WRITE; // 101 = 5
console.log(permissions & WRITE); // 0 (write removed)
Short-Circuit Evaluation
Short-circuit evaluation is a fundamental behavior of the logical AND (&&) and OR (||) operators. When the result of a logical expression can be determined from the first operand alone, the second operand is never evaluated. This is not just an optimization -- it is a feature you can use to write safer and more concise code.
Example: Short-Circuit Evaluation Patterns
// AND short-circuits on the first falsy value
false && console.log('This never runs');
true && console.log('This runs');
// OR short-circuits on the first truthy value
true || console.log('This never runs');
false || console.log('This runs');
// Safe property access (before optional chaining existed)
let user = null;
let name = user && user.name; // null (no TypeError)
// Conditional function execution
function processOrder(order) {
// Only validate if the function exists
order.validate && order.validate();
// Only log if in debug mode
order.debug && console.log('Processing order:', order.id);
}
// Default values with OR
function createElement(tag, className) {
tag = tag || 'div';
className = className || 'default';
console.log(`<${tag} class="${className}">`);
}
createElement('span', 'highlight'); // <span class="highlight">
createElement(); // <div class="default">
// Combining patterns
function getUserDisplay(user) {
return (user && user.displayName) || (user && user.email) || 'Guest';
}
console.log(getUserDisplay({ displayName: 'Alice' })); // "Alice"
console.log(getUserDisplay({ email: 'a@b.com' })); // "a@b.com"
console.log(getUserDisplay(null)); // "Guest"
Operator Precedence
When an expression contains multiple operators, JavaScript needs to decide which operations to perform first. Operator precedence defines this order. Operators with higher precedence are evaluated before operators with lower precedence. When operators have the same precedence, associativity (left-to-right or right-to-left) determines the order. Understanding precedence helps you predict how expressions are evaluated and when to use parentheses for clarity.
Example: Operator Precedence
// Multiplication has higher precedence than addition
console.log(2 + 3 * 4); // 14 (not 20)
console.log((2 + 3) * 4); // 20 (parentheses override)
// Precedence order (high to low, simplified):
// 1. () -- Grouping (highest)
// 2. ?. -- Optional chaining
// 3. ++ -- -- Increment/Decrement
// 4. ! ~ + - -- Unary operators, typeof, void, delete
// 5. ** -- Exponentiation
// 6. * / % -- Multiplication, Division, Remainder
// 7. + - -- Addition, Subtraction
// 8. << >> >>> -- Bitwise shifts
// 9. < > <= >= -- Relational, in, instanceof
// 10. == != === !== -- Equality
// 11. & -- Bitwise AND
// 12. ^ -- Bitwise XOR
// 13. | -- Bitwise OR
// 14. && -- Logical AND
// 15. || -- Logical OR
// 16. ?? -- Nullish coalescing
// 17. ? : -- Ternary
// 18. = += -= etc -- Assignment (lowest)
// Real-world examples
let result = 2 + 3 * 4 ** 2;
// Step 1: 4 ** 2 = 16 (exponentiation first)
// Step 2: 3 * 16 = 48 (multiplication second)
// Step 3: 2 + 48 = 50 (addition last)
console.log(result); // 50
// Logical operators precedence
console.log(true || false && false); // true
// && has higher precedence than ||
// Evaluated as: true || (false && false)
// = true || false
// = true
// NOT has highest precedence among logical operators
console.log(!true && false); // false
// Evaluated as: (!true) && false = false && false = false
// Assignment has very low precedence
let a, b, c;
a = b = c = 5; // Right-to-left associativity
// c = 5, then b = 5, then a = 5
console.log(a, b, c); // 5 5 5
The Comma Operator
The comma operator evaluates each of its operands from left to right and returns the value of the last operand. It is the operator with the lowest precedence in JavaScript (even lower than assignment). While rarely used in general code, it appears in for loops and sometimes in minified code.
Example: Comma Operator
// Comma operator returns the last value
let x = (1, 2, 3);
console.log(x); // 3
// Common use in for loops
for (let i = 0, j = 10; i < j; i++, j--) {
console.log(i, j);
}
// 0 10
// 1 9
// 2 8
// 3 7
// 4 6
// Without parentheses, comma has lower precedence than assignment
let a = 1, b = 2; // This is variable declaration, not comma operator
// versus
let c;
c = (1, 2); // Comma operator: evaluates 1, then 2, returns 2
console.log(c); // 2
The typeof and delete Operators
We covered typeof in detail in the previous lesson. Two other unary operators worth mentioning are delete (removes a property from an object) and void (evaluates an expression and returns undefined).
Example: delete and void Operators
// delete removes object properties
let user = { name: 'Alice', age: 30, role: 'admin' };
delete user.role;
console.log(user); // { name: "Alice", age: 30 }
console.log(user.role); // undefined
// delete does NOT work on variables
let x = 5;
// delete x; // Has no effect in strict mode, false in sloppy mode
// delete returns true if successful
console.log(delete user.age); // true
console.log(delete user.nonexistent); // true (already does not exist)
// void operator returns undefined
console.log(void 0); // undefined
console.log(void 'hello'); // undefined
console.log(void (2 + 3)); // undefined
// void is sometimes used to ensure undefined
// (in case undefined has been reassigned in old browsers)
if (value === void 0) {
console.log('value is undefined');
}
The Spread and Rest Operators (...)
The spread operator (...) and rest operator (...) use the same syntax but serve opposite purposes. Spread expands an iterable (like an array) into individual elements, while rest collects multiple elements into an array. These are incredibly useful in modern JavaScript and appear frequently in real-world code.
Example: Spread and Rest
// Spread: expanding arrays
let numbers = [1, 2, 3];
let more = [...numbers, 4, 5, 6];
console.log(more); // [1, 2, 3, 4, 5, 6]
// Spread: copying arrays
let original = [1, 2, 3];
let copy = [...original];
copy.push(4);
console.log(original); // [1, 2, 3] (unchanged)
console.log(copy); // [1, 2, 3, 4]
// Spread: merging objects
let defaults = { color: 'blue', size: 'medium', border: true };
let custom = { size: 'large', weight: 'bold' };
let merged = { ...defaults, ...custom };
console.log(merged);
// { color: "blue", size: "large", border: true, weight: "bold" }
// Spread: function arguments
let values = [5, 10, 15, 3, 8];
console.log(Math.max(...values)); // 15
// Rest: collecting function arguments
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4, 5)); // 15
// Rest: destructuring
let [first, second, ...remaining] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(second); // 2
console.log(remaining); // [3, 4, 5]
// Rest: object destructuring
let { name, ...otherProps } = { name: 'Alice', age: 30, role: 'admin' };
console.log(name); // "Alice"
console.log(otherProps); // { age: 30, role: "admin" }
Combining Operators: Real-World Patterns
In practice, operators are rarely used in isolation. Here are common patterns that combine multiple operators to solve real programming problems elegantly.
Example: Common Operator Patterns
// Pattern 1: Safe property access with default
function getConfig(options) {
let timeout = options?.timeout ?? 5000;
let retries = options?.retries ?? 3;
let verbose = options?.verbose ?? false;
return { timeout, retries, verbose };
}
console.log(getConfig({ timeout: 0 }));
// { timeout: 0, retries: 3, verbose: false }
console.log(getConfig(null));
// { timeout: 5000, retries: 3, verbose: false }
// Pattern 2: Toggle boolean
let isVisible = true;
isVisible = !isVisible; // false
isVisible = !isVisible; // true
// Pattern 3: Swap variables without temp
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2, 1
// Pattern 4: Clamp a value within a range
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
console.log(clamp(15, 0, 10)); // 10
console.log(clamp(-5, 0, 10)); // 0
console.log(clamp(5, 0, 10)); // 5
// Pattern 5: Check if a number is even or odd
let num = 7;
console.log(num % 2 === 0 ? 'even' : 'odd'); // "odd"
// Pattern 6: Convert to integer (multiple approaches)
let float = 3.7;
console.log(Math.floor(float)); // 3 (rounds down)
console.log(Math.ceil(float)); // 4 (rounds up)
console.log(Math.trunc(float)); // 3 (removes decimal)
console.log(float | 0); // 3 (bitwise truncation)
console.log(~~float); // 3 (double bitwise NOT)
// Pattern 7: Chain of conditions with early returns
function processPayment(order) {
if (!order) return { error: 'No order provided' };
if (!order.items?.length) return { error: 'Empty cart' };
if (!(order.total > 0)) return { error: 'Invalid total' };
if (!order.paymentMethod) return { error: 'No payment method' };
return { success: true, orderId: order.id };
}
Practice Exercise
Build a mini calculator module that demonstrates your understanding of all operator categories. Create the following functions: (1) calculate(a, operator, b) that takes two numbers and an operator string ('+', '-', '*', '/', '%', '**') and returns the result, handling division by zero and invalid operators with descriptive error messages. (2) compareValues(a, b) that returns an object describing the relationship between two values: { equal, strictEqual, greaterThan, lessThan, type_a, type_b }. (3) bitwiseInfo(n) that takes a number and returns an object with: the binary representation as a string, the result of shifting left by 1, the result of shifting right by 1, and the bitwise NOT. (4) safeAccess(obj, path, defaultValue) that safely accesses a nested property using a dot-separated path string (e.g., 'user.address.city') and returns the defaultValue if any part of the path is null or undefined. Do NOT use optional chaining in this function -- implement the safe access manually using a loop and short-circuit evaluation. Test all four functions with both normal and edge-case inputs.