Skip to main content

Logical Operators

Introduction​

If mathematical operators are the building blocks for calculations, logical operators are the building blocks for decision-making. They let you combine conditions, invert them, and create complex logic that makes your programs intelligent and interactive.

Think of logical operators as the "if-then" thinking you do every day:

  • "If I have money AND the store is open, I'll go shopping"
  • "If it's raining OR it's cold, I'll wear a jacket"
  • "If it's NOT a workday, I can sleep in"

In programming, we formalize this thinking using boolean logic - working with true and false values to make decisions.

Why TypeScript's Approach is Different​

In JavaScript, many values are "truthy" or "falsy" (like 0, "", null, etc.), which can lead to subtle bugs. TypeScript encourages you to be explicit with boolean types, making your code clearer and safer.

JavaScript (implicit):

let count = 0;
if (count) { // 0 is falsy, so this is false
console.log("Has count");
}

TypeScript (explicit):

let count: number = 0;
if (count > 0) { // Explicit comparison - much clearer!
console.log("Has count");
}

TypeScript still supports JavaScript's truthy/falsy behavior, but it nudges you toward being explicit, which prevents bugs.​

The Boolean Type​

Before we dive into operators, let's understand booleans.

What is a Boolean?​

A boolean is a data type with only two possible values:

let isLoggedIn: boolean = true;
let hasPermission: boolean = false;

Booleans are named after George Boole, a 19th-century mathematician who developed Boolean algebra - the foundation of all computer logic!

Boolean Expressions​

Any expression that evaluates to true or false is a boolean expression:

let age: number = 20;
let isAdult: boolean = age >= 18; // true
let isTeenager: boolean = age >= 13 && age <= 19; // true
let isChild: boolean = age < 13; // false

Comparison Operators Create Booleans​

These operators compare values and return booleans:

let x: number = 5;
let y: number = 10;

console.log(x === y); // false (equal to)
console.log(x !== y); // true (not equal to)
console.log(x < y); // true (less than)
console.log(x <= y); // true (less than or equal)
console.log(x > y); // false (greater than)
console.log(x >= y); // false (greater than or equal)

Important: Use === and !== (strict equality) in TypeScript, not == and != (loose equality). Strict equality checks both type and value:

console.log(5 === "5"); // false (different types)
console.log(5 == "5"); // true (JavaScript converts types - avoid this!)

Logical AND (&&)​

The AND operator returns true only if both conditions are true.

Truth Table​

A | B | A && B
------|-------|-------
true | true | true
true | false | false
false | true | false
false | false | false

Examples​

let age: number = 20;
let hasID: boolean = true;

// Can enter the club?
let canEnter: boolean = age >= 18 && hasID;
console.log(canEnter); // true

// Both conditions must be true
console.log(true && true); // true
console.log(true && false); // false
console.log(false && true); // false
console.log(false && false); // false

Real-World Use Cases​

// Online shopping validation
function canCheckout(hasItems: boolean, hasPayment: boolean, hasAddress: boolean): boolean {
return hasItems && hasPayment && hasAddress;
}

console.log(canCheckout(true, true, true)); // true
console.log(canCheckout(true, false, true)); // false - missing payment

// Permission checking
function canDeletePost(isAuthor: boolean, isAdmin: boolean): boolean {
return isAuthor || isAdmin; // We'll cover OR next!
}

// Age verification
function canWatchMovie(age: number, hasParentalConsent: boolean): boolean {
return age >= 18 || (age >= 13 && hasParentalConsent);
}

Short-Circuit Evaluation​

This is a crucial concept: if the first condition is false, JavaScript doesn't even check the second!

let isLoggedIn: boolean = false;
let userData = null;

// This is safe - if isLoggedIn is false, userData is never accessed
if (isLoggedIn && userData.name) {
console.log("Welcome " + userData.name);
}

// Without short-circuit, this would crash trying to access userData.name when userData is null

Practical use:

function displayUserName(user: { name: string } | null): void {
// Safe: if user is null, user.name is never accessed
if (user && user.name) {
console.log(user.name);
} else {
console.log("No user");
}
}

Logical OR (||)​

The OR operator returns true if at least one condition is true.

Truth Table​

A | B | A || B
------|-------|-------
true | true | true
true | false | true
false | true | true
false | false | false

Examples​

let isWeekend: boolean = true;
let isHoliday: boolean = false;

// Can sleep in?
let canSleepIn: boolean = isWeekend || isHoliday;
console.log(canSleepIn); // true

// All possibilities
console.log(true || true); // true
console.log(true || false); // true
console.log(false || true); // true
console.log(false || false); // false

Real-World Use Cases​

// Access control
function canAccessPage(isLoggedIn: boolean, isGuest: boolean): boolean {
return isLoggedIn || isGuest;
}

// Weather check
function shouldTakeUmbrella(isRaining: boolean, mightRain: boolean): boolean {
return isRaining || mightRain;
}

// Error handling
function hasError(networkError: boolean, validationError: boolean, serverError: boolean): boolean {
return networkError || validationError || serverError;
}

Default Values (Traditional OR)​

Before the nullish coalescing operator, || was used for default values:

function greet(name: string | undefined): string {
const displayName = name || "Guest"; // If name is undefined/empty, use "Guest"
return `Hello, ${displayName}!`;
}

console.log(greet("Alice")); // "Hello, Alice!"
console.log(greet(undefined)); // "Hello, Guest!"

BUT there's a problem:

let count: number | undefined = 0;
let display = count || 10; // 0 is falsy, so display becomes 10 - probably not what we want!
console.log(display); // 10

This is why the nullish coalescing operator (??) was introduced!


Logical NOT (!)​

The NOT operator inverts a boolean value.

Truth Table​

A | !A
------|-------
true | false
false | true

Examples​

let isOpen: boolean = true;
let isClosed: boolean = !isOpen;
console.log(isClosed); // false

console.log(!true); // false
console.log(!false); // true

// Double negation
console.log(!!true); // true (converts to boolean)

Real-World Use Cases​

// Toggle functionality
function toggleSwitch(isOn: boolean): boolean {
return !isOn;
}

let lightOn: boolean = true;
lightOn = toggleSwitch(lightOn);
console.log(lightOn); // false

// Checking for absence
function hasNoErrors(errorCount: number): boolean {
return !(errorCount > 0); // Same as errorCount === 0
}

// Inverting conditions
function isInvalid(isValid: boolean): boolean {
return !isValid;
}

// Type checking with negation
function isNotString(value: unknown): boolean {
return !(typeof value === "string");
}

Converting to Boolean​

The ! operator converts any value to a boolean and inverts it. Use !! to convert without inverting:

console.log(!!"hello"); // true (non-empty string)
console.log(!!0); // false (zero)
console.log(!!""); // false (empty string)
console.log(!!null); // false
console.log(!!undefined); // false
console.log(!!{}); // true (any object)
console.log(!![]); // true (any array)

Nullish Coalescing Operator (??)​

This is a TypeScript/modern JavaScript feature that's safer than || for default values.

The Problem with OR​

let count: number = 0;
let port: number = 8080;

// Using OR (problematic):
let displayCount = count || 10; // 10 (0 is falsy)
let usePort = port || 3000; // 8080 (correct)

The Solution: Nullish Coalescing​

?? only treats null and undefined as "missing" - not 0, "", or false:

let count: number = 0;
let name: string = "";

// Using ?? (correct):
let displayCount = count ?? 10; // 0 (only replaces null/undefined)
let displayName = name ?? "Guest"; // "" (empty string is valid)

// Comparison:
let value: number | null = null;
console.log(value || 100); // 100
console.log(value ?? 100); // 100

value = 0;
console.log(value || 100); // 100 (wrong!)
console.log(value ?? 100); // 0 (correct!)

Real-World Use Cases​

// Configuration with defaults
function getConfig(userPort?: number): number {
return userPort ?? 3000; // Only use default if userPort is null/undefined
}

console.log(getConfig(8080)); // 8080
console.log(getConfig(0)); // 0 (valid port!)
console.log(getConfig(undefined)); // 3000

// API responses
interface UserResponse {
name: string;
age: number | null;
email?: string;
}

function displayAge(user: UserResponse): string {
const age = user.age ?? "Not provided";
return `Age: ${age}`;
}

// Database values
function getScore(score: number | null | undefined): number {
return score ?? 0; // Only default if actually missing
}

?? vs || Decision Guide​

  • Use ?? when: 0, "", or false are valid values
  • Use || when: you want to treat all falsy values as "missing"
// Use ??:
let retryCount: number | undefined = 0;
let attempts = retryCount ?? 3; // Keep 0, don't replace it

// Use ||:
let userInput: string = "";
let message = userInput || "No message"; // Empty string should use default

Combining Logical Operators​

You can combine multiple logical operators to create complex conditions.

Precedence (Order of Operations)​

  1. ! (NOT) - highest priority
  2. && (AND)
  3. || (OR)
  4. ?? (Nullish Coalescing)
// Precedence example:
let result = true || false && false;
console.log(result); // true (AND happens first: false && false = false, then true || false = true)

// Use parentheses for clarity:
let clear = (true || false) && false;
console.log(clear); // false

Complex Conditions​

// Access control
function canEditDocument(
isOwner: boolean,
isAdmin: boolean,
isCollaborator: boolean,
documentLocked: boolean
): boolean {
return (isOwner || isAdmin || isCollaborator) && !documentLocked;
}

// Age-based pricing
function getTicketPrice(age: number, isStudent: boolean, isSenior: boolean): number {
if (age < 5) {
return 0; // Free for young children
} else if ((age >= 5 && age <= 12) || isStudent) {
return 10; // Child or student discount
} else if (age >= 65 || isSenior) {
return 12; // Senior discount
} else {
return 20; // Regular price
}
}

// Form validation
function isValidForm(
hasName: boolean,
hasEmail: boolean,
hasPassword: boolean,
agreedToTerms: boolean
): boolean {
return hasName && hasEmail && hasPassword && agreedToTerms;
}

Type Guards with Logical Operators​

TypeScript can understand your logic and narrow types accordingly!

Type Narrowing​

function processValue(value: string | number | null): void {
// TypeScript narrows the type based on your checks
if (typeof value === "string") {
console.log(value.toUpperCase()); // TypeScript knows it's a string
} else if (typeof value === "number") {
console.log(value.toFixed(2)); // TypeScript knows it's a number
} else {
console.log("No value"); // TypeScript knows it's null
}
}

// With logical operators:
function printLength(value: string | null): void {
if (value && typeof value === "string") {
console.log(value.length); // Safe - TypeScript knows value is not null
}
}

Complex Type Guards​

interface User {
name: string;
age: number;
}

function greetUser(user: User | null | undefined): void {
// Check if user exists and has name
if (user && user.name) {
console.log(`Hello, ${user.name}!`);
} else {
console.log("Hello, Guest!");
}
}

// Array type guards
function processData(data: string[] | null): void {
if (data && data.length > 0) {
console.log(`Processing ${data.length} items`);
data.forEach(item => console.log(item));
}
}

Truthy and Falsy Values​

While TypeScript encourages explicit booleans, it's important to understand truthy/falsy values.

Falsy Values (these convert to false):​

  • false
  • 0
  • "" (empty string)
  • null
  • undefined
  • NaN

Everything Else is Truthy:​

  • true
  • Any non-zero number
  • Any non-empty string
  • Objects {}
  • Arrays []
  • Functions
// Examples:
console.log(Boolean(0)); // false
console.log(Boolean("")); // false
console.log(Boolean("hello")); // true
console.log(Boolean([])); // true (even empty arrays!)
console.log(Boolean({})); // true (even empty objects!)

Concepts​

Quick Reference​

// AND (&&) - both must be true
true && true // true
true && false // false

// OR (||) - at least one must be true
true || false // true
false || false // false

// NOT (!) - inverts
!true // false
!false // true

// Nullish Coalescing (??) - default for null/undefined only
null ?? "default" // "default"
0 ?? "default" // 0
"" ?? "default" // ""

// Combining operators
(true || false) && !false // true

// Type guards
value && value.property // Safe property access
typeof value === "string" // Type checking

Examples​

// Permission system
type Permission = {
canRead: boolean;
canWrite: boolean;
canDelete: boolean;
};

function checkAccess(perm: Permission, action: string): boolean {
if (action === "read") {
return perm.canRead;
} else if (action === "write") {
return perm.canRead && perm.canWrite;
} else if (action === "delete") {
return perm.canRead && perm.canWrite && perm.canDelete;
}
return false;
}

// Weather decision
function getClothingAdvice(temp: number, isRaining: boolean, isWindy: boolean): string {
if (temp < 0) {
return "Wear a heavy coat!";
} else if (temp < 15 && (isRaining || isWindy)) {
return "Wear a jacket and bring an umbrella if raining";
} else if (isRaining) {
return "Bring an umbrella";
} else {
return "Dress normally";
}
}

// Discount calculator
function getDiscount(age: number, isStudent: boolean, isMember: boolean): number {
if (age < 18 || age >= 65) {
return 0.20; // 20% age discount
} else if (isStudent && isMember) {
return 0.25; // 25% combined discount
} else if (isStudent || isMember) {
return 0.15; // 15% single discount
}
return 0; // No discount
}

Challenges​

1. Range Checker​

  • Write a function isBetween that checks if a number is between two values (inclusive).
  • Make the min and max parameters with default values of 10 and 20.
  • Test with various numbers.
💡 Show Answer
function isBetween(num: number, min: number = 10, max: number = 20): boolean {
return num >= min && num <= max;
}

console.log(isBetween(15)); // true (default range)
console.log(isBetween(25)); // false
console.log(isBetween(5, 1, 10)); // true (custom range)
console.log(isBetween(0, 1, 10)); // false

// Alternative: check if outside range
function isOutsideRange(num: number, min: number, max: number): boolean {
return num < min || num > max;
}

console.log(isOutsideRange(15, 10, 20)); // false
console.log(isOutsideRange(25, 10, 20)); // true

// Exclusive range (not including boundaries):
function isBetweenExclusive(num: number, min: number, max: number): boolean {
return num > min && num < max;
}

console.log(isBetweenExclusive(10, 10, 20)); // false (10 is boundary)
console.log(isBetweenExclusive(15, 10, 20)); // true


2. Driving Eligibility​

  • Create a function canDrive that returns true if:
    • Age is at least 18 AND has a license
  • Test with different age and license combinations.
💡 Show Answer
function canDrive(age: number, hasLicense: boolean): boolean {
return age >= 18 && hasLicense;
}

console.log(canDrive(18, true)); // true
console.log(canDrive(18, false)); // false (no license)
console.log(canDrive(16, true)); // false (too young)
console.log(canDrive(25, true)); // true

// Enhanced version with learner's permit:
function canDriveWithPermit(
age: number,
hasLicense: boolean,
hasPermit: boolean,
withAdult: boolean
): boolean {
// Can drive if has license, OR has permit and is with adult
return (age >= 18 && hasLicense) || (age >= 16 && hasPermit && withAdult);
}

console.log(canDriveWithPermit(17, false, true, true)); // true (permit + adult)
console.log(canDriveWithPermit(17, false, true, false)); // false (no adult)
console.log(canDriveWithPermit(18, true, false, false)); // true (has license)

3. Access Control​

  • Write a function that returns "Welcome" if user is logged in OR is a guest.
  • Return "Access Denied" otherwise.
  • Add a third parameter for isBlocked that denies access even if logged in.
💡 Show Answer
function getWelcomeMessage(isLoggedIn: boolean, isGuest: boolean): string {
return isLoggedIn || isGuest ? "Welcome" : "Access Denied";
}

console.log(getWelcomeMessage(true, false)); // Welcome
console.log(getWelcomeMessage(false, true)); // Welcome
console.log(getWelcomeMessage(false, false)); // Access Denied

// Enhanced with blocked status:
function getAccessMessage(
isLoggedIn: boolean,
isGuest: boolean,
isBlocked: boolean
): string {
if (isBlocked) {
return "Access Denied - Account Blocked";
}
return isLoggedIn || isGuest ? "Welcome" : "Access Denied";
}

console.log(getAccessMessage(true, false, false)); // Welcome
console.log(getAccessMessage(true, false, true)); // Access Denied - Account Blocked
console.log(getAccessMessage(false, true, false)); // Welcome

// With user type:
type UserType = "member" | "guest" | "blocked" | "none";

function checkAccess(userType: UserType): string {
if (userType === "blocked") {
return "Access Denied - Blocked";
} else if (userType === "member" || userType === "guest") {
return "Welcome";
} else {
return "Please log in";
}
}


4. Boolean Toggle​

  • Create a function toggleStatus that flips a boolean value.
  • Create a toggleLight function that toggles a light state and returns a message.
💡 Show Answer
function toggleStatus(status: boolean): boolean {
return !status;
}

let isActive: boolean = true;
isActive = toggleStatus(isActive);
console.log(isActive); // false

// Enhanced toggle with message:
function toggleLight(isOn: boolean): { newState: boolean; message: string } {
const newState = !isOn;
const message = newState ? "Light turned ON" : "Light turned OFF";
return { newState, message };
}

let lightOn: boolean = false;
let result = toggleLight(lightOn);
console.log(result); // { newState: true, message: 'Light turned ON' }

// Toggle with state tracking:
class LightSwitch {
private isOn: boolean = false;

toggle(): void {
this.isOn = !this.isOn;
console.log(`Light is now ${this.isOn ? 'ON' : 'OFF'}`);
}

getState(): boolean {
return this.isOn;
}
}

const switch1 = new LightSwitch();
switch1.toggle(); // Light is now ON
switch1.toggle(); // Light is now OFF

5. Short-Circuit Security​

  • Create a function that only returns sensitive data if the user is authenticated.
  • Use short-circuit evaluation to prevent accessing data when not authenticated.
  • Return null if not authenticated.
💡 Show Answer
function getSecureData(isAuthenticated: boolean): string | null {
// Short-circuit: if false, never tries to access the string
return isAuthenticated && "Sensitive data: Account #12345";
}

console.log(getSecureData(true)); // "Sensitive data: Account #12345"
console.log(getSecureData(false)); // false (not null!)

// Better version that returns null:
function getSecureDataSafe(isAuthenticated: boolean): string | null {
if (!isAuthenticated) {
return null;
}
return "Sensitive data: Account #12345";
}

console.log(getSecureDataSafe(true)); // "Sensitive data: Account #12345"
console.log(getSecureDataSafe(false)); // null

// With permission levels:
type Permission = "none" | "read" | "write" | "admin";

function getDataByPermission(permission: Permission): string | null {
if (permission === "none") {
return null;
}

const baseData = "Public data";
const readData = "User data";
const writeData = "Editable data";
const adminData = "Admin dashboard";

if (permission === "admin") {
return `${baseData}, ${readData}, ${writeData}, ${adminData}`;
} else if (permission === "write") {
return `${baseData}, ${readData}, ${writeData}`;
} else {
return `${baseData}, ${readData}`;
}
}

console.log(getDataByPermission("admin")); // All data
console.log(getDataByPermission("none")); // null


6. Nullish Coalescing Defaults​

  • Create a function that accepts an optional username.
  • Use ?? to return "Guest" if the username is null or undefined.
  • Test with null, undefined, empty string, and a valid name.
💡 Show Answer
function getUsername(name: string | null | undefined): string {
return name ?? "Guest";
}

console.log(getUsername("Alice")); // "Alice"
console.log(getUsername(null)); // "Guest"
console.log(getUsername(undefined)); // "Guest"
console.log(getUsername("")); // "" (empty string is valid!)

// If you want empty strings to also use default, use ||:
function getUsernameWithEmpty(name: string | null | undefined): string {
return name || "Guest";
}

console.log(getUsernameWithEmpty("")); // "Guest" (empty treated as missing)

// Configuration example:
interface Config {
port?: number;
host?: string;
timeout?: number;
}

function getConfig(userConfig: Config): Required<Config> {
return {
port: userConfig.port ?? 3000,
host: userConfig.host ?? "localhost",
timeout: userConfig.timeout ?? 5000
};
}

console.log(getConfig({})); // All defaults
console.log(getConfig({ port: 8080 })); // Custom port, default others
console.log(getConfig({ port: 0 })); // Port 0 is valid! (not replaced)

7. Type Guard Practice​

  • Create a function that accepts a string or null.
  • Use a type guard to safely get the string length.
  • Return 0 if the value is null.
💡 Show Answer
function getLength(value: string | null): number {
// Type guard with logical operator
if (value && typeof value === "string") {
return value.length;
}
return 0;
}

console.log(getLength("hello")); // 5
console.log(getLength(null)); // 0
console.log(getLength("")); // 0 (empty string)

// More complex type guard:
function processValue(value: string | number | null): string {
if (typeof value === "string") {
return `String: ${value.toUpperCase()}`;
} else if (typeof value === "number") {
return `Number: ${value.toFixed(2)}`;
} else {
return "No value provided";
}
}

console.log(processValue("hello")); // "String: HELLO"
console.log(processValue(42.5)); // "Number: 42.50"
console.log(processValue(null)); // "No value provided"

// With custom type guard function:
interface User {
name: string;
email: string;
}

function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"name" in value &&
"email" in value
);
}

function greetUser(value: unknown): string {
if (isUser(value)) {
return `Hello, ${value.name}!`; // TypeScript knows it's User
}
return "Hello, stranger!";
}

console.log(greetUser({ name: "Alice", email: "alice@email.com" })); // Hello, Alice!
console.log(greetUser("not a user")); // Hello, stranger!


8. Grade Validator​

  • Write a function that checks if a grade is valid (between 0 and 100).
  • Return a boolean result.
  • Add a second function that validates and returns an error message if invalid.
💡 Show Answer
function isValidGrade(grade: number): boolean {
return grade >= 0 && grade <= 100;
}

console.log(isValidGrade(85)); // true
console.log(isValidGrade(101)); // false
console.log(isValidGrade(-5)); // false

// With error message:
function validateGrade(grade: number): { valid: boolean; error?: string } {
if (grade < 0) {
return { valid: false, error: "Grade cannot be negative" };
} else if (grade > 100) {
return { valid: false, error: "Grade cannot exceed 100" };
}
return { valid: true };
}

console.log(validateGrade(85)); // { valid: true }
console.log(validateGrade(101)); // { valid: false, error: 'Grade cannot exceed 100' }

// With letter grade:
function getLetterGrade(grade: number): string {
if (!isValidGrade(grade)) {
return "Invalid grade";
} else if (grade >= 90) {
return "A";
} else if (grade >= 80) {
return "B";
} else if (grade >= 70) {
return "C";
} else if (grade >= 60) {
return "D";
} else {
return "F";
}
}

console.log(getLetterGrade(95)); // A
console.log(getLetterGrade(75)); // C
console.log(getLetterGrade(150)); // Invalid grade

9. Simple Login System​

  • Create a login function that checks username and password.
  • Return true only if both match the expected values.
  • Add a third parameter for a security question.
💡 Show Answer
function login(username: string, password: string): boolean {
const VALID_USER = "admin";
const VALID_PASS = "secret123";
return username === VALID_USER && password === VALID_PASS;
}

console.log(login("admin", "secret123")); // true
console.log(login("admin", "wrong")); // false
console.log(login("user", "secret123")); // false

// With security question:
function loginWithSecurity(
username: string,
password: string,
securityAnswer: string
): { success: boolean; message: string } {
const VALID_USER = "admin";
const VALID_PASS = "secret123";
const VALID_ANSWER = "blue";

if (username !== VALID_USER) {
return { success: false, message: "Invalid username" };
} else if (password !== VALID_PASS) {
return { success: false, message: "Invalid password" };
} else if (securityAnswer.toLowerCase() !== VALID_ANSWER) {
return { success: false, message: "Invalid security answer" };
}

return { success: true, message: "Login successful" };
}

console.log(loginWithSecurity("admin", "secret123", "blue")); // Success
console.log(loginWithSecurity("admin", "secret123", "red")); // Invalid answer

// With attempt tracking:
class LoginSystem {
private attempts: number = 0;
private readonly MAX_ATTEMPTS: number = 3;
private locked: boolean = false;

login(username: string, password: string): string {
if (this.locked) {
return "Account locked. Too many failed attempts.";
}

if (username === "admin" && password === "secret123") {
this.attempts = 0;
return "Login successful";
}

this.attempts++;
if (this.attempts >= this.MAX_ATTEMPTS) {
this.locked = true;
return "Account locked. Too many failed attempts.";
}

return`Invalid credentials. ${this.MAX_ATTEMPTS - this.attempts} attempts remaining.`;
}
}

const system = new LoginSystem();
console.log(system.login("admin", "wrong")); // 2 attempts remaining
console.log(system.login("admin", "wrong")); // 1 attempt remaining
console.log(system.login("admin", "wrong")); // Account locked


10. Majority Vote Function​

  • Create a function that takes three boolean values.
  • Return true if at least two of them are true.
  • Test with all possible combinations.
💡 Show Answer
function majorityVote(a: boolean, b: boolean, c: boolean): boolean {
const count = [a, b, c].filter(Boolean).length;
return count >= 2;
}

console.log(majorityVote(true, true, false)); // true (2 true)
console.log(majorityVote(true, false, false)); // false (1 true)
console.log(majorityVote(false, false, false)); // false (0 true)
console.log(majorityVote(true, true, true)); // true (3 true)

// Alternative implementation without array:
function majorityVoteAlt(a: boolean, b: boolean, c: boolean): boolean {
return (a && b) || (a && c) || (b && c);
}

// Explanation: At least two must be true means:
// (a AND b) OR (a AND c) OR (b AND c)

console.log(majorityVoteAlt(true, true, false)); // true

// Generic version for any number of votes:
function majorityVoteGeneric(...votes: boolean[]): boolean {
const trueCount = votes.filter(Boolean).length;
return trueCount > votes.length / 2;
}

console.log(majorityVoteGeneric(true, true, false)); // true
console.log(majorityVoteGeneric(true, false, false, false)); // false (1 out of 4)
console.log(majorityVoteGeneric(true, true, true, false, false)); // true (3 out of 5)

// Real-world example: approval system
type Approver = {
name: string;
approved: boolean;
};

function isApproved(approvers: Approver[]): boolean {
const approvedCount = approvers.filter(a => a.approved).length;
const majority = Math.ceil(approvers.length / 2);
return approvedCount >= majority;
}

const approvers: Approver[] = [
{ name: "Alice", approved: true },
{ name: "Bob", approved: true },
{ name: "Charlie", approved: false }
];

console.log(isApproved(approvers)); // true (2 out of 3)

Summary​

In this chapter, you learned:

✅ How to use AND (&&), OR (||), and NOT (!) operators

✅ The nullish coalescing operator (??) for safer defaults

✅ Short-circuit evaluation to write more efficient code

✅ Type guards to safely work with union types

✅ How to combine logical operators for complex conditions

✅ The difference between truthy/falsy and explicit booleans

✅ Real-world applications of boolean logic

Key Takeaways:

  • Use && when ALL conditions must be true
  • Use || when AT LEAST ONE condition must be true
  • Use ! to invert a boolean value
  • Prefer ?? over || for default values when 0, "", or false are valid
  • TypeScript's type system works beautifully with logical operators
  • Always be explicit with boolean comparisons for clarity
  • Short-circuit evaluation can prevent errors and improve performance

Best Practices:

  • Use === instead of == for comparisons
  • Be explicit: count > 0 instead of just count
  • Use parentheses to make complex logic clear
  • Leverage TypeScript's type narrowing with type guards

In the next chapter, we'll dive into Functions and learn how to create reusable, type-safe blocks of code!

Continue to Chapter 4: Functions →