100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Lists and Cons Cells

How Lisp builds every list out of two-slot cons cells, and how car, cdr, and quoting let you construct and inspect them.

Lists & Data StructuresBeginner8 min readJul 10, 2026
Analogies

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.

lisp
(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))))  ; => T

Dotted 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

Was this page helpful?

Topics covered

#Programming#LISPStudyNotes#ListsAndConsCells#Lists#Cons#Cells#Cell#DataStructures#StudyNotes#SkillVeris