programming
conditional-operator
type-conversion
ternary-operator
if-else

Why does ? cause a conversion error while if-else does not?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

The ternary operator ? : in C# (and similar languages) requires both branches to have a common type that the compiler can determine at compile time. If the true and false branches return different types with no implicit conversion between them, the compiler raises a type error. The if-else statement does not have this restriction because each branch is an independent statement — it does not need to produce a single result type. This difference in type resolution rules is the root cause of the conversion error.

The Problem in C#

csharp
1int x = 10;
2double y = 20.5;
3
4// IF-ELSE: works fine — each branch assigns independently
5double result;
6if (true)
7    result = x;    // int → double (implicit widening)
8else
9    result = y;    // already double
10
11// TERNARY: may cause issues depending on types
12double result2 = true ? x : y;  // OK here: int and double → common type is double
13
14// But with incompatible types:
15object a = true ? "hello" : 42;
16// Error in some contexts: no implicit conversion between 'string' and 'int'

Why the Ternary Operator Is Strict

csharp
1// The ternary operator is an EXPRESSION — it must have a single result type
2// The compiler determines this type from the two branches:
3// 1. If both branches are the same type → use that type
4// 2. If one can be implicitly converted to the other → use the wider type
5// 3. If neither converts to the other → compile error
6
7// Case 1: Same type — works
8var r1 = true ? "hello" : "world";  // string
9
10// Case 2: Implicit widening — works
11var r2 = true ? 42 : 3.14;  // double (int converts to double)
12
13// Case 3: No implicit conversion — fails
14// var r3 = true ? "hello" : 42;  // Error: cannot determine type

The compiler does not consider the type of the variable being assigned to. It only looks at the two branches of the ternary expression to find a common type.

The Same Issue in Java

java
1// Java ternary: both branches must have a compatible type
2Object result = true ? "hello" : 42;  // OK: both autobox to Object
3
4// But with primitives and references:
5int x = 10;
6String s = "hello";
7// var result = true ? x : s;
8// Error: incompatible types: int and String
9
10// If-else: works because assignments are separate statements
11Object result2;
12if (true)
13    result2 = x;    // autoboxed to Integer, then widened to Object
14else
15    result2 = s;     // String is already Object

In Java, the ternary operator uses binary numeric promotion rules. If neither operand is convertible to the other, it is a compile error.

Nullable Types in C#

csharp
1// Common scenario: nullable int vs int
2int? nullableInt = 10;
3int regularInt = 20;
4
5// IF-ELSE: works
6int? result1;
7if (true)
8    result1 = regularInt;  // int → int? (implicit)
9else
10    result1 = nullableInt;
11
12// TERNARY: the compiler looks at int and int?
13int? result2 = true ? regularInt : nullableInt;  // OK: int → int? implicitly
14
15// But if you flip and assign to non-nullable:
16// int result3 = true ? regularInt : nullableInt;
17// Error: cannot implicitly convert 'int?' to 'int'
18// The ternary returns int? (common type), which doesn't fit into int

The ternary operator determines its result type as the common type of both branches. If one branch is int?, the result type is int?, even if the condition guarantees the non-null branch is selected.

TypeScript Ternary Behavior

typescript
1// TypeScript infers a union type for the ternary
2const result = true ? "hello" : 42;
3// Type: string | number
4
5// Assigning to a specific type may fail:
6const str: string = true ? "hello" : 42;
7// Error: Type 'string | number' is not assignable to type 'string'
8
9// If-else with assignment:
10let str2: string;
11if (true) {
12    str2 = "hello";  // OK: string assigned to string
13} else {
14    // str2 = 42;     // Error: number not assignable to string
15}

TypeScript infers the ternary result as a union type. The compiler does not narrow based on the condition — it considers both branches possible.

Kotlin's when Expression

kotlin
1// Kotlin's when is an expression with similar rules
2val result = when {
3    true -> "hello"
4    else -> 42
5}
6// Type: Any (common supertype of String and Int)
7
8// Assigning to String fails:
9// val str: String = when { true -> "hello"; else -> 42 }
10// Error: Type mismatch: inferred type is Any but String was expected
11
12// If-else as expression in Kotlin:
13val r = if (true) "hello" else 42
14// Type: Any — same rule applies because if-else is an expression in Kotlin

In Kotlin, if-else is also an expression, so it follows the same type resolution rules as the ternary operator in other languages.

How to Fix the Error

csharp
1// Fix 1: Explicit cast to a common type
2object result = true ? (object)"hello" : (object)42;
3
4// Fix 2: Use if-else when types are incompatible
5object result2;
6if (someCondition)
7    result2 = "hello";
8else
9    result2 = 42;
10
11// Fix 3: Convert one branch to match the other
12string result3 = true ? "hello" : 42.ToString();
13// Both branches are string → no error
14
15// Fix 4: Use a specific return type with explicit conversion
16int? result4 = true ? (int?)regularInt : nullableInt;

Common Pitfalls

  • Assuming the ternary uses the target variable's type for resolution: The compiler determines the ternary's result type from its two branches only, ignoring the variable being assigned to. double x = true ? 1 : "hello" fails because int and string have no common type, regardless of x being double.
  • Confusing implicit widening with type compatibility: int to double works because there is an implicit conversion. int to string does not because there is no implicit conversion — you must call .ToString() explicitly.
  • Nullable confusion in C#: true ? nonNullable : nullable returns the nullable type. Assigning this to a non-nullable variable fails. The compiler cannot use the condition to determine which branch executes at compile time.
  • Forgetting that Kotlin's if-else is an expression: Unlike C# or Java, Kotlin's if-else follows expression typing rules (same as ternary). Both branches must have a compatible type when used as an expression.
  • Using ternary for side effects instead of value production: condition ? doA() : doB() is valid but misleading when the return values are discarded. Use if-else for side-effect-only branches for clarity.

Summary

  • The ternary operator (? :) is an expression that must produce a single result type from both branches
  • The compiler determines the result type from the two branches, not from the assignment target
  • if-else statements allow different types in each branch because they are independent assignments
  • Fix type errors by casting both branches to a common type, using .ToString(), or switching to if-else
  • In Kotlin, if-else is also an expression and follows the same type resolution rules
  • Nullable types in the ternary make the result nullable, even if the condition guarantees non-null

Course illustration
Course illustration

All Rights Reserved.