Lists and Cons Cells
In Lisp, the list is not a primitive built-in container the way arrays are in most languages — it is constructed entirely out of a single two-slot structure called a cons cell. Every list you write, from a short (1 2 3) to a deeply nested tree of symbols, is really a chain of these cells linked together, and understanding that chain is the key to understanding how Lisp represents and manipulates data.
Cricket analogy: Think of a ball-by-ball scorecard for a Virat Kohli innings: each delivery is recorded and linked to the next delivery's entry, so the whole innings is really just a chain of individual ball records strung end to end.
The Cons Cell: Lisp's Fundamental Building Block
A cons cell is created with the function (cons a b), producing a pair with two pointers: the car (the first slot, historically 'Contents of the Address Register') and the cdr (the second slot, 'Contents of the Decrement Register'). Given a cons cell c, (car c) returns whatever was passed as the first argument to cons, and (cdr c) returns the second. There is nothing list-specific about a cons cell in isolation — it is simply a pair, and lists are just one particular way of chaining pairs together.
Cricket analogy: A single ball-by-ball entry pairs two things: the outcome of that delivery (a boundary, a dot ball) in one slot, and a link to the next ball's entry in the other — exactly like car holding the outcome and cdr holding what comes next.
Building Lists from Cons Cells
A proper list is a chain of cons cells where the car of each cell holds an element and the cdr points to the next cons cell — or to the empty list, nil, which marks the end of the chain. So (list 1 2 3) is shorthand for (cons 1 (cons 2 (cons 3 nil))). Printing a list, walking it with car/cdr, or recursing over it are all really just traversing this chain one cell at a time until you hit nil.
Cricket analogy: Jasprit Bumrah's over of six deliveries is a chain of six ball-records, each pointing to the next, and the umpire calling 'over' at the sixth ball is exactly like hitting nil — it marks where the chain stops.
(setf my-list (cons 1 (cons 2 (cons 3 nil))))
;; => (1 2 3)
(car my-list) ; => 1
(cdr my-list) ; => (2 3)
(car (cdr my-list)) ; => 2, also written (cadr my-list)
;; (list 1 2 3) is shorthand for the nested cons form above
(equal (list 1 2 3) (cons 1 (cons 2 (cons 3 nil)))) ; => TDotted Pairs vs Proper Lists
If the cdr of the final cons cell is anything other than nil, the result is called a dotted pair (or an improper list), and it prints with an explicit dot, like (1 . 2) or (1 2 . 3). A dotted pair (a . b) is the purest form of cons — just two values, no list structure implied. Improper lists are legitimate and occasionally useful, for instance as lightweight key-value pairs in an association list entry, but functions that expect a proper, nil-terminated list will error or loop forever if handed one.
Cricket analogy: A rain-interrupted innings governed by Duckworth-Lewis breaks the normal over-by-over pattern, producing an irregular target score that doesn't fit the usual chain of overs — much like a dotted pair breaking the normal nil-terminated list.
Never pass an improper (dotted) list to a function like length, mapcar, or reverse that assumes a proper, nil-terminated list. Because the traversal keeps calling cdr looking for nil, it will either signal a type error on hitting a non-cons cdr, or — for a genuinely circular list — loop forever. If you need list-like key/value pairing without full list overhead, an association list of proper dotted pairs, e.g. ((:a . 1) (:b . 2)), is the idiomatic and safe use case.
Quoting and List Literals
Because a list like (+ 1 2) is normally evaluated as a function call, you need quote to tell the reader 'treat this as literal data, not code to execute': '(1 2 3) is shorthand for (quote (1 2 3)), and it returns the list itself unevaluated. This is distinct from calling the function list, which evaluates each argument first — (list (+ 1 2) 3) produces (3 3), while '((+ 1 2) 3) produces the literal list ((+ 1 2) 3) with the addition left un-evaluated inside it.
Cricket analogy: A commentator reading out '250 for 4 at close of play' verbatim from the printed scorecard, rather than recalculating the score live from ball-by-ball data, is exactly what quote does — it takes the data as-is instead of evaluating it.
'(1 2 3) and (quote (1 2 3)) are identical — the single quote is just reader syntax (a macro character) that expands to a call to the quote special form. quote never evaluates its argument, so '(+ 1 2) returns the three-element list (+ 1 2), not the number 3.
- Every Lisp list is a chain of cons cells, each holding a car (element) and a cdr (pointer to the rest).
- (cons a b) builds a pair; (car p) and (cdr p) extract its two slots.
- A proper list is nil-terminated: (list 1 2 3) equals (cons 1 (cons 2 (cons 3 nil))).
- A dotted pair like (1 . 2) has a non-nil, non-cons final cdr and prints with an explicit dot.
- Passing an improper or circular list to functions expecting a proper list can error or loop forever.
- quote (or the ' reader macro) returns its argument unevaluated, distinguishing list literals from function calls.
- The function list evaluates its arguments before assembling them, unlike quote which never evaluates anything.
Practice what you learned
1. What does (cdr '(1 2 3)) return?
2. Which expression is exactly equivalent to (list 1 2 3)?
3. What is (1 . 2) called in Lisp terminology?
4. What does '(+ 1 2) evaluate to?
5. Why can passing a circular list to length cause an infinite loop?
Was this page helpful?
You May Also Like
List Manipulation Functions
The core built-in functions for navigating, searching, combining, and transforming lists, and the crucial distinction between destructive and non-destructive operations.
mapcar and Functional Iteration
How mapcar and its relatives let you transform lists by applying a function across them, without hand-writing explicit loops.
Vectors and Hash Tables in LISP
When to reach for vectors and hash tables instead of lists — constant-time indexed and keyed access versus linked-chain traversal.
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