If the "cons at the front, finish by reversing" idiom isn't suitable for you (if you. for example, need to pass the list on to other functions DURING its construction), there's also the "keep track of the end" trick. However, it's probably cleaner to just build the list by consing to the front of it, then finish by using reverse or nreverse before finally using it.
In essence, this allows you to have the list in the right order while building it, at the expense of needing to keep track of it.
(defun track-tail (count)
(let* ((list (cons 0 nil))
(tail list))
(loop for n from 1 below count
do (progn
(setf (cdr tail) (cons n nil))
(setf tail (cdr tail))
(format t "With n == ~d, the list is ~a~%" n list)))
list))
This gives the following output:
CL-USER> (track-tail 5)
With n == 1, the list is (0 1)
With n == 2, the list is (0 1 2)
With n == 3, the list is (0 1 2 3)
With n == 4, the list is (0 1 2 3 4)
(0 1 2 3 4)