NAND, NOR & XNOR: Beyond Basic Bitwise Operations
Most bitwise tutorials stop at AND, OR, XOR and NOT. That covers the basics. But there are three more operations that show up constantly in hardware design, low-level optimization, and interview problems: NAND, NOR, and XNOR.
NAND and NOR are called universal gates. You can build every other logic function — AND, OR, NOT, XOR — using only NAND gates. Or only NOR gates. This is why they matter: in chip design, sticking to one gate type simplifies manufacturing. In code, understanding the inverse operations gives you shorter expressions.
NAND (NOT AND)
NAND inverts the output of AND. If AND says 1 only when both inputs are 1, NAND says 0 only when both inputs are 1. In C-like languages, there's no single NAND operator — you write ~(a & b).
| A | B | A AND B | A NAND B |
|---|---|---|---|
| 0 | 0 | 0 | 1 |
| 0 | 1 | 0 | 1 |
| 1 | 0 | 0 | 1 |
| 1 | 1 | 1 | 0 |
Real use: NAND flash memory. Every cell in your SSD stores data using a NAND gate structure. The name isn't random — the cells are physically arranged as a NAND logic chain.
// NAND in C
int nand(int a, int b) {
return ~(a & b);
}
// NAND truth table test
assert(nand(0, 0) == 1);
assert(nand(0, 1) == 1);
assert(nand(1, 0) == 1);
assert(nand(1, 1) == 0); // only case where output is 0
NOR (NOT OR)
NOR outputs 1 only when both inputs are 0. It's the inverse of OR. In code: ~(a | b).
| A | B | A OR B | A NOR B |
|---|---|---|---|
| 0 | 0 | 0 | 1 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 1 | 0 |
NOR has a neat property: it's the only gate that outputs 1 for exactly the (0,0) input pair. That makes it useful for detecting when nothing is set — think interrupt flags that are all clear, or empty bitmasks.
// NOR in C
int nor(int a, int b) {
return ~(a | b);
}
// Check if no flags are set
int flags = read_status_register();
if (nor(flags, 0xFF) == 0xFF) {
// All bits in the lower byte are 0
}
XNOR (Exclusive NOR)
XNOR outputs 1 when inputs are equal. It's the opposite of XOR. This is sometimes called the "equivalence" gate because it answers: "are these two bits the same?"
| A | B | A XOR B | A XNOR B |
|---|---|---|---|
| 0 | 0 | 0 | 1 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 |
In code: ~(a ^ b). XNOR comes up in parity checking, digital comparators, and anywhere you need to test bitwise equality.
// XNOR: check if two values are bitwise-equal
int xnor(int a, int b) {
return ~(a ^ b);
}
// In Python, the ~ operator gives two's complement, so mask it:
def xnor(a, b, bits=8):
mask = (1 << bits) - 1
return (~(a ^ b)) & mask
# In JavaScript:
const xnor = (a, b) => ~(a ^ b);
Universal Gate Implementation
Here is every basic gate, built from NAND only. This is the foundation of digital logic design — and it shows up in interview questions.
| Target Gate | Using Only NAND |
|---|---|
| NOT A | NAND(A, A) |
| A AND B | NOT(NAND(A, B)) = NAND(NAND(A,B), NAND(A,B)) |
| A OR B | NAND(NOT A, NOT B) |
| A XOR B | NAND(NAND(A, NAND(A,B)), NAND(B, NAND(A,B))) |
When Do These Actually Matter?
1. NAND Flash and SSD Controllers
Every time you write a file, your OS talks to a NAND flash controller. The name isn't marketing — the memory cells use a physical NAND structure. If you're doing embedded systems or OS development, you'll see SLC/MLC/TLC/QLC NAND in datasheets.
2. De Morgan's Laws in Code
De Morgan's laws state: ~(A & B) = ~A | ~B and ~(A | B) = ~A & ~B. These let you rewrite conditions. Sometimes !(a && b) reads better as !a || !b — same logic, different clarity.
3. Digital Comparators
An XNOR gate is a 1-bit equality checker. Chain 8 of them together and you get an 8-bit comparator. CPUs do this in hardware — the ALU has dedicated XNOR chains for the CMP instruction.
// 1-bit equality using XNOR
bool is_equal_bit(int a, int b, int bit_pos) {
int mask = 1 << bit_pos;
int bit_a = (a & mask) >> bit_pos;
int bit_b = (b & mask) >> bit_pos;
return ~(bit_a ^ bit_b) & 1; // XNOR → 1 if equal
}