JAVASCRIPT / OPERATORS
Ternary conditionals without nesting pain
Use the conditional operator to pick values inline, flatten multi-case decisions into a flat else-chain, and know when to switch to if or a lookup.
What you will learn
- Use ?: to choose a value where an if statement cannot go, such as a template literal
- Extend a decision through the else slot so chains stay flat instead of nesting
- Rely on only the taken branch running, so the unused branch can hold unsafe code
- Parenthesize a ternary inside + or a comparison, since ?: binds looser than both
Understanding Ternary conditionals without nesting pain
The conditional operator takes three operands: condition ? whenTrue : whenFalse. Unlike if, which is a statement, this is an expression, so it produces a value and can appear anywhere a value is allowed: a const initializer, a function argument, the value half of an object literal, a ${} slot in a template literal. That is the real reason to reach for it, not brevity but the fact that if simply cannot stand in those positions.
?: is right-associative, and that is what makes long chains painless. a ? 1 : b ? 2 : 3 groups as a ? 1 : (b ? 2 : 3) with no parentheses, because after the colon the parser wants one more expression and a whole ternary is an expression. Written one rung per line with each line starting at the colon, it reads as a list of guarded answers with a default at the bottom. The pain comes from the other slot: a ? b ? 1 : 2 : 3 puts the second question before the first colon, so a reader has to count colons backwards to see which answer belongs to which test.
Two signals tell you to stop. First, ?: binds looser than nearly every other operator, so a ternary embedded in concatenation, arithmetic or a comparison needs parentheses or the surrounding expression gets swallowed into the condition. Second, a ternary chooses a value; when the branches call functions for their effects, or when the rungs test unrelated subjects instead of one subject, an if statement, an object keyed by the input, or a function with early returns communicates more and lets each case carry a comment.
const grade = (score) =>
score >= 90 ? 'A'
: score >= 80 ? 'B'
: score >= 70 ? 'C'
: 'F';
console.log([95, 83, 71, 42].map(grade).join(' '));
// Right-associative: the chain above groups as nested else-branches.
console.log(grade(83) === (83 >= 90 ? 'A' : (83 >= 80 ? 'B' : (83 >= 70 ? 'C' : 'F'))));
// An expression, so it fits where an if statement cannot go.
const n = 1;
console.log(`Found ${n} file${n === 1 ? '' : 's'}`);A ternary is a value-producing expression that evaluates only the branch it selects, and because ?: is right-associative, extra cases belong in the else slot where they stay flat.
Worked examples
Precedence trap in string concatenation
Shows how a missing pair of parentheses turns the whole message into the ternary's condition.
for (const n of [1, 3]) {
console.log('You have ' + n + ' item' + n === 1 ? '' : 's');
console.log('You have ' + n + ' item' + (n === 1 ? '' : 's'));
}Example explained
Line 1+ binds tighter than ===, which binds tighter than ?:, so the condition is really ('You have 1 item1') === 1.
Line 2That comparison is false for every n, so the broken line always picks 's' and the message never reaches console.log.
Line 3Wrapping the ternary in parentheses restores the intended grouping: only the suffix is chosen conditionally.
Trading a long chain for a lookup
Replaces a four-rung equality chain with an object plus one ternary for the fallback.
const label = (status) =>
status === 'open' ? 'Open'
: status === 'ack' ? 'Acknowledged'
: status === 'closed' ? 'Closed'
: 'Unknown';
const LABELS = { open: 'Open', ack: 'Acknowledged', closed: 'Closed' };
const lookup = (status) =>
Object.hasOwn(LABELS, status) ? LABELS[status] : 'Unknown';
for (const s of ['ack', 'closed', 'toString']) {
console.log(s, '->', label(s), '/', lookup(s));
}Example explained
Line 1Every rung of the chain repeats the same shape, status === X ? Y, which is data pretending to be control flow.
Line 2The object holds that data as keys, so the ternary shrinks to one decision: known key or default.
Line 3Object.hasOwn is used rather than 'toString' in LABELS, which is true for inherited keys and would return a function instead of 'Unknown'.
Only one branch is evaluated
Demonstrates that the branch not selected is never run, so it may contain code that would throw.
const rows = [];
const first = rows.length > 0 ? rows[0].name.toUpperCase() : 'EMPTY';
console.log(first);
const seen = (label) => { console.log('ran', label); return label; };
console.log(rows.length > 0 ? seen('has rows') : seen('no rows'));Example explained
Line 1rows[0].name would raise a TypeError on an empty array, but it sits in the untaken branch and is never evaluated.
Line 2seen logs whenever it runs, and exactly one 'ran' line appears, so the other call was skipped entirely.
Line 3This laziness is why a ternary can guard its own risky branch, unlike a function call whose arguments are all evaluated before the call.
Important notes
The condition is tested for truthiness, not compared with true, so 0, '' and NaN all take the false branch; count ? count : 'none' quietly hides a legitimate zero.
Both branches must be expressions, so ok ? x : throw new Error('no') is a SyntaxError because throw is a statement; call a helper that throws instead.
Common mistakes
Writing 'item' + n === 1 ? '' : 's' without parentheses: + and === bind tighter than ?:, so the text joins the condition and the same branch wins every time.
Nesting in the true branch, as in a ? b ? 1 : 2 : 3; the colons pair by position, so inserting a case later re-pairs them silently and produces the wrong value with no error.
Using a ternary for side effects, like ok ? save() : warn(): the produced value is discarded, and as soon as one branch needs a second statement the expression cannot be extended.
Try it yourself
Change, predict, then run
In a browser console, write describeTemp(c) as one flat ternary chain that returns 'freezing' below 0, 'cold' below 15, 'mild' below 25 and 'hot' otherwise. Log it for -5, 10, 20 and 30 and confirm you get four different labels.
Open the JavaScript workspaceCheck your understanding
A chain like a ? 1 : b ? 2 : 3 is unambiguous without parentheses, while a ternary nested in the true slot needs careful reading. What makes the trailing chain unambiguous to the parser?
- ?: has higher precedence than the comparison operators, so the conditions group before the branches.
- The parser rewrites ternary chains into an equivalent switch statement.
- ?: is right-associative, so a trailing ternary is absorbed into the else operand automatically.
- The else operand accepts only a single literal value, so the parser stops looking after it.
Show answer
Right-associativity groups a ? 1 : b ? 2 : 3 as a ? 1 : (b ? 2 : 3), and the else operand accepts any expression, another ternary included, so no parentheses are needed. The first option has it backwards: ?: binds looser than comparison, which is exactly why x > 0 ? ... : ... needs no parentheses around the test, and precedence governs how different operators mix, not how two ternaries in a row are grouped.