Control Flow in Common Lisp
Master control flow in Common Lisp - conditionals with if/when/unless/cond/case, iteration with the loop macro, and recursion, all with Docker-ready examples
Control flow is how a program decides what to do next: which branch to take, how many times to repeat work, and when to stop. Most languages express this with statements like if, for, and while. Common Lisp is different in a crucial way — control flow constructs are expressions that return values, and they are built from the same parenthesized S-expressions as everything else in the language.
As a multi-paradigm language, Common Lisp gives you a remarkably rich toolkit. There are several conditional forms (if, when, unless, cond, case), several iteration constructs (dotimes, dolist, do, and the famously powerful loop macro), and — because Lisp grew out of the functional tradition — recursion as a first-class way to express repetition. Rather than one canonical loop, you choose the form that best fits the shape of the problem.
In this tutorial you’ll learn how to branch with the conditional forms, iterate with the looping macros, control loops with early exits and skipping, and express repetition functionally with recursion. Each example is a complete program you can run with SBCL under Docker.
Conditionals
Common Lisp’s conditional forms range from the basic two-way if to the multi-way cond and value-dispatching case. Because these are expressions, if doubles as the language’s “ternary” operator — it simply returns the value of whichever branch runs.
Create a file named conditionals.lisp:
| |
A few things to notice. if accepts exactly one then-form and one optional else-form — when you need multiple statements in a branch, reach for when/unless (which wrap their body in an implicit progn) or cond. In cond, each clause is (test form...) and the first clause whose test is true wins; the final t clause acts as the catch-all “else”. case compares its key against the literal values in each clause and uses otherwise (or t) as the default.
Iteration with Loops
Common Lisp offers several looping constructs. dotimes repeats a fixed number of times, dolist walks the elements of a list, do is the general-purpose loop with explicit stepping, and the loop macro is a miniature language of its own for accumulating and iterating.
Create a file named loops.lisp:
| |
The loop macro is worth dwelling on: loop for n from 1 to 5 sum n reads almost like English and replaces the manual accumulator-and-counter boilerplate found in most languages. The collect clause gathers values into a fresh list. do is more verbose but fully general — each variable spec is (var init step), and the loop stops as soon as the termination test becomes true.
Loop Control: Early Exit and Skipping
Sometimes you need to break out of a loop early or skip an iteration. Common Lisp uses return (and return-from) to exit a loop immediately. There is no dedicated continue keyword — the idiomatic approach is to guard the body with when/unless so unwanted iterations simply do nothing.
Create a file named loop_control.lisp:
| |
The first loop scans 0 through 49 and bails out with (return) the moment it finds 7. The second shows the simplest infinite loop — it has no clauses, so it repeats its body until something returns. The third demonstrates the Lisp way to “skip” an iteration: rather than a continue jump, you wrap the work in unless so even numbers fall through with nothing done.
Recursion: Functional Control Flow
Because Common Lisp descends from the functional tradition, recursion is a natural and idiomatic way to express repetition — especially when walking recursive data like lists. Instead of mutating a counter, a recursive function calls itself with a smaller problem until it reaches a base case.
Create a file named recursion.lisp:
| |
Each function has a base case that stops the recursion and a recursive case that reduces the problem. factorial shrinks n toward 1; sum-list peels off the first element and recurses on the rest until the list is empty. This pattern — base case plus a step toward it — is the recursive equivalent of a loop’s termination test and update step.
Running with Docker
Each example is a standalone script you can run with SBCL inside Docker, with no local installation required.
| |
Expected Output
Running conditionals.lisp:
10 is positive
7 is odd
It's hot! Temperature is 95
Please log in
Grade: B
Wednesday
Running loops.lisp:
Counting with dotimes:
i = 0
i = 1
i = 2
i = 3
i = 4
Iterating with dolist:
apple
banana
cherry
Summing with loop:
Sum 1..5 = 15
Squares: (1 4 9 16 25)
Countdown with do:
3
2
1
Running loop_control.lisp:
Searching for first multiple of 7:
Found: 7
Loop until a condition:
i = 0
i = 1
i = 2
Odd numbers only:
1
3
5
Running recursion.lisp:
5! = 120
Countdown: 5 4 3 2 1
Sum of (1 2 3 4 5) = 15
Key Concepts
- Conditionals are expressions —
if,cond, andcaseall return values, soifdoubles as Common Lisp’s ternary operator. - Pick the right conditional — use
iffor a single two-way choice,when/unlessfor a one-sided branch with multiple body forms,condfor multi-way tests, andcaseto dispatch on a single value. tandotherwiseare the catch-alls — the finaltclause incondandotherwiseincaseact as the “else” branch.- Many loops, one for each job —
dotimesfor fixed counts,dolistfor list elements,dofor general stepping, andloopfor expressive accumulation with clauses likesumandcollect. returnexits a loop early — there is nocontinuekeyword; skip an iteration by guarding the body withwhen/unlessinstead.- Recursion replaces iteration — with a base case and a step toward it, recursive functions express repetition naturally, especially when processing lists with
firstandrest. nilis false, everything else is true — onlynil(the empty list) counts as false in a test, which makes list-walking base cases like(null lst)clean and idiomatic.
Running Today
All examples can be run using Docker:
docker pull clfoundation/sbcl:latest
Comments
Loading comments...
Leave a Comment