Making decisions
Think of a sorting machine at a post office. A parcel rolls in; if it’s heavier than 20 kg it goes left, otherwise if it’s international it goes right, otherwise straight on. Each parcel takes exactly one path, and the machine checks the questions in order, stopping at the first “yes”.
if, else if and else work exactly like that.
The interesting parts of conditionals are expression vs statement forms (Swift 5.9 and Kotlin have if/switch/when expressions; Java has switch expressions and the ternary; TypeScript only the ternary), definite initialization (a let/val/final assigned in every branch), and structure: guard clauses that exit early make preconditions explicit and keep the happy path at one indentation level.
func shippingLabel(weightKg: Double, international: Bool) -> String {
if weightKg > 20 {
return "Freight"
} else if international {
return "International"
} else {
return "Standard"
}
}fun shippingLabel(weightKg: Double, international: Boolean): String {
if (weightKg > 20) {
return "Freight"
} else if (international) {
return "International"
} else {
return "Standard"
}
}String shippingLabel(double weightKg, boolean international) {
if (weightKg > 20) {
return "Freight";
} else if (international) {
return "International";
} else {
return "Standard";
}
}function shippingLabel(weightKg: number, international: boolean): string {
if (weightKg > 20) {
return "Freight";
} else if (international) {
return "International";
} else {
return "Standard";
}
}The first true condition wins
Tap the line with the bug.
func grade(_ score: Int) -> String {
if score >= 50 {
return "Pass"
} else if score >= 90 {
return "A"
} else {
return "Fail"
}
}fun grade(score: Int): String {
if (score >= 50) {
return "Pass"
} else if (score >= 90) {
return "A"
} else {
return "Fail"
}
}String grade(int score) {
if (score >= 50) {
return "Pass";
} else if (score >= 90) {
return "A";
} else {
return "Fail";
}
}function grade(score: number): string {
if (score >= 50) {
return "Pass";
} else if (score >= 90) {
return "A";
} else {
return "Fail";
}
}Hint: Try a score of 95. Which condition is checked first, and is it true?
A score of 95 is also >= 50, so the first branch catches it and returns “Pass”. The >= 90 branch can never run: every score that reaches it is already below 50. In a chain, conditions are checked top to bottom and the first true one wins, so the most specific condition goes first:
if score >= 90 { return "A" }
if score >= 50 { return "Pass" }
return "Fail"if (score >= 90) return "A"
if (score >= 50) return "Pass"
return "Fail"if (score >= 90) return "A";
if (score >= 50) return "Pass";
return "Fail";if (score >= 90) return "A";
if (score >= 50) return "Pass";
return "Fail";Exit early
When a function needs several things to be true before it can do its job, check each one and leave as soon as one fails. The main work then stays at the left edge, instead of inside three nested ifs:
func checkout(cart: Cart?, user: User?) -> String {
guard let user else { return "Please sign in" }
guard let cart, !cart.items.isEmpty else { return "Your cart is empty" }
guard user.hasPaymentMethod else { return "Add a card" }
return "Charging \(cart.total) to \(user.name)"
}fun checkout(cart: Cart?, user: User?): String {
if (user == null) return "Please sign in"
if (cart == null || cart.items.isEmpty()) return "Your cart is empty"
if (!user.hasPaymentMethod) return "Add a card"
return "Charging ${cart.total} to ${user.name}"
}String checkout(Cart cart, User user) {
if (user == null) return "Please sign in";
if (cart == null || cart.items().isEmpty()) return "Your cart is empty";
if (!user.hasPaymentMethod()) return "Add a card";
return "Charging " + cart.total() + " to " + user.name();
}function checkout(cart: Cart | undefined, user: User | undefined): string {
if (!user) return "Please sign in";
if (!cart || cart.items.length === 0) return "Your cart is empty";
if (!user.hasPaymentMethod) return "Add a card";
return `Charging ${cart.total} to ${user.name}`;
}Swift has a keyword for this, guard … else { return }: the compiler checks that the else really leaves the function. After a guard let, the unwrapped value is available for the rest of the function. In Kotlin, after if (user == null) return, the compiler knows user isn’t null below it (a smart cast).
Practice: flatten a pyramid
- Start with nested checks:
if user != nil { if cart != nil { if !cart.items.isEmpty { …charge… } } }. The real work is three levels deep, and eachelseis far from itsif. - Invert the first condition: “if there’s no user, return”. The rest of the function can now assume a user exists.
- Repeat for every condition, in the same order, each with its own clear message.
- What’s left at the bottom is the happy path, unindented, readable top to bottom.
if as a value
In Swift and Kotlin, if can produce a value directly, so a constant can be set from a decision:
let label = if count == 1 { "1 item" } else { "\(count) items" } // Swift 5.9+val label = if (count == 1) "1 item" else "$count items"String label = count == 1 ? "1 item" : count + " items"; // the ternary operatorconst label = count === 1 ? "1 item" : `${count} items`; // the ternary operatorConditions in production
- Short-circuit order is a guard:
user != null && user.isAdminis safe, the reverse isn’t. Expensive checks (a network call, a database query) go after cheap ones. - Truthiness in TypeScript:
if (!count)is true for0as well asundefined.if (!name)treats""as missing. Use=== undefinedor== nullwhen 0 and "" are valid values. - Boolean parameters hide decisions:
render(true, false)says nothing at the call site. Named arguments (Swift labels, Kotlin named args) or a small enum make the branch explicit. - Definite assignment: declare
let/val/finalbefore anifand assign it in every branch; the compiler proves it’s set exactly once, which beats avarwith a placeholder value.
- In an else-if chain the first true condition wins: most specific first, or use non-overlapping conditions.
- Guard clauses / early returns for preconditions; Swift
guardenforces the exit, Kotlin smart-casts afterif (x == null) return. ifis an expression in Swift (5.9+) and Kotlin; Java and TypeScript use the ternary.- TypeScript truthiness treats
0,""andNaNas false; compare withundefined/nullexplicitly.
Check this part
In an if / else if chain, which branch runs when several conditions are true?
What is an early return (guard clause)?
Why prefer
guard let user else { return }over wrapping the body inif let user { … }?Afterif (user == null) return, what does Kotlin know aboutuserbelow?What do guard clauses mainly buy you in Java code?In TypeScript, why canif (!count)be a bug?Why put
user != nullbeforeuser.isAdminin a condition?