Loading content...
If AND is the strict gatekeeper that demands unanimous agreement, then OR is the welcoming host that opens the door when anyone knocks. The OR operator embodies inclusivity at its core—it outputs a 1 whenever at least one input is 1, regardless of what the other input might be.
This inclusive nature mirrors everyday language. The statement "I'll be happy if I get a raise OR a promotion" is true if either condition (or both) is met. Unlike the exclusive "or" we sometimes use colloquially ("Would you like coffee or tea?"), the bitwise OR is always inclusive—it includes the possibility of both.
By the end of this page, you will master the OR operator's behavior, understand its hardware realization, and know how to apply it for setting bits, combining flags, constructing values from parts, and implementing efficient algorithms. You'll see how OR complements AND to provide a complete toolkit for bit manipulation.
The bitwise OR operator, typically represented by the | symbol (pipe character), performs a logical disjunction on each pair of corresponding bits from two operands. The result bit is 1 if at least one of the input bits is 1; the result is 0 only when both inputs are 0.
This behavior is the dual (or complement) of AND. Where AND demands both bits be 1, OR requires only one.
Think of OR as an inclusive collector. It gathers all the 1s from both operands. A 1 in either input (or both) results in a 1 in the output. Only complete absence of 1s (both inputs 0) yields a 0. This makes OR perfect for combining, merging, and setting bits.
Formal Definition:
Given two binary values A and B, the OR operation is defined as:
A OR B = 1 if A = 1 OR B = 1 (or both)
A OR B = 0 if A = 0 AND B = 0
In mathematical notation, this can be expressed as:
A ∨ B = max(A, B) (where A, B ∈ {0, 1})
Or using the complementary relationship with AND:
A OR B = NOT(NOT A AND NOT B) (De Morgan's Law)
This means OR can be thought of as: "NOT(both are 0)".
| Bit A | Bit B | A OR B | Explanation |
|---|---|---|---|
| 0 | 0 | 0 | Neither bit is set → result is 0 |
| 0 | 1 | 1 | Second bit is set → result is 1 |
| 1 | 0 | 1 | First bit is set → result is 1 |
| 1 | 1 | 1 | Both bits set → result is 1 |
Key Insight from the Truth Table:
Notice that three out of four possible input combinations result in 1. This is the exact inverse of AND's distribution. This asymmetry makes OR ideal for accumulation and union operations—it's an inclusive operator that combines the "on" bits from both inputs.
Just as the AND gate has a physical realization, the OR gate is a fundamental component of digital circuits. Understanding its implementation illuminates why OR behaves as it does.
Transistor-Level Implementation:
An OR gate can be constructed using two transistors connected in parallel. Current can flow through the circuit if either transistor is on. Only when both transistors are off is the circuit fully open (no current flows).
This is the physical opposite of AND's series configuration. Parallel paths provide alternative routes for current, embodying the "at least one" logic of OR.
12345678910111213141516171819
Voltage Source (+V) │ ┌──────┴──────┐ │ │ ┌────┴────┐ ┌────┴────┐ │ A │ │ B │ ← Parallel switches (transistors) │ (SW) │ │ (SW) │ └────┬────┘ └────┬────┘ │ │ └──────┬──────┘ │ ▼ OUTPUT │ ▼ Ground If A = 1 OR B = 1 → At least one switch closed → Current flows → Output = 1If A = 0 AND B = 0 → Both switches open → No current → Output = 0Boolean Algebra Properties:
The OR operation follows these fundamental laws:
A OR 0 = A (ORing with 0 preserves the original value)A OR 1 = 1 (ORing with 1 always yields 1)A OR A = A (ORing a value with itself yields that value)A OR B = B OR A (order doesn't matter)(A OR B) OR C = A OR (B OR C) (grouping doesn't matter)A OR (A AND B) = ANOT(A OR B) = (NOT A) AND (NOT B)These laws are essential for simplifying complex Boolean expressions and optimizing bit manipulation code.
AND and OR are duals in Boolean algebra. Every theorem involving AND has a corresponding theorem for OR obtained by swapping AND with OR and swapping 0 with 1. This principle of duality is powerful for deriving new identities and understanding the symmetry of Boolean logic.
Like AND, the bitwise OR operator applies its single-bit logic independently to each corresponding bit position. When you OR two 32-bit integers, you're performing 32 parallel, independent OR operations—one for each bit position.
12345678910111213141516
Operand A: 1 0 1 1 0 1 1 0 (decimal: 182) Operand B: 0 1 0 1 1 0 1 0 (decimal: 90) ───────────────── A OR B: 1 1 1 1 1 1 1 0 (decimal: 254) Position-by-position breakdown: Position 7: 1 OR 0 = 1 Position 6: 0 OR 1 = 1 Position 5: 1 OR 0 = 1 Position 4: 1 OR 1 = 1 Position 3: 0 OR 1 = 1 Position 2: 1 OR 0 = 1 Position 1: 1 OR 1 = 1 Position 0: 0 OR 0 = 0 Notice: The result contains all the 1s from both inputs — it's a "union" of set bits.The Union Interpretation:
If you think of each bit position as representing membership in a set (1 = member, 0 = not member), then OR performs a set union. The result contains every element that appears in either set (or both). This interpretation is fundamental to understanding bitmasks as sets.
12345678910111213141516171819202122232425
// 8-bit integer OR operationconst a = 0b10110110; // 182 in decimalconst b = 0b01011010; // 90 in decimalconst result = a | b; // 0b11111110 = 254 console.log(result.toString(2).padStart(8, '0')); // Output: "11111110" // Practical: Combining two 4-bit nibbles into one byteconst highNibble = 0b1010 << 4; // 0b10100000const lowNibble = 0b0011; // 0b00000011const combined = highNibble | lowNibble; // 0b10100011 = 163 console.log(combined.toString(2).padStart(8, '0'));// Output: "10100011" // Building a 32-bit value from four bytesconst byte3 = 0xAB; // Most significant byteconst byte2 = 0xCD;const byte1 = 0xEF;const byte0 = 0x12; // Least significant byte const value = (byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0;console.log(value.toString(16).toUpperCase()); // Output: "ABCDEF12"An important property: A | B >= max(A, B) for non-negative integers. OR never clears bits—it can only keep them or set them. The result always has at least as many 1-bits as either operand alone. This is crucial when combining flags or building values.
The most important application of OR is setting specific bits to 1 without affecting other bits. This is the conceptual opposite of AND's bit-clearing capability.
The Setting Principle:
Recall the Boolean laws:
A OR 0 = A (0 preserves the original bit)A OR 1 = 1 (1 forces the bit to 1)By creating a mask with 1s in positions we want to set and 0s in positions we want to leave unchanged, we can turn on arbitrary bits.
1234567891011121314
Goal: Set bits 2 and 5 to 1, leave all other bits unchanged Original Value: 1 0 0 1 0 0 0 1 (decimal: 145) 7 6 5 4 3 2 1 0 ← bit positions Mask: 0 0 1 0 0 1 0 0 (0b00100100 = 36) ───────────────── Zeros: unchanged Ones: will be set Result: 1 0 1 1 0 1 0 1 (decimal: 181) ↑ ↑ Bits 5 and 2 are now set! Other bits remain exactly as they were.1234567891011121314151617181920212223242526
// Set bit at position nfunction setBit(value: number, n: number): number { const mask = 1 << n; // Create mask with only bit n set return value | mask;} // Visual example:// value: 10010001 (decimal 145)// 1 << 5: 00100000 (bit 5 set)// OR: 10110001 (decimal 177) — bit 5 now set! const original = 0b10010001; // 145const withBit5 = setBit(original, 5);console.log(withBit5.toString(2).padStart(8, '0'));// Output: "10110001" (177) // Set multiple bits at oncefunction setBits(value: number, mask: number): number { return value | mask;} // Set bits 1, 3, and 6const multiMask = 0b01001010; // Bits to setconst result = setBits(0b00000000, multiMask);console.log(result.toString(2).padStart(8, '0'));// Output: "01001010" — bits 1, 3, 6 are now 1Idempotency: Setting Already-Set Bits
An important property of OR is idempotency with respect to 1s. ORing a bit that's already 1 with another 1 still yields 1. This means setting a bit that's already set has no effect—there's no "double setting" or overflow:
1 | 1 = 1 (not 10, not 2, just 1)
This is crucial for safe repeated flag operations. You can confidently add a flag without checking if it's already present.
AND with mask clears bits where mask has 0s, preserves where mask has 1s. OR with mask sets bits where mask has 1s, preserves where mask has 0s. They're complementary operations that together give complete control over individual bits.
One of the most widespread uses of OR is combining multiple flags into a single value. This pattern appears throughout systems programming, from file permissions to window styles to compilation options.
Why Flags Use OR:
When each flag occupies a unique bit position (typically each flag is a power of 2), ORing them together creates a composite value with multiple flags set. Because no two flags share bit positions, they don't interfere with each other.
1234567891011121314151617181920212223242526272829303132
// Define flags as individual bits (powers of 2)const FileFlags = { READ: 0b00000001, // 1 WRITE: 0b00000010, // 2 EXECUTE: 0b00000100, // 4 HIDDEN: 0b00001000, // 8 SYSTEM: 0b00010000, // 16 ARCHIVE: 0b00100000, // 32 READONLY: 0b01000000, // 64 TEMPORARY: 0b10000000, // 128} as const; // Combine flags using ORconst normalFile = FileFlags.READ | FileFlags.WRITE;console.log(normalFile.toString(2).padStart(8, '0'));// Output: "00000011" — READ and WRITE const executable = FileFlags.READ | FileFlags.EXECUTE;console.log(executable.toString(2).padStart(8, '0'));// Output: "00000101" — READ and EXECUTE const hiddenSystemFile = FileFlags.READ | FileFlags.HIDDEN | FileFlags.SYSTEM;console.log(hiddenSystemFile.toString(2).padStart(8, '0'));// Output: "00011001" — READ, HIDDEN, and SYSTEM // Add a flag to existing flagslet permissions = FileFlags.READ;permissions = permissions | FileFlags.WRITE; // Add WRITEpermissions |= FileFlags.EXECUTE; // Shorthand: Add EXECUTEconsole.log(permissions.toString(2).padStart(8, '0'));// Output: "00000111" — READ, WRITE, and EXECUTEReal-World Examples:
This pattern is ubiquitous in programming APIs:
123456789101112131415161718192021222324252627282930313233343536
// Pattern: Unix-style file open flags (conceptual)const O_RDONLY = 0x0000; // Read-onlyconst O_WRONLY = 0x0001; // Write-onlyconst O_RDWR = 0x0002; // Read-writeconst O_CREAT = 0x0040; // Create if doesn't existconst O_EXCL = 0x0080; // Fail if exists (with O_CREAT)const O_TRUNC = 0x0200; // Truncate to zero lengthconst O_APPEND = 0x0400; // Append mode // Open file for writing, create if needed, truncate if existsconst flags = O_WRONLY | O_CREAT | O_TRUNC; // Pattern: CSS-like text decorations as flagsconst TextDecoration = { NONE: 0, UNDERLINE: 1 << 0, OVERLINE: 1 << 1, LINE_THROUGH: 1 << 2, BLINK: 1 << 3,} as const; const decoration = TextDecoration.UNDERLINE | TextDecoration.LINE_THROUGH;// Creates "strikethrough with underline" effect // Pattern: Keyboard modifier keysconst Modifiers = { NONE: 0, SHIFT: 1 << 0, CTRL: 1 << 1, ALT: 1 << 2, META: 1 << 3, // Cmd on Mac, Win key on Windows} as const; // User pressed Ctrl+Shift+Sconst mods = Modifiers.CTRL | Modifiers.SHIFT;Always define flags as powers of 2 (1 << n) to ensure each occupies a unique bit position. Using 1 << n syntax is clearer than literal powers of 2 and reduces the chance of accidentally reusing a bit position.
Beyond flags, OR is essential for assembling multi-part values where different bit ranges represent different components. This is fundamental in:
12345678910111213141516171819202122232425262728
// RGB color: 8 bits each for Red, Green, Blue// Format: 0xRRGGBB (24-bit color) function makeRGB(r: number, g: number, b: number): number { // Ensure each component is 8 bits (0-255) r = r & 0xFF; g = g & 0xFF; b = b & 0xFF; // Shift each to its position and combine with OR return (r << 16) | (g << 8) | b;} const orange = makeRGB(255, 165, 0);console.log(orange.toString(16).toUpperCase()); // Output: "FFA500" // RGBA color: includes 8-bit alpha channel// Format: 0xAARRGGBB or 0xRRGGBBAA depending on convention function makeRGBA(r: number, g: number, b: number, a: number): number { return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);} const semiTransparentRed = makeRGBA(255, 0, 0, 128);console.log(semiTransparentRed.toString(16).toUpperCase());// Output: "80FF0000" (128 alpha, 255 red, 0 green, 0 blue)Assembling Network Packets:
Network protocols often pack multiple fields into headers. Here's how you might construct a simplified packet header:
12345678910111213141516171819202122232425262728293031
// Simplified packet header (16 bits):// Bits 14-15: Version (2 bits, 0-3)// Bits 10-13: Type (4 bits, 0-15)// Bits 0-9: Length (10 bits, 0-1023) function buildPacketHeader( version: number, // 0-3 type: number, // 0-15 length: number // 0-1023): number { // Mask each field to its allowed range, then shift and combine const versionBits = (version & 0b11) << 14; // Bits 14-15 const typeBits = (type & 0b1111) << 10; // Bits 10-13 const lengthBits = length & 0b1111111111; // Bits 0-9 return versionBits | typeBits | lengthBits;} const header = buildPacketHeader(2, 5, 512);console.log(header.toString(2).padStart(16, '0'));// Output: "1001010000000000"// ^^----version=2// ^^^^----type=5// ^^^^^^^^^^----length=512 // Decode for verificationconst extractedVersion = (header >> 14) & 0b11;const extractedType = (header >> 10) & 0b1111;const extractedLength = header & 0b1111111111;console.log({ extractedVersion, extractedType, extractedLength });// Output: { extractedVersion: 2, extractedType: 5, extractedLength: 512 }When building multi-field values, always mask each component to its allowed bit width BEFORE shifting. If a component exceeds its range, the overflow bits will corrupt adjacent fields. The pattern (value & mask) << shift prevents this.
When bits represent set membership, OR performs a set union. This interpretation is powerful for algorithms that track presence, combine results, or merge state.
Applications:
12345678910111213141516171819202122232425
// Represent sets as bitmasks// Bit n is 1 if element n is in the set const setA = 0b10110011; // Elements: {0, 1, 4, 5, 7}const setB = 0b01011100; // Elements: {2, 3, 4, 6} // Union: elements in A OR Bconst union = setA | setB;console.log(union.toString(2).padStart(8, '0'));// Output: "11111111" — Elements: {0, 1, 2, 3, 4, 5, 6, 7} // Helper to list elements in a bitmask setfunction listElements(set: number): number[] { const elements: number[] = []; for (let i = 0; i < 32; i++) { if ((set & (1 << i)) !== 0) { elements.push(i); } } return elements;} console.log("Set A:", listElements(setA)); // [0, 1, 4, 5, 7]console.log("Set B:", listElements(setB)); // [2, 3, 4, 6]console.log("Union:", listElements(union)); // [0, 1, 2, 3, 4, 5, 6, 7]Merging Configuration Options:
OR is useful when combining options from multiple sources with a "most permissive" policy—if any source enables an option, it's enabled in the result.
1234567891011121314151617181920212223242526
// Feature flags from different config sourcesconst Features = { DARK_MODE: 1 << 0, BETA_TESTING: 1 << 1, ANALYTICS: 1 << 2, PREMIUM: 1 << 3, OFFLINE: 1 << 4,} as const; // Config from different sourcesconst defaultConfig = Features.ANALYTICS; // Default: just analyticsconst userPrefs = Features.DARK_MODE | Features.OFFLINE;const adminOverrides = Features.BETA_TESTING | Features.PREMIUM; // Merge: enable feature if ANY source enables itconst finalConfig = defaultConfig | userPrefs | adminOverrides; console.log("Final config:", finalConfig.toString(2).padStart(8, '0'));// Output: "00011111" — All features enabled // Check which features are enabledconst hasFeature = (config: number, feature: number) => (config & feature) !== 0; console.log("Has dark mode?", hasFeature(finalConfig, Features.DARK_MODE)); // trueconsole.log("Has premium?", hasFeature(finalConfig, Features.PREMIUM)); // trueRemember that OR can only add 1s, never remove them. For merging with "any source can disable" semantics, you'd use AND instead. Choose OR for "any source can enable" (union) and AND for "all sources must enable" (intersection).
Beyond its direct uses, OR appears in several clever optimization patterns that exploit its properties.
Pattern 1: Convert to Boolean Without Branching
OR with zero is identity, but OR can help normalize non-zero values to 1 in certain contexts.
1234567891011121314151617181920
// Check if a number is non-zero (any bit set)// Traditional: value !== 0// Bitwise context: we want 0 or 1 function hasAnyBitSet(value: number): number { // Collapse all bits to position 0 using OR reductions // For a 32-bit value: let v = value; v = v | (v >> 16); // Combine upper and lower 16 bits v = v | (v >> 8); // Combine into lower 8 bits v = v | (v >> 4); // Combine into lower 4 bits v = v | (v >> 2); // Combine into lower 2 bits v = v | (v >> 1); // Combine into bit 0 return v & 1; // Mask to get 0 or 1} console.log(hasAnyBitSet(0)); // 0console.log(hasAnyBitSet(1)); // 1console.log(hasAnyBitSet(0x80000000)); // 1 (only high bit set)console.log(hasAnyBitSet(12345678)); // 1Pattern 2: Branchless Maximum
OR can help compute maximum without conditional branches, which is useful in SIMD-style programming.
1234567891011121314151617181920212223242526272829303132
// Round up to the next power of 2// Uses OR to propagate the highest set bit down function nextPowerOf2(n: number): number { if (n <= 0) return 1; n--; // Handle case where n is already a power of 2 // Propagate the highest bit to all lower positions n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n |= n >> 16; return n + 1;} // How it works for n = 20:// n = 19 = 0b00010011// n |= n >> 1 = 0b00011011// n |= n >> 2 = 0b00011111// n |= n >> 4 = 0b00011111// n |= n >> 8 = 0b00011111// n |= n >> 16 = 0b00011111// n + 1 = 0b00100000 = 32 console.log(nextPowerOf2(1)); // 1console.log(nextPowerOf2(5)); // 8console.log(nextPowerOf2(16)); // 16 (already power of 2)console.log(nextPowerOf2(17)); // 32console.log(nextPowerOf2(100)); // 128The pattern of n |= n >> k for doubling k values (1, 2, 4, 8, 16) is a powerful technique for "smearing" the highest set bit to fill all lower positions. This creates a mask of 1s from the highest bit down, useful for power-of-2 operations and bit counting optimizations.
The OR operator is the natural complement to AND, providing the inclusive counterpart to AND's exclusive filtering. Let's consolidate what we've learned:
| Operation | Expression | Notes |
|---|---|---|
| Set bit n | value | (1 << n) | Set bit to 1 |
| Set multiple bits | value | mask | Mask has 1s where to set |
| Combine flags | flag1 | flag2 | flag3 | Each flag is power of 2 |
| Build from parts | (a << n) | b | Shift then combine |
| Set union | setA | setB | Elements in either set |
| Propagate bits down | n |= n >> k | Smear highest bit |
| Ensure minimum 1 | n | 1 | Lowest bit always set |
What's Next:
With AND and OR understood, we'll explore XOR—the exclusive or operator that outputs 1 when inputs differ. XOR has unique properties that make it invaluable for toggling, swapping, encryption, and error detection.
You now have deep understanding of the OR bitwise operator—from its Boolean algebra roots to practical applications in setting bits, combining flags, building composite values, and computing set unions. Combined with AND, you have powerful tools for selective bit manipulation.