Programming Language Guide

(Lisp)

The language that thinks in parentheses. A gentle, hands-on introduction to one of the oldest and most powerful programming languages ever created.

Difficulty Beginner Year 1958 Paradigm Multi-paradigm

01 What is Lisp?

Lisp (short for List Processing) is one of the oldest high-level programming languages still in widespread use today. Invented in 1958 by John McCarthy at MIT, it pioneered ideas that are now standard in modern languages — garbage collection, dynamic typing, first-class functions, and the read-eval-print loop (REPL).

At its core, Lisp is built around a single, elegant idea: code and data share the same structure. Programs are written as nested lists, and those lists can themselves be treated as data, manipulated, and generated at runtime. This property — called homoiconicity — makes Lisp uniquely powerful for metaprogramming and building domain-specific languages.

Homoiconic

Code is data. Programs are just lists you can inspect and modify at runtime.

λ

Functional

First-class functions, closures, and higher-order programming baked in from day one.

Macros

Extend the language itself. Write code that writes code — true metaprogramming.

Dynamic

Interactive development via the REPL — evaluate expressions and see results instantly.

💡
Learning Lisp will change how you think about programming, even if you never write it professionally. Its influence lives on in Python, JavaScript, Haskell, Clojure, and Rust.

02 A Brief History

John McCarthy conceived Lisp in 1958 while working on artificial intelligence research at MIT. He needed a language powerful enough to reason about symbolic expressions — things like math formulas, game trees, and logical propositions. The result was a language of startling simplicity built on just a handful of core primitives.

In 1960, McCarthy published "Recursive Functions of Symbolic Expressions and Their Computation by Machine", one of the most influential papers in computer science history. It defined the theoretical foundations of Lisp using lambda calculus.

YearMilestone
1958John McCarthy designs Lisp at MIT
1960Landmark paper establishes Lisp's mathematical foundations
1962First Lisp compiler written (by Tim Hart and Mike Levin)
1975Scheme published — a minimal, elegant Lisp dialect
1984Common Lisp standardization effort begins
1994ANSI standard for Common Lisp finalized
2007Clojure introduced — Lisp on the JVM
PresentLisp dialects thrive in AI, finance, and scripting

03 Dialects

Lisp is not a single language — it's a family. Each dialect shares the fundamental parenthesized syntax and list-centric worldview, but they diverge in their standard libraries, type systems, and target platforms.

Common Lisp

ProductionMulti-paradigmMature

The most feature-rich dialect. ANSI-standardized, with a huge standard library covering everything from CLOS (object-oriented programming) to networking. Best for large applications. Recommended for beginners wanting depth.

Scheme

MinimalEducationalR7RS

A small, clean, rigorously defined dialect. Beloved in academia, used to teach CS fundamentals (MIT's classic textbook SICP uses Scheme). Tail-call optimization is mandatory in the standard.

Clojure

JVM / JSImmutableConcurrent

A modern Lisp that runs on the Java Virtual Machine. Emphasizes immutability and designed for concurrent programming. Interoperates fully with Java libraries. Very popular in the enterprise world.

Emacs Lisp

EmbeddedScriptingGNU Emacs

The dialect powering the GNU Emacs text editor. Writing Emacs Lisp lets you customize and extend one of the most powerful editors in existence. A great practical entry point.

ℹ️
The examples in this guide use Common Lisp syntax, the most widely used dialect for general-purpose programming. Most concepts transfer directly to other dialects.

04 Getting Started

The fastest way to run Common Lisp is to install SBCL (Steel Bank Common Lisp), a free, high-performance implementation.

Install SBCL

Terminal — macOS / Linux / Windows
# macOS (Homebrew)
brew install sbcl

# Ubuntu / Debian
sudo apt install sbcl

# Windows — download the installer from sbcl.org
# or use WSL with the Linux instructions above

# Start the interactive REPL
sbcl

Your First Expression

Once the REPL is running (you'll see a * prompt), type your first Lisp expression:

Common Lisp REPL — sbcl
*(+ 1 2)
3
*(print "Hello, Lisp!")
"Hello, Lisp!"
*(expt 2 10)
1024
💡
For a richer editing experience, install Portacle — an all-in-one Emacs + SBCL environment — or use VS Code with the Alive extension for inline evaluation and debugging.

05 Syntax & S-Expressions

Lisp's syntax is built on a single structure: the S-expression (symbolic expression). An S-expression is either an atom (a number, symbol, or string) or a list enclosed in parentheses.

Every list is interpreted as a function call: the first element is the function (or operator), and the remaining elements are the arguments. This is called prefix notation.

lisp — prefix notation examples
; Form: (function arg1 arg2 ...)

(+ 3 4)          ; → 7  (addition)
(- 10 3)         ; → 7  (subtraction)
(* 6 7)          ; → 42 (multiplication)
(/ 10 2)         ; → 5  (division)

; Nesting: innermost evaluates first
(+ (* 2 3) (- 10 4))   ; → 12  (2×3=6, 10-4=6, 6+6=12)

; Arithmetic on many numbers at once
(+ 1 2 3 4 5)    ; → 15
(max 3 7 2 9)    ; → 9
ℹ️
Anything after a semicolon ; on the same line is a comment and is ignored by the interpreter. Use ;; for section-level comments — it's a common convention.

Quoting — Preventing Evaluation

By default, Lisp evaluates every list as a function call. To treat a list as raw data instead, quote it with the ' shorthand (or the quote special form):

lisp — quoting
(+ 1 2)           ; Evaluates → 3
'(+ 1 2)          ; Quoted   → (+ 1 2)  (the list itself)

'hello           ; A quoted symbol → HELLO
'(cat dog bird)  ; A list of symbols

; Long form — identical result:
(quote (a b c))  ; → (A B C)

06 Data Types

Common Lisp has a rich, dynamic type system. Types are checked at runtime, and variables can hold any type.

TypeExampleNotes
Integer 42, -7, 0 Arbitrary precision — no overflow
Float 3.14, -0.5 IEEE 754 double precision
Ratio 1/3, 22/7 Exact fractions — no rounding
String "Hello" Double-quoted, mutable character arrays
Character #\A, #\space Single characters prefixed with #\
Symbol FOO, my-var Named identifiers, case-insensitive by default
List (1 2 3), (a b c) Singly-linked lists — the heart of Lisp
Boolean T, NIL NIL is false AND the empty list; everything else is true
Vector #(1 2 3) Fixed-size, O(1) random-access arrays
Hash Table make-hash-table Key-value store with O(1) average access
⚠️
In Common Lisp, NIL and the empty list () are the same object. This means an empty list is always falsy — a useful and intentional design choice.

Type Predicates

lisp — checking types
(integerp 42)      ; → T
(stringp  "hi")   ; → T
(listp    '(1 2)) ; → T
(null     nil)    ; → T  (NIL is the empty list)
(numberp  "x")    ; → NIL

; typep for general type checking
(typep 3.14 'float)    ; → T
(type-of "hello")     ; → (SIMPLE-ARRAY CHARACTER (5))

07 Variables

Lisp has two kinds of variables: global (dynamic) variables defined with defvar or defparameter, and local variables bound with let.

lisp — global variables
;; defvar — only sets the value if unbound
(defvar *player-name* "Alice")

;; defparameter — always resets the value
(defparameter *max-lives* 3)
(defparameter *pi-approx* 3.14159)

;; Mutate with setf
(setf *max-lives* 5)   ; *max-lives* is now 5

;; Constants
(defconstant +speed-of-light+ 299792458)
💡
By convention, global dynamic variables are written with earmuffs: *like-this*. Constants use +plus-signs+. These are naming conventions, not syntax rules, but following them makes your code immediately readable to other Lispers.

Local Variables with let

lisp — local bindings
;; let — bindings are evaluated in parallel
(let ((x 10)
      (y 20))
  (+ x y))   ; → 30

;; let* — bindings evaluated sequentially (each can see previous)
(let* ((base  5)
       (area  (* base base)))
  (format t "Area: ~a~%" area))
; Prints: Area: 25

08 Functions

Functions are defined with defun. They are first-class values in Lisp — they can be passed as arguments, returned from other functions, and stored in variables.

lisp — defining and calling functions
;; Basic function definition
(defun greet (name)
  (format t "Hello, ~a!~%" name))

(greet "World")   ; Prints: Hello, World!

;; Returning a value (last expression is the return value)
(defun square (n)
  (* n n))

(square 7)       ; → 49

;; Multiple parameters
(defun hypotenuse (a b)
  (sqrt (+ (square a)
           (square b))))

(hypotenuse 3 4)  ; → 5.0

Optional & Keyword Arguments

lisp — advanced argument lists
;; &optional — has a default value if not supplied
(defun power (base &optional (exp 2))
  (expt base exp))

(power 3)       ; → 9   (3²)
(power 2 10)    ; → 1024 (2¹⁰)

;; &key — named arguments (order doesn't matter)
(defun describe-person (&key name (age 0))
  (format t "~a is ~a years old.~%" name age))

(describe-person :name "Bob" :age 30)
; → Bob is 30 years old.

Anonymous Functions (Lambda)

lisp — lambda expressions
;; Create a function without naming it
(lambda (x) (* x x))

;; Call it immediately
((lambda (x) (* x x)) 5)   ; → 25

;; Store it in a variable with #' (function operator)
(let ((double (lambda (x) (* 2 x))))
  (funcall double 7))     ; → 14

09 Conditionals

Lisp provides several conditional forms. The most fundamental is if, but cond, when, and unless cover common patterns more expressively.

lisp — if, when, unless
;; if: (if test then-expr else-expr)
(if (> 5 3)
    "five is greater"
    "three is greater")
; → "five is greater"

;; when: runs only if test is true; can have multiple body forms
(when (> *max-lives* 0)
  (format t "Player is alive!~%"))

;; unless: runs only if test is NIL (false)
(unless (zerop *max-lives*)
  (decf *max-lives*))    ; decrement by 1

;; cond: like an if/else-if chain
(defun classify-temp (c)
  (cond
    ((< c  0)  "freezing")
    ((< c 15)  "cold")
    ((< c 25)  "comfortable")
    (t         "hot")))   ; t = default (else)

(classify-temp 22)   ; → "comfortable"

Comparison Operators

OperatorMeaningExampleResult
=Numeric equality(= 3 3)T
/=Numeric inequality(/= 3 4)T
eqObject identity(eq 'a 'a)T
eqlNumbers or identity(eql 42 42)T
equalStructural equality(equal '(1 2) '(1 2))T
string=String equality(string= "hi" "hi")T

10 Loops

Common Lisp's loop macro is extraordinarily powerful — almost a mini-language of its own. For simpler cases, dotimes and dolist are more readable.

lisp — loop forms
;; dotimes — repeat N times (i goes 0 to N-1)
(dotimes (i 5)
  (format t "~a " i))
; Prints: 0 1 2 3 4

;; dolist — iterate over a list
(dolist (fruit '(apple banana cherry))
  (format t "I like ~a~%" fruit))

;; loop macro — powerful iteration
(loop for i from 1 to 5
      collect (* i i))
; → (1 4 9 16 25)  (list of squares)

;; loop with filtering
(loop for i from 1 to 20
      when (evenp i)
      collect i)
; → (2 4 6 8 10 12 14 16 18 20)

;; loop with accumulation
(loop for i from 1 to 100 sum i)
; → 5050  (sum of 1 to 100)

11 Recursion

Recursion is the idiomatic way to express repetition in Lisp. A recursive function calls itself with a smaller version of the problem until it reaches a base case.

lisp — classic recursive functions
;; Factorial: n! = n × (n-1)!
(defun factorial (n)
  (if (<= n 1)
      1
      (* n (factorial (- n 1)))))

(factorial 10)  ; → 3628800

;; Fibonacci sequence
(defun fib (n)
  (cond
    ((= n 0) 0)
    ((= n 1) 1)
    (t       (+ (fib (- n 1))
                (fib (- n 2))))))

(fib 10)  ; → 55

;; Recursive list sum
(defun list-sum (lst)
  (if (null lst)
      0
      (+ (car lst)
         (list-sum (cdr lst)))))

(list-sum '(1 2 3 4 5))  ; → 15
💡
Scheme mandates tail-call optimization (TCO). In Common Lisp, you can write tail-recursive functions, but TCO is not guaranteed — use loop for performance-critical iterations, or write explicitly tail-recursive code with a compiler that supports TCO like SBCL.

12 List Operations

Lists are the native data structure of Lisp. They are built from cons cells — each cell holds a value (car) and a pointer to the rest of the list (cdr). Understanding cons cells unlocks all of Lisp.

lisp — fundamental list operations
(defparameter *nums* '(10 20 30 40))

(car  *nums*)          ; → 10       (first element)
(cdr  *nums*)          ; → (20 30 40) (rest of list)
(cadr *nums*)          ; → 20       (second — car of cdr)
(caddr *nums*)         ; → 30       (third)
(last *nums*)          ; → (40)     (last cons cell)

;; Building lists
(cons 5 '(6 7))         ; → (5 6 7)  (prepend)
(list 1 2 3)            ; → (1 2 3)
(append '(1 2) '(3 4)) ; → (1 2 3 4)

;; Inspection
(length *nums*)         ; → 4
(reverse *nums*)        ; → (40 30 20 10)
(member 20 *nums*)      ; → (20 30 40) — tail from match
(nth 2 *nums*)          ; → 30       (0-indexed)
(sort '(3 1 4 1 5) #'<)  ; → (1 1 3 4 5)

13 Higher-Order Functions

Higher-order functions take other functions as arguments or return them. They let you express patterns like mapping, filtering, and folding without writing explicit loops.

lisp — mapcar, remove-if, reduce
;; mapcar — apply a function to every element
(mapcar #'square '(1 2 3 4))
; → (1 4 9 16)

(mapcar (lambda (x) (* x 10)) '(1 2 3))
; → (10 20 30)

;; remove-if — filter out matching elements
(remove-if #'evenp '(1 2 3 4 5 6))
; → (1 3 5)   (removed even numbers)

(remove-if-not #'evenp '(1 2 3 4 5 6))
; → (2 4 6)   (keep only evens)

;; reduce — fold a list into a single value
(reduce #'+ '(1 2 3 4 5))      ; → 15
(reduce #'* '(1 2 3 4 5))      ; → 120
(reduce #'max '(3 9 1 7))     ; → 9

;; Combining them — pipeline style
(reduce #'+
        (mapcar #'square
                (remove-if #'evenp '(1 2 3 4 5))))
; Sum of squares of odd numbers: 1+9+25 = 35

14 Macros

Macros are Lisp's most powerful feature. Unlike functions, macros receive their arguments unevaluated — as raw syntax — and return new code that Lisp then evaluates. This lets you extend the language with new control structures, DSLs, and compile-time transformations.

⚠️
Macros are an advanced topic. Master functions, let, conditionals, and recursion first. Return here once you're comfortable with the basics.
lisp — defining macros
;; A simple macro: swap two variables
(defmacro swap! (a b)
  `(let ((tmp ,a))
     (setf ,a ,b)
     (setf ,b tmp)))

(let ((x 1) (y 2))
  (swap! x y)
  (list x y))  ; → (2 1)

;; A macro: my-when (like built-in when)
(defmacro my-when (test &body body)
  `(if ,test
       (progn ,@body)))

;; Check what code a macro expands to:
(macroexpand-1 '(my-when t (print "hi")))
; → (IF T (PROGN (PRINT "hi")))
💡
The backtick ` is a quasiquote — like a regular quote, but commas , selectively evaluate expressions inside it. ,@ splices a list in. These are the building blocks of macro templates.

15 Practical Examples

Let's put everything together with a few short but realistic programs.

FizzBuzz

lisp — fizzbuzz
(defun fizzbuzz (n)
  (loop for i from 1 to n do
    (format t "~a~%"
      (cond
        ((zerop (mod i 15)) "FizzBuzz")
        ((zerop (mod i  3)) "Fizz")
        ((zerop (mod i  5)) "Buzz")
        (t                 i)))))

(fizzbuzz 15)

Flatten a Nested List

lisp — flatten nested lists recursively
(defun flatten (lst)
  (cond
    ((null lst)   nil)
    ((listp (car lst))
     (append (flatten (car lst))
             (flatten (cdr lst))))
    (t
     (cons (car lst)
           (flatten (cdr lst))))))

(flatten '(1 (2 (3 4)) (5 (6 7 (8)))))
; → (1 2 3 4 5 6 7 8)

Simple Word Counter

lisp — counting word frequency
(defun word-frequencies (words)
  (let ((freq (make-hash-table :test #'equal)))
    (dolist (word words)
      (incf (gethash word freq 0)))
    freq))

(let ((counts (word-frequencies
                 '(the cat sat on the mat the cat))))
  (format t "the: ~a~%" (gethash 'the counts))
  (format t "cat: ~a~%" (gethash 'cat counts)))
; the: 3
; cat: 2
📖
The classic next step: read Structure and Interpretation of Computer Programs (Abelson & Sussman), freely available at mitpress.mit.edu/sicp. It uses Scheme but every concept applies to Common Lisp. One of the most influential CS textbooks ever written.