Emacs Lisp Basics

Must-know Emacs Lisp functions to practice and internalize. These are the building blocks for writing your own commands, customizing Emacs, and reading other people’s configs.

Variables & Scope Link to heading

setq Link to heading

Set the value of a variable.

(setq my-name "Jonathan")
(setq x 1 y 2 z 3) ;; set multiple at once
(message "x: %d" x) ;; format %d for number
(message "y: %s" y) ;; format %s for any character
(message "%d" z)

let / let* Link to heading

Bind local variables. let* allows later bindings to reference earlier ones.

;; all bindings are evaluated in parallel
;; x and y do not know about each other
(let ((x 10)
      (y 20))
  (+ x y)) ;; => 30

;; bindings are evaluated sequentially
(let* ((x 10)
       (y (* x 2)))  ;; without let*, this would be an error
  y) ;; => 20

defvar / defcustom Link to heading

Define a variable. defvar won’t overwrite an existing value and is intended for internal state. defcustom creates a user-customizable variable – something they can configure later.

(defvar my-default-directory "~/projects"
  "Default directory for projects.")

(defcustom my-line-spacing 0.1
  "Line spacing for my setup."
  :type 'float
  :group 'my-config)

Functions Link to heading

defun Link to heading

Define a named function.

(defun greet (name)
  "Greet someone by NAME."
  (message "Hello, %s!" name))

(greet "world") ;; => "Hello, world!"

(defun poke (thing)
  "Poke a THING."
  (message "Poke, %s!" thing))

(poke "bear") ;; => "Poke, bear!"

lambda Link to heading

Create an anonymous function.

(mapcar (lambda (x) (* x x)) '(1 2 3 4)) ;; => (1 4 9 16)

interactive Link to heading

Make a function callable via M-x.

(defun insert-current-date ()
  "Insert today's date at point."
  (interactive)
  (insert (format-time-string "%Y-%m-%d")))

funcall / apply Link to heading

Call a function stored in a variable. apply spreads a list as arguments.

(funcall #'+ 1 2 3) ;; => 6
(apply #'+ '(1 2 3)) ;; => 6

Lists & Cons Cells Link to heading

car / cdr / cons Link to heading

car returns the first element, cdr returns the rest, cons constructs a new cons cell.

(car '(a b c))   ;; => a
(cdr '(a b c))   ;; => (b c)
(cons 'a '(b c)) ;; => (a b c)

list / append Link to heading

list creates a new list. append concatenates lists.

(list 1 2 3)           ;; => (1 2 3)
(append '(1 2) '(3 4)) ;; => (1 2 3 4)

nth / length Link to heading

nth returns the element at index n (0-based). length returns the list length.

(nth 0 '(a b c))    ;; => a
(nth 2 '(a b c))    ;; => c
(length '(a b c))   ;; => 3

push / pop Link to heading

push adds to the front of a list (mutates). pop removes and returns the first element.

(setq my-list '(b c))
(push 'a my-list) ;; my-list is now (a b c)
(pop my-list)     ;; => a, my-list is now (b c)

assoc / alist-get Link to heading

Look up a key in an association list.

(setq my-alist '((name . "Jonathan") (editor . "Emacs")))
(assoc 'name my-alist)      ;; => (name . "Jonathan")
(alist-get 'name my-alist)  ;; => "Jonathan"

Predicates Link to heading

Type checking Link to heading

(numberp 42)      ;; => t
(stringp "hello") ;; => t
(listp '(1 2))    ;; => t
(symbolp 'foo)    ;; => t
(functionp #'+)   ;; => t
(null nil)        ;; => t
(null '())        ;; => t

Equality Link to heading

(eq 'foo 'foo)          ;; => t  (same object / symbol)
(equal '(1 2) '(1 2))   ;; => t  (structural equality)
(string= "abc" "abc")   ;; => t  (string comparison)
(= 1 1.0)               ;; => t  (numeric equality)

Control Flow Link to heading

if / when / unless Link to heading

(if (> 3 2)
    "yes"    ;; then
  "no")      ;; else

(when (> 3 2)
  (message "this runs") ;; implicit progn, no else branch
  "yes")

(unless (> 2 3)
  (message "2 is not greater than 3")
  "correct")

cond Link to heading

Multi-branch conditional.

(defun describe-number (n)
  (cond
   ((< n 0) "negative")
   ((= n 0) "zero")
   ((< n 10) "small")
   (t "big")))

(describe-number 3)

progn Link to heading

Evaluate multiple forms, return the last one.

(progn
  (setq x 1)
  (setq y 2)
  (+ x y)) ;; => 3

Iteration Link to heading

dolist / dotimes Link to heading

(dolist (item '("a" "b" "c"))
  (message "Item: %s" item))

(dotimes (i 5)
  (message "Count: %d" i)) ;; 0 through 4

(dotimes 2)

mapcar / mapc Link to heading

mapcar applies a function to each element and collects results. mapc does the same but for side effects (returns the original list).

(mapcar #'upcase '("hello" "world")) ;; => ("HELLO" "WORLD")
(mapcar #'1+ '(1 2 3))               ;; => (2 3 4)

(mapc #'upcase '("Hello" "World"))

seq-filter / seq-reduce Link to heading

(seq-filter #'cl-evenp '(1 2 3 4 5 6)) ;; => (2 4 6)
(seq-reduce #'+ '(1 2 3 4) 0)          ;; => 10

Strings Link to heading

concat / format Link to heading

(concat "Hello" ", " "world!") ;; => "Hello, world!"
(format "Name: %s, Age: %d" "Jonathan" 30) ;; => "Name: Jonathan, Age: 30"

substring / split-string / string-join Link to heading

(substring "Hello, world!" 0 5)  ;; => "Hello"
(split-string "a:b:c" ":")       ;; => ("a" "b" "c")
(string-join '("a" "b" "c") "-") ;; => "a-b-c"

string-match / replace-regexp-in-string Link to heading

(string-match "o+" "foobar") ;; => 1 (index of match)
(replace-regexp-in-string "[0-9]+" "N" "foo123bar456") ;; => "fooNbarN"

string-prefix-p / string-suffix-p / string-trim Link to heading

(string-prefix-p "foo" "foobar")  ;; => t
(string-suffix-p ".el" "init.el") ;; => t
(string-trim "  hello  ")         ;; => "hello"

Buffer Operations Link to heading

point / goto-char Link to heading

point returns cursor position. goto-char moves to a position.

(point)                 ;; => current cursor position (integer)
(point-min)             ;; => 1 (beginning of buffer)
(point-max)             ;; => end of buffer
(goto-char (point-min)) ;; move to beginning

insert / delete-region / buffer-substring Link to heading

(insert "some text")
(delete-region (point-min) (point-max)) ;; delete everything
(buffer-substring (point-min) (point-max)) ;; get buffer contents as string

save-excursion Link to heading

Run body, then restore point and current buffer.

(save-excursion
  (goto-char (point-min))
  (insert "Header\n")) ;; point returns to where it was

with-current-buffer Link to heading

Execute body in the context of another buffer.

(with-current-buffer "*Messages*"
  (buffer-string)) ;; returns content of *Messages* buffer

Hooks & Advice Link to heading

add-hook / remove-hook Link to heading

Attach functions to run at specific events.

(add-hook 'emacs-lisp-mode-hook #'eldoc-mode)
(add-hook 'before-save-hook #'delete-trailing-whitespace)
(remove-hook 'before-save-hook #'delete-trailing-whitespace)

advice-add / advice-remove Link to heading

Modify existing function behavior without changing the original.

(defun my-before-save-message (&rest _)
  (message "About to save!"))

(advice-add 'save-buffer :before #'my-before-save-message)
(advice-remove 'save-buffer #'my-before-save-message)

Common Patterns Link to heading

Reading user input Link to heading

(read-string "Enter your name: ")
(completing-read "Choose: " '("option-a" "option-b" "option-c"))
(y-or-n-p "Continue? ")

message Link to heading

Print to the *Messages* buffer (the elisp equivalent of print / console.log).

(message "Hello!")
(message "Value is: %s" some-var)
(message "Number: %d, String: %s, S-expr: %S" 42 "foo" '(1 2 3))