JavaScript Ternary Operator Table
| Pattern | What it does | Field note |
|---|---|---|
cond ? a : b | Pick one of two values | An EXPRESSION - it has a value; if/else is a statement |
x = cond ? a : b | Assign conditionally | The everyday form; const works, if/else assign does not |
cond1 ? a : cond2 ? b : c | Chained ternary | Reads as if/else-if/else; indent for the reader |
cond ?? fallback | Nullish default only | ?? skips the default for 0 and ''; ternary skips for any falsy |
cond && (x = a) | Guard without else | Shorter but a side-effect pattern; ternary is clearer |
The ternary operator is JavaScript's only three-operand operator: condition ? valueIfTrue : valueIfFalse. It is an EXPRESSION - it produces a value - which is the entire reason it exists and the entire reason to choose it over if/else. The table below is the working five: the assignment form, chaining, the ?? alternative, and the honest boundaries.
Bottom line: ternary for values, if/else for side effects. When both branches produce a value (assignment, return, template literal, default parameter), the ternary reads in one line where if/else takes six. When the branches trigger different actions (fetch one API, redirect to another), if/else reads better - the ternary's branches should be pure values, not statements in disguise.
The honest part: the chaining rule keeps nested ternaries honest. cond1 ? a : cond2 ? b : c reads as if/else-if/else - the engine parses right-associatively, so the chain falls through naturally. The failure mode is ternaries inside ternaries INSIDE template literals: if you need scroll-to-read, the branches belong in a helper function with real if/else.
How to use
- Use the ternary where an expression is required: assignments, return statements, template literals, default parameters, JSX.
- Chain with indentation: each : starts a new line at the same indent as its ? - the else-if ladder reads top to bottom.
- Reach for ?? instead when the condition is just a null check: x ?? fallback is shorter than x !== null && x !== undefined ? x : fallback.
Frequently asked questions
When is a ternary better than if/else?
When the result is a VALUE: assignments (const max = a > b ? a : b), return statements, template-literal slots, default parameters, and JSX conditionals are all expression positions where if/else needs six lines of scaffolding. The switch to if/else is right when the branches have side effects - calling different functions, redirecting, throwing. The litmus test: could both branches be the right-hand side of an equals sign? Then ternary.
How does the chained ternary parse?
Right-associatively: a ? x : b ? y : z parses as a ? x : (b ? y : z) - the engine evaluates the condition, and if falsy, falls into the next ternary as the else branch. Written with indentation it reads exactly like an if/else-if ladder. The hazard is ambiguity when a branch itself contains a nested ternary without parentheses - parenthesize the inner one, or extract it to a variable.
What is the difference between the ternary and the ?? operator?
They overlap on one case and differ on the rest. x !== null && x !== undefined ? x : fallback is what ?? does in one token: x ?? fallback. But the ternary's condition can be ANY test (x > 0 ? x : 0 clamps negatives; str.includes(y) ? str : fallback), while ?? only checks nullish-ness. The modern pairing: ?? for nullish defaults, ternary for every other conditional value.
Why do linters flag nested ternaries?
Because readability scales worse than any other construct: a three-deep nested ternary has 9 tokens between ? and : and no named intermediate values to anchor the reader. The linter's suggestion (extract to a function with real if/else) is not pedantry - naming the branches (const plan = isPremium ? premiumPlan : basicPlan) gives the condition a name too. The one-nesting rule keeps ternaries in the sweet spot: concise without becoming a puzzle.