The rest of the operators
Finish JavaScript's operator toolbox: ternary choices, type checks, property checks, delete, bitwise flags, shifts, comma, void, and the precedence table.
- 01Choose expressions on purposeUse the ternary operator when it stays readable, and avoid clever nests.
- 02Inspect values and propertiesPick between
typeof,instanceof,Array.isArray,in, andObject.hasOwn. - 03Read rare operators safelyDecode bitwise flags, shifts, comma, void, and precedence without guessing.
The last operator map
You already know the everyday operators: arithmetic, assignment, comparisons, logical operators, ??, and optional chaining. This lesson finishes the operator map. Some of these operators are common, like the ternary. Some are rare, like void. A few are powerful but easy to misuse, like bitwise operators.
The goal is not to make your code clever. In the Coding style lesson you learned to avoid tricks that make readers pause. Here you will learn what these operators mean so you can read them in real code, choose the clear ones, and know when to reach for a named helper or an if statement instead.
Use the ternary for one small choice, use type and property checks deliberately, treat bitwise operators as 32-bit integer tools, and use parentheses when precedence is not obvious.
A good workshop has more tools than you use every day. You do not hammer with a voltage tester, and you do not use delete to remove an array item from the middle. Knowing the tool’s shape helps you pick the right one and recognize it when someone else used it.
- In real life: A screwdriver used every day
- In JavaScript:
+,===,&&, and?? - In real life: A compact pocket tool
- In JavaScript: The ternary for one small expression choice
- In real life: A voltage tester
- In JavaScript:
typeof,instanceof,in, andObject.hasOwn - In real life: A specialized bit driver
- In JavaScript: Bitwise operators for flags and binary data
Where the analogy stops: A real tool has one physical job. Operators can be combined, and JavaScript may convert values before using them, so always check the exact language rule.
We will move from the friendly, expression-sized ternary to type checks, property checks, bitwise operators, shifts, comma, void, and a practical precedence table you can use while reading code.
The conditional (ternary) operator
STEP THROUGHThe conditional operator is the only JavaScript operator with three operands. Its shape is:
condition ? valueIfTrue : valueIfFalseRead it like a quick fork in the road: if the condition is truthy, JavaScript takes the left path after ?; otherwise it takes the right path after :. Because the whole thing is an expression, it produces a value. That means you can store it, return it, or put it inside a template literal.
Imagine a staff member checking an age and handing out one of two wristbands. There is no long conversation: one question, one of two answers. That is the sweet spot for a ternary.
- In real life: The sign: “Age 18 or older?”
- In JavaScript: The condition:
age >= 18 - In real life: Left path: adult wristband
- In JavaScript: The true branch:
"adult" - In real life: Right path: minor wristband
- In JavaScript: The false branch:
"minor" - In real life: The wristband you leave with
- In JavaScript: The expression’s final value
Where the analogy stops: A real road can split into many paths. A ternary is best for one small two-way choice; nested ternaries quickly become hard to read.
Predict which branch the condition chooses. Then step through the expression one event at a time.
script
const label = age >= 18 ? "adult" : "minor";const sentence = `This visitor is an ${label}.`;console.log(sentence);A good ternary reads in one breath:
const shipping = cartTotal >= 500 ? "free" : "₹50";
console.log(`Shipping is ${shipping}.`);A nested ternary asks the reader to hold several forks in their head:
const label = score >= 90 ? "A" : score >= 80 ? "B" : "keep practicing";
let readableLabel;
if (score >= 90) {
readableLabel = "A";
} else if (score >= 80) {
readableLabel = "B";
} else {
readableLabel = "keep practicing";
}The next lesson is if, else & conditions. For now, use this rule: ternary for a small value choice; if / else when the logic needs room.
typeof & instanceof
STEP THROUGHtypeof asks, “what kind of value is this?” It is most useful for primitives: "string", "number", "boolean", "bigint", "symbol", "undefined", and "function". For most objects, it simply says "object".
instanceof asks a different question: “does this object come from this constructor’s family tree?” That is a simple way to use it today. The prototype chain lesson later explains the exact chain it walks.
Step through the checks. Notice where typeof, instanceof, and Array.isArray answer different questions.
script
console.log(typeof values[0]);console.log(Array.isArray(values[1]));console.log(values[1] instanceof Array);console.log(values[1] instanceof Object);console.log(values[3] instanceof String);| Question | Good check | Watch out |
|---|---|---|
| Is it a primitive string? | typeof value === "string" | new String("x") is an object, not a primitive. |
| Is it missing? | value === null or value === undefined | typeof null is the old quirk "object". |
| Is it an array? | Array.isArray(value) | typeof [] is "object". |
| Was it made by a constructor? | value instanceof Date | Different browser frames can have different constructors; learn the nuance later. |
typeof null is still objectThis is a very old JavaScript bug that cannot be changed without breaking existing websites. The Data types lesson introduced it; here you saw why professional code checks null directly.
in & delete
STEP THROUGH"key" in object checks whether a property is available on an object. It includes inherited properties, so it answers “can this key be found?” not “did this object define the key itself?” For own properties, use Object.hasOwn(object, "key").
delete object.key removes a configurable property. It is useful for object dictionaries, but not for normal array removal: deleting an array element leaves a hole and keeps the same length.
Follow in, Object.hasOwn, and delete. Watch the array hole: length and existing keys are different ideas.
script
console.log("name" in user);console.log("toString" in user);console.log(Object.hasOwn(user, "toString"));delete user.age;console.log("age" in user);const letters = ["a", "b", "c"];delete letters[1];console.log(letters.length);console.log(1 in letters);Some built-in properties are not configurable. For example, delete Object.prototype evaluates to false in sloppy scripts and throws a TypeError in strict mode. Modern modules are strict by default, so failed deletes are often loud instead of silent.
| Check | Includes inherited keys? | Beginner-friendly use |
|---|---|---|
"name" in user | Yes | Feature checks or cases where inherited methods count. |
Object.hasOwn(user, "name") | No | Checking data that should be stored directly on this object. |
delete user.age | Not a check | Remove a configurable object property when absence has meaning. |
Bitwise operators: & | ^ ~
INTERACTIVEJavaScript numbers are 64-bit floating-point values, as you learned in Numbers & math. Bitwise operators are different: before they work, JavaScript converts the number to a 32-bit integer. Then it treats the integer as a row of 32 tiny switches: each switch is 0 or 1.
Picture two rows of light switches. & asks which switches are on in both rows. | asks which are on in either row. ^ asks which switches differ. ~ flips the entire row.
- In real life: Switch off
- In JavaScript: Bit
0 - In real life: Switch on
- In JavaScript: Bit
1 - In real life: Both switches must be on
- In JavaScript:
& - In real life: At least one switch is on
- In JavaScript:
| - In real life: Exactly one switch is on
- In JavaScript:
^ - In real life: Flip every switch
- In JavaScript:
~
Where the analogy stops: The playground shows 8 switches so it fits on the page. JavaScript's bitwise operators actually work with 32 switches, which is why ~5 becomes -6.
const a = 12; // 00001100const b = 10; // 00001010console.log(a & b); // both bits are 1console.log(a | b); // either bit is 1console.log(a ^ b); // bits are differentconsole.log(~a); // flip 32 bitsconsole.log(a << 1); // shift leftconsole.log(a >> 1); // shift rightconsole.log(a >>> 1); // unsigned shift right00001000both bits are 100001110either bit is 100000110bits differ11110011flip 32 bits00011000slide left00000110signed slide right00000110unsigned slide righta is 12 (00001100), b is 10 (00001010). JavaScript computes these operators by first converting to 32-bit integers; this playground shows the low 8 bits so the switches fit on screen.
~ and >>> still use all 32 bits, which is why ~12 is negative.console.log(2 ** 32 | 0); // 0: wraps to a 32-bit signed integer
console.log(~5); // -6: flip all 32 bits
console.log(-1 >>> 0); // 4294967295: read all bits as unsignedA realistic beginner-friendly use is permission flags. Give each permission one bit, combine flags with |, test one flag with &, and toggle a flag with ^.
const READ = 1; // 001
const WRITE = 2; // 010
const EXECUTE = 4; // 100
let permissions = READ | WRITE;
console.log((permissions & WRITE) !== 0);
permissions = permissions ^ WRITE;
console.log((permissions & WRITE) !== 0);Shifts, comma & void
STEP THROUGHShift operators slide the 32-bit switches left or right. << shifts left and fills with zeros on the right. >> shifts right while keeping the sign bit, so negative numbers stay negative. >>> shifts right and fills with zeros, producing an unsigned 32-bit result.
console.log(5 << 1); // 10
console.log(5 >> 1); // 2
console.log(-1 >>> 0); // 4294967295Two final operators mostly appear in older or specialized code. The comma operator evaluates expressions from left to right and keeps the last value. The void operator evaluates an expression, throws away its value, and produces undefined.
Comma evaluates in order and keeps the last value. void is a black hole: it always gives undefined.
script
i++, j--;const kept = (1, 2, 3);const swallowed = void kept;console.log(i, j, kept, swallowed);Old code sometimes used void 0 as a guaranteed undefined, or <a href="javascript:void(0)"> to make a link do nothing. Modern code should use real buttons, event handlers, and the normal undefined value instead.
The operator precedence table
SORTING GAMEPrecedence answers: “which operator groups with its operands first?” It does not mean “which part is evaluated first” in every detail; many expressions still evaluate their operands left to right. For beginners, the practical rule is: when mixing unfamiliar operators, add parentheses.
| Level | Group | Operators | Memory hook |
|---|---|---|---|
| 1 | Grouping | (...) | Forces an expression to happen first. |
| 2 | Member access and calls | ., [], ?., () | Read a property or call a function before most operators around it. |
| 3 | Postfix update | x++, x-- | Use the current value, then update the variable. |
| 4 | Unary | !, ~, +x, -x, typeof, void, delete | One-operand operators bind before binary math. |
| 5 | Exponentiation | ** | Right-associative: 2 ** 3 ** 2 means 2 ** (3 ** 2). |
| 6 | Multiply / divide | *, /, % | Higher than addition and subtraction. |
| 7 | Add / subtract | +, - | Also includes string concatenation with +. |
| 8 | Shifts | <<, >>, >>> | Move 32-bit integer bits left or right. |
| 9 | Relational | <, >, <=, >=, in, instanceof | Comparisons and membership checks. |
| 10 | Equality | ==, !=, ===, !== | Usually prefer strict equality. |
| 11 | Bitwise AND | & | Works on each bit. |
| 12 | Bitwise XOR | ^ | Bits that differ. |
| 13 | Bitwise OR | | | Bits from either side. |
| 14 | Logical AND | && | Short-circuits and returns an operand. |
| 15 | Logical OR | || | Short-circuits and returns an operand. |
| 16 | Nullish coalescing | ?? | Chooses the right side only for null or undefined. |
| 17 | Conditional | condition ? a : b | Chooses one of two expressions. |
| 18 | Assignment | =, +=, ??=, &&=, ||= | Stores a value. |
| 19 | Comma | , | Evaluates left to right and keeps the last value. |
Sort the cards by whether they bind higher or lower than addition. If you are unsure, the feedback tells you where the operator sits.
getTotal()typeof valueprice * taxsubtotal + taxtotal - discountflags << 1ready && savedok ? a : bfirst(), second()
Place each card. The goal is not memorizing the whole table; it is spotting when parentheses would make code clearer.
Where you’ll use this
In everyday front-end code, these operators show up in predictable places:
- Use a ternary to choose a CSS class, label, or short message:
const tone = error ? "danger" : "success"; - Use
typeofat API boundaries when a value might be a string, number, or function. - Use
Array.isArraybefore calling array methods on data you did not create. - Use
Object.hasOwnwhen reading plain object data where inherited keys should not count. - Read bitwise flags in libraries, file permissions, graphics, and low-level browser APIs, even if you do not write them often.
function describeUpload(file) {
const kind = Array.isArray(file.tags) ? "tagged" : "untagged";
const owner = Object.hasOwn(file, "owner") ? file.owner : "unknown";
return `A ${kind} file owned by ${owner}.`;
}Common misconceptions
“A ternary is always shorter, so it is always better.”
Shorter is not the same as clearer. A one-line label choice is a good ternary. Multiple branches usually deserve if /else or a lookup object.
“typeof tells me the exact type of everything.”
typeof null is "object", arrays are "object", and dates are "object". Use more specific checks when you need them.
“in means the object owns that property.”
in includes inherited keys like toString. Use Object.hasOwn for own data properties.
“delete removes an array item like splice.”
It leaves a hole. Use array methods such as splice or filtering when you want a shorter array.
“Bitwise operators are just faster math.”
They force 32-bit integer conversion. Tricks like ~~x can hide bugs and go against the readability advice from Coding style.
“Precedence tables replace parentheses.”
Professionals still add parentheses when mixed operators might slow the reader down. Clarity wins.
Practice: the rest of the operators
5 EXERCISESWork out which branch is chosen before you run the code.
const age = 15;
const label = age >= 18 ? "adult" : "minor";
console.log(label);15 >= 18 is false, so the ternary keeps "minor" and the console prints minor.
Rewrite the nested ternary as an if / else if / else chain.
const label = score >= 90 ? "A" : score >= 80 ? "B" : "keep practicing";let label;
if (score >= 90) {
label = "A";
} else if (score >= 80) {
label = "B";
} else {
label = "keep practicing";
}The readable version gives each branch its own line. The answer checker only asks for the first keyword: if.
Predict the two printed boolean values.
const READ = 1;
const WRITE = 2;
const EXECUTE = 4;
let permissions = READ | EXECUTE;
console.log((permissions & WRITE) !== 0);
permissions = permissions | WRITE;
console.log((permissions & WRITE) !== 0);The first check is false because the WRITE bit is not set. permissions | WRITE adds it, so the second check is true.
Which call checks only the keys stored directly on user?
const user = { name: "Ada" };
console.log(Object.hasOwn(user, "toString"));Use Object.hasOwn(user, "toString") when inherited keys should not count. It prints false for this object.
Predict both lines. This is where the hidden 32-bit conversion matters.
console.log(~5);
console.log(-1 >>> 0);~5 flips all 32 bits, which is -6. -1 has every 32-bit switch on; read unsigned, that is 4294967295.
Quiz: check your understanding
8 QUESTIONSTake your time. Every answer explains why it is right or wrong, and code questions are based on real JavaScript output.
Question 1 of 8What does the ternary print?
Read the code, then predictconst age = 20; console.log(age >= 18 ? "adult" : "minor");Choose an answer to see the explanation.
Question 2 of 8Which check is the reliable way to detect arrays?
Choose an answer to see the explanation.
Question 3 of 8What do
inandObject.hasOwnprint?Read the code, then predictconst user = { name: "Ada" }; console.log(("toString" in user) + " " + Object.hasOwn(user, "toString"));Choose an answer to see the explanation.
Question 4 of 8What does deleting an array element print?
Read the code, then predictconst letters = ["a", "b", "c"]; delete letters[1]; console.log(letters.length + " " + (1 in letters));Choose an answer to see the explanation.
Question 5 of 8What do these bitwise expressions print?
Read the code, then predictconsole.log(~5 + " " + (-1 >>> 0));Choose an answer to see the explanation.
Question 6 of 8Which expression tests whether the WRITE permission flag is present?
Choose an answer to see the explanation.
Question 7 of 8What do comma and void print?
Read the code, then predictconst value = (1, 2, 3); console.log(value + " " + (void value));Choose an answer to see the explanation.
Question 8 of 8Which operator in this lesson has the lowest precedence?
Choose an answer to see the explanation.
Key takeaways
- A ternary is an expression-sized two-way choice:
condition ? left : right. typeofis best for primitives; useArray.isArrayfor arrays andinstanceoffor constructor family checks.inincludes inherited properties;Object.hasOwnchecks only direct keys.deleteremoves object properties, but on arrays it leaves holes and keepslength.- Bitwise operators convert to 32-bit integers; flags are a clear use, clever number tricks are not.
- Comma and
voidare rare; recognize them, but do not reach for them first. - Precedence explains grouping, but parentheses explain your intent to humans.
Remember the one-liner.
The “other operators” are specialized expression tools: useful when they make a choice, check, flag, or grouping clearer than the long form.
Up next: if, else & conditions, where choices get full statements, blocks, and room to breathe.