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))