The if Special Form
The most basic conditional in LISP is if, which takes exactly three parts: a test, a then-branch, and an optional else-branch — (if test then else). Only one branch is ever evaluated depending on whether test is non-nil, and in Common LISP anything other than nil counts as true, including numbers, strings, and symbols like t. Unlike in many languages, if is an expression that returns a value, so (if (> x 0) "positive" "non-positive") can be used directly wherever a value is expected, including as an argument to another function.
Cricket analogy: The if form is like the third umpire's DRS review: exactly one test (was the batter out?) determines exactly one outcome — either the decision stands or it's overturned — and the result (out or not out) is then used directly as the value that updates the scoreboard, not just an announcement.
(defun classify (x)
(if (> x 0)
"positive"
(if (< x 0) "negative" "zero")))
(classify 5) ; => "positive"
(classify -3) ; => "negative"
(classify 0) ; => "zero"cond for Multi-Way Branching
Nesting if forms to handle more than two cases quickly becomes unreadable, which is why LISP provides cond: a series of (test result...) clauses evaluated in order, where the first clause whose test is non-nil has its result forms evaluated and returned, and remaining clauses are skipped entirely. A final clause using t as the test acts as a catch-all default, since t always evaluates to true. This makes cond the idiomatic replacement for chained if-else-if structures found in other languages.
Cricket analogy: cond is like an umpire working through the LBW checklist in order — pitching, impact, wicket zone — stopping at the first condition that fails and giving that verdict, with a final catch-all rule ('out' if all checks pass) mirroring the t clause.
cond clauses can omit the result form entirely, e.g. (test), in which case the value of the test itself is returned if it is non-nil — useful as a shorthand when the test's truthy value is also the value you want.
when, unless, and case
when and unless are convenience macros for single-branch conditionals with an implicit body of multiple forms: (when test body...) executes and returns the body only if test is non-nil, while (unless test body...) does the opposite, executing only if test is nil. Neither has an else-branch — they are meant for one-sided side-effecting logic, unlike if. For discrete-value dispatch, case compares a key against a set of literal values using eql, similar to a switch statement: (case x (1 "one") (2 "two") (otherwise "many")), where otherwise (or t) serves as the default clause.
Cricket analogy: when is like a fielding restriction rule that only kicks in during powerplay overs — extra actions (only two fielders outside the circle) apply solely when that condition holds, with no alternate rule needed otherwise, while case resembles reading out a specific over number to trigger a specific bowling change from a pre-set list.
case uses eql for comparison by default, which works fine for numbers, characters, and symbols, but will not correctly match strings (since two equal strings need not be eql) — use cond with string= instead when dispatching on string values.
- if takes a test, a then-branch, and an optional else-branch, and is an expression that returns a value.
- Any non-nil value is treated as true in Common LISP; only nil is false.
- cond evaluates clauses in order and executes the first one whose test is non-nil; use t as a catch-all final clause.
- when executes its body only if the test is true; unless executes its body only if the test is false — neither has an else-branch.
- case dispatches on a key value compared with eql against literal clause values, similar to a switch statement.
- case does not reliably match strings because eql does not guarantee string equality — use cond with string= for string dispatch.
- A cond clause with only a test and no result form returns the test's own value if it is non-nil.
Practice what you learned
1. What value does (if nil "yes" "no") evaluate to?
2. In a cond form, what is the purpose of a final clause like (t "default")?
3. What is the key difference between when and if?
4. Why might (case some-string ("a" 1) ("b" 2)) fail to work as expected?
5. Which values are considered 'false' in Common LISP conditional tests?
Was this page helpful?
You May Also Like
Recursion in LISP
Understand how recursion works in LISP, including base cases, recursive list processing, and tail-call optimization.
Defining Functions in LISP
Learn how to define named functions in LISP using defun, understand parameter lists, return values, and documentation strings.
let and Local Bindings
Learn how let and let* create local variable bindings in LISP, and how their scoping rules differ from global assignment.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics