JavaScript Ternary Operator Table

PatternWhat it doesField note
cond ? a : bPick one of two valuesAn EXPRESSION - it has a value; if/else is a statement
x = cond ? a : bAssign conditionallyThe everyday form; const works, if/else assign does not
cond1 ? a : cond2 ? b : cChained ternaryReads as if/else-if/else; indent for the reader
cond ?? fallbackNullish default only?? skips the default for 0 and ''; ternary skips for any falsy
cond && (x = a)Guard without elseShorter but a side-effect pattern; ternary is clearer
Reference: the MDN conditional operator reference. The ternary is JavaScript's only ternary (three-operand) operator, and the reason to reach for it over if/else is that it is an EXPRESSION - it produces a value. const max = a > b ? a : b cannot be written as if/else in one expression position (template literal, default parameter, return). Bottom line: ternary for values, if/else for side effects - when both branches assign the same variable, ternary reads better; when they trigger different actions, if/else reads better. Related tools: equality table for the conditions, optional chaining table for the ?? alternative, and closures table for ternaries inside callbacks.

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

  1. Use the ternary where an expression is required: assignments, return statements, template literals, default parameters, JSX.
  2. Chain with indentation: each : starts a new line at the same indent as its ? - the else-if ladder reads top to bottom.
  3. 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.

Related tools