Few general points about your code: 1. `(while condition &body)` form already treats `&body` as an implicit `progn`. There's no reason to wrap it in a `progn`. 2. `insert` accepts multiple arguments. So, you could write `(insert (mapconcat ...) "\n")` instead of calling `(insert ...)` followed by `(newline)`. 3. Traditionally, Lisp functions use `-` when the name of the function consists of multiple words, so, it would be better to call `zip-list`, not `zip_list`. 4. `(let (res) ...)` is exactly the same as `(let ((res)) ...)` but shorter. 5. `(setq ...) can handle multiple assignments, they are executed in order they are written, so `(setq a b c a)` will assign `b` to `c`. No reason to write `(setq a b) (setq c a)`. 6. `split-string` has argument `OMIT-NULLS` that controls whether empty strings are included in the results. It would be better to rely on this argument than to post-process the results. 7. As mentioned in the comments, there's an easier, more idiomatic way to obtain all lines of text from the buffer: `(split-string (buffer-string) "\n")`. Finally, unless only for the sake of an exercise in writing the `zip-list` function, the transpose operation calls for something like vector, not a list. Below is a possible alternative which uses vectors to perform this operation: <!-- language: lang-lisp --> (defun wvxvw/transpose-buffer () (interactive) (let* ((lines (split-string (buffer-string) "\n")) (max-length (cl-loop for line in lines maximize (max (length line)))) (src (cl-coerce lines 'vector)) (dst (cl-loop with matrix = (make-vector max-length nil) with len = (length src) for i below max-length do (aset matrix i (make-vector len ?\ )) finally (cl-return matrix)))) (cl-loop for i upfrom 0 for line across src do (cl-loop for j upfrom 0 for c across line do (aset (aref dst j) i c))) (erase-buffer) (cl-loop for line across dst do (insert (cl-coerce line 'string) "\n"))))