
You'll hear the origin story of Clojure and meet Rich Hickey, the veteran C++, Java, and C# programmer who spent two and a half self-funded years designing it. You'll understand the problem he set out to solve in 2007 — uncontrolled mutable state in a world of multi-core CPUs and rising complexity — and why that makes Clojure a pragmatic, opinionated language rather than an academic experiment.
You'll type your very first Clojure expression at the REPL and watch it evaluate, printing a short line of text with println. You'll learn the parenthesized prefix-notation call structure, where the function name comes first and the arguments follow, contrast it with the infix notation you may already know, and print your own line to lock in the shape of a Clojure function call.
You'll learn the four common printing functions — println, print, pr, and prn — and exactly when to reach for each, so you stop defaulting to println by reflex. By printing the same string and data through each one, you'll see the difference between human-readable and machine-readable output and discover the read/print symmetry that matters later for serialization.
You'll learn the three ways to comment Clojure code: the semicolon line comment, the #_ form-skipping reader macro, and the comment macro. You'll also see that Clojure treats commas as whitespace, and you'll practice cleaning up a noisy expression with each comment style to build early fluency with the reader macros.
You'll internalize the foundational rule that in Clojure nearly everything is an expression that returns a value — including do blocks and conditionals — unlike the statement-versus-expression split of C-family languages. By predicting the return value of literals, function calls, and nested expressions before running them, you'll start thinking in terms of values produced rather than effects performed.
You'll trace Clojure's place in the 60-year Lisp family tree, from John McCarthy's 1958 original through Maclisp, Scheme, and Common Lisp. You'll see what Clojure inherited — homoiconicity, the REPL, macros, and code-as-data — and where it deliberately broke with tradition on mutability and hosting, and why it became the most widely deployed Lisp in industry.
You'll use def to bind a name to a value at the top level of a namespace, and learn why that's a binding to an immutable value rather than a mutable variable like you'd find in Python or JavaScript. You'll def a number and a string, use a defined name inside an arithmetic expression, and see that re-defing creates a new binding instead of mutating the old one.
You'll use let to name intermediate values inside an expression without polluting the surrounding namespace, and confirm that those local names disappear outside the let. You'll take a deeply nested expression and refactor it with let, giving meaningful names to its parts and making your code safer to change without paying the cost of global state.
You'll tour Clojure's scalar values — integers and longs, doubles, arbitrary-precision BigInts, exact ratios, strings, characters, booleans, and the special value nil. You'll see that ratios and bignums are first-class, so 1/3 stays exact, and you'll mix integers, doubles, and ratios in one expression while predicting the resulting type to build intuition for the numeric tower.
You'll learn keywords and symbols, the two name-like values beginners most often confuse. You'll see that keywords like :name evaluate to themselves and serve as map keys, markers, and lookup functions, while symbols are how the language names its own variables and are usually quoted to be used as data — and you'll use each in its natural role.
You'll learn to inspect any value at the REPL using type and class to get its runtime class and instance? to check membership. You'll see that Clojure values are Java objects with familiar classes like java.lang.Long and java.lang.String, then confirm that ratios and bignums are Clojure-specific classes, reinforcing both the hosted nature of the language and the habit of investigating values directly.
You'll understand what it means that Clojure is a hosted language and why that's central to its identity. You'll weigh the upside — full access to the Java ecosystem, mature garbage collection, JIT compilation, and battle-tested deployment — against the real costs of slower startup, JVM memory overhead, and interop friction, and see how the same philosophy extends to ClojureScript and Clojure CLR.
You'll do arithmetic the Clojure way, calling +, -, *, and / in prefix form where the operator comes first. You'll see that + and * take any number of arguments so (+ 1 2 3 4) is natural, learn how exact ratios and the quot and rem functions handle division, and compute an average using only +, /, and count.
You'll learn the comparison operators and Clojure's deliberately simple truthiness rule. You'll see that = does deep value equality across types and nested collections, far more powerful than Java's reference equality or JavaScript's coercion, and that only false and nil are falsy — so zero and the empty string are truthy. You'll predict the truthiness of mixed values to eliminate a common newcomer bug.
You'll branch with if and when, learning that if takes exactly three arguments — a condition, a then-branch, and an else-branch — and that each branch is a single expression, not a block. You'll use when as the no-else shorthand where a side effect fits, and write a small guard that prints different messages depending on a value.
You'll replace nested if-trees with cond for readable multi-way branching and case for fast literal dispatch. You'll see cond evaluate condition-result pairs in order with a fallback clause, watch case jump on compile-time constants, and classify a value into ordered tiers with cond instead of an if-else cascade.
You'll learn the boolean macros and, or, and not, including the behavior most beginners miss: and returns the last truthy value or the first falsy one, while or returns the first truthy value. You'll use or to supply a fallback when a value might be nil — the Clojure equivalent of JavaScript's ?? — so you can stop writing explicit nil checks everywhere.
You'll learn Rich Hickey's design philosophy from his famous talk Simple Made Easy: the difference between simple (one-fold, uncomplected) and easy (familiar, near at hand), and why Clojure relentlessly chooses simplicity even at the cost of a steeper start. You'll connect this to concrete decisions like immutable data, separating functions from data, and making time explicit through reference types.
You'll meet the vector, written with square brackets, as the most common ordered collection in idiomatic Clojure. You'll look up elements with get and by calling the vector as a function, add to the end with conj, and count it — and see that because vectors are immutable, conj returns a new vector while the original stays untouched. You'll build a vector of items and look up an element by index.
You'll work with lists, written with parentheses, and compare their performance with vectors: lists prepend quickly with conj but index slowly, the opposite tradeoff. You'll also see the deeper point that Clojure source code is itself made of lists — the basis for the homoiconicity that powers macros — and watch conj add to the front of a list, behaving differently than it does on a vector.
You'll learn the hash map, written with curly braces, as the data structure that dominates real Clojure code. You'll look up entries three ways — with get, with the map as a function, and with the keyword as a function — then assoc and dissoc entries, watching each return a new map. You'll model a record as a map and update one field with assoc, the move that replaces setters in mutable languages.
You'll learn sets, written with #{} literal syntax, as the collection for unique-membership questions. You'll add with conj, remove with disj, test membership with contains? and by calling the set as a function, and combine sets with clojure.set/union. Then you'll take a vector with duplicates, convert it to a set, and check whether a specific value is present.
You'll learn the trio of nested-access functions — get-in, assoc-in, and update-in — that make deeply nested data ergonomic. You'll reach into a map containing vectors of maps with a vector of keys, replace a deep value, and transform one by passing a function, all without mutation. You'll update a deeply nested field and feel the superpower that developers from Python often describe.
You'll meet the seq abstraction, calling first, rest, and next on a vector, a list, a map, and a set, and seeing that all four produce sequences that nearly every collection function speaks. This is the unifying idea that makes map, filter, and reduce work uniformly across data types. You'll call first on a map and watch it return a key-value pair, surfacing the elegant uniformity behind the whole collection library.
You'll get a frank audit of Clojure's real weaknesses so you can commit with eyes open: parenthesis-heavy syntax that intimidates newcomers, JVM startup latency that hurts command-line scripts, cryptic stack traces, dynamic typing that pushes bugs to runtime, a small talent pool, and niche tooling. You'll also weigh the cultural tradeoffs of an opinionated community and a language that evolves slowly by design.
You'll define your first named function with defn, walking through its anatomy: name, optional docstring, parameter vector, and a body whose last expression is the return value — there's no return keyword because every expression has a value. You'll write a small numeric function and call it with several inputs, building the habit of writing reusable functions from day one.
You'll learn the two ways to write a function without naming it: the full fn form for clarity and the terse #(...) reader macro with % and %1, %2 for one-liners. You'll pass each directly into map, and learn why the reader macro can't be nested so fn wins when readability matters. You'll write the same squaring operation both ways and pass each into map.
You'll write multi-arity functions, where a single defn provides different implementations for zero, one, and two or more arguments, often by having lower-arity versions call higher-arity ones with defaults. After contrasting this with Python's optional parameters and Java's overloading, you'll build a function that supplies sensible defaults when fewer arguments are passed, all in one clean definition.
You'll combine two power features: variadic arguments collected with the & symbol, and destructuring that pulls map and vector parts directly out of the parameter list. You'll write a function taking a name and any number of extra arguments, then ones that destructure a vector by position and a map by keyword — including the :keys shortcut — inline, the move that makes Clojure signatures self-documenting while staying terse.
You'll learn closures by writing a function that returns another function capturing a value from its enclosing let, then calling the returned function repeatedly to see the captured value persist. You'll connect this to partial application and configurable behavior without classes, and write the classic make-multiplier function that returns a function multiplying by a given factor.
You'll survey who actually runs Clojure in production — Walmart, Nubank, Apple, Netflix, CircleCI, Cisco, and many fintech and healthtech firms — and understand why data-heavy backends and correctness-critical systems gravitate toward it. You'll see figures from the State of Clojure survey and an honest take on market share: niche by raw count, but punching far above its weight in mission-critical systems.
You'll learn doseq, the construct for running a side effect once per item, distinct from map which transforms. You'll doseq across a vector to print each item, then bind from two collections to create a nested loop, and see that doseq returns nil because its purpose is the effect, not a value. You'll print each entry of a map on its own line using destructuring.
You'll use dotimes to repeat something a fixed number of times — printing a counter from zero up to a fixed limit and running a small calculation N times. You'll see that dotimes is the rare Clojure construct resembling a mainstream for-loop, mostly used in benchmarks, demos, and initialization, and you'll print a small two-dimensional table with nested dotimes as a familiar foothold.
You'll learn loop and recur, Clojure's explicit construct for iterative recursion that compiles to a JVM loop and never overflows the stack. You'll write a small sum or running product that loops with an accumulator and recurs with the new value, learn that recur must sit in tail position, and write a countdown loop that prints each number from a start value down to zero.
You'll contrast plain recursion, where a function calls itself by name, with loop/recur by computing the same result both ways. You'll see that plain recursion reads more cleanly but risks stack overflow because Clojure lacks general tail-call optimization, while loop/recur is uglier but safe. You'll write a recursive fibonacci and watch its runtime behavior grow on larger inputs.
You'll learn the for comprehension, which looks like a loop but is really an expression returning a lazy sequence, much like Python's list comprehensions. You'll generate all pairs from two collections, filter with :when, and name intermediate values with :let, then build all coordinate pairs of a 3x3 grid with for — and see how rarely you need explicit loops in Clojure.
You'll understand how Clojure's vectors and maps can be both immutable and fast through structural sharing in hash array mapped tries. You'll build the mental model: assoc into a large map shares most of its internal tree with the original instead of copying, so updates are near-constant time. You'll learn the academic origin in Phil Bagwell's HAMT paper and why this makes immutability viable at scale.
You'll learn the three workhorse higher-order functions by taking a vector of numbers, mapping a squaring function across it, filtering for evens, and reducing to a sum. You'll feel the declarative shift from explicit loops to describing a transformation as a pipeline, learn that map and filter are lazy while reduce is eager, and combine all three to transform, filter by a threshold, and total a sequence of values.
You'll learn the thread-first (->) and thread-last (->>) macros that turn nested function calls into linear, top-to-bottom pipelines. You'll see -> thread a value as the first argument through a chain of steps, and ->> thread it as the last for seq-processing functions like map and filter, then build a multi-step pipeline that processes a sequence so the data flow reads like English.
You'll tour four function combinators that show Clojure treating functions as values with rare ease: comp to combine functions, partial to fix leading arguments, juxt to apply several functions and return a vector of results, and complement to flip a predicate. You'll use juxt to compute several summary statistics over a collection in a single pass.
You'll discover that reduce is far more powerful than it first appears, using it to build a frequency map from a sequence and reduce-kv to walk a map's keys and values together. You'll see that nearly every aggregation in idiomatic Clojure runs through reduce — building collections, computing statistics, running state machines — and you'll count word occurrences in a short phrase using only reduce.
You'll learn transducers, building one with comp, map, and filter and running it over a collection with transduce, sequence, and into. You'll see that this version creates no intermediate collections and that the same transducer can be reused across different input types, then convert a ->> pipeline of map and filter into transducer form with into to cut memory and time on large data.
You'll learn why code is data in Clojure: source files are first parsed by the reader into ordinary data structures — lists, vectors, maps, symbols, and keywords — and only then evaluated. You'll see how this homoiconicity is the secret behind macros, reader macros like #() and ', and the prevalence of DSLs, and contrast it with the private compiler ASTs of Python and Java.
You'll learn atoms, Clojure's everyday tool for shared mutable state, by creating an atom holding a counter, incrementing it from many threads with swap! and a function, and reading it with deref or @. You'll see that swap! takes a function that may be retried under contention, making updates lock-free and safe, then accumulate a running total across calls and clear it back to a starting value with reset!.
You'll learn refs and the dosync transaction by transferring an amount between two refs atomically and printing both balances. You'll understand that refs are for the rare case where multiple identities must change together with ACI guarantees from software transactional memory, learn honestly why atoms are more common, then run a transfer from two threads and watch the totals stay consistent.
You'll learn future and promise, the lightweight asynchrony primitives that wrap the JVM thread pool. You'll run a slow computation in the background with future and deref it to block for the result, then use a promise to bridge code that delivers a value and code that waits for it. You'll fire off three futures that each return after a small delay and collect them with deref.
You'll learn core.async by creating a channel with chan, spawning a go block that puts a value with >!, and another that takes it with <!. You'll understand the communicating-sequential-processes model inspired by Go and Hoare, where go blocks become cooperative state machines on a small thread pool, then wire two go blocks through a channel as a tiny producer-consumer.
You'll learn pmap for painless parallelism, applying a slow function across a collection serially with map and then in parallel with pmap while timing both. You'll learn when pmap helps — CPU-bound pure work on moderate collections — and when it hurts, on I/O-bound work or tiny tasks, then run a deliberately slow function across inputs and watch the speedup.
You'll learn add-watch and set-validator!, attaching a watcher that prints any state change to an atom and a validator that rejects negative values. You'll see how Clojure separates observing change from causing it — cleaner than callbacks or observer-pattern classes — then attach a watcher that logs every change to a counter and a validator that keeps it from going below zero.
You'll understand what makes macros fundamentally different from functions: a function takes evaluated values and returns a value at runtime, while a macro takes unevaluated code as data and returns transformed code at compile time. You'll see that when, and, and or are themselves macros built on if, learn how libraries build entire sub-languages this way, and absorb the rule to reach for macros last.
You'll learn lazy sequences and infinite streams by using range with no upper bound, then pulling the first values with take and combining filter with take. You'll see that nothing is computed until something pulls on the sequence, which is why infinite definitions are normal in Clojure, learn about the head-holding pitfall, and use take-while to keep pulling only while a condition holds.
You'll learn four sequence generators: iterate to apply a function repeatedly for successive values, repeat for an infinite sequence of one value, repeatedly to call a side-effectful zero-arg function over and over, and cycle to loop a small collection forever. You'll use take to keep output finite, then grow a sequence with iterate and take the first several values.
You'll learn the slicing toolkit: partition for fixed-size chunks, partition-by to group adjacent items sharing a key, take-while to take elements while a predicate holds, and drop-while to skip them. You'll see why these are the everyday tools for windowing data and handling grouped inputs, then partition a sequence of numbers into fixed-size chunks and process each chunk.
You'll learn three small but heavily used functions: group-by to partition a collection by a key function into a map of groups, frequencies to count how often each value appears, and distinct to drop duplicates while preserving order. You'll run all three on small collections, then group a vector of orders by customer and count the orders per customer in a single line.
You'll learn two surprising lazy-sequence behaviors: map over a range realizes elements in chunks of 32, which can run side effects more times than expected, and binding a sequence with def can hold the head and prevent garbage collection. You'll absorb the rules — don't mix side effects with lazy seqs, don't bind long-lived names to their heads — then spot and refactor the risky snippet.
You'll learn the big idea behind every reference type: Rich Hickey's distinction between value (immutable), identity (a stable name for a series of values over time), and state (the value an identity holds at a moment). You'll map this onto atoms for uncoordinated updates, refs for coordinated transactions, and agents for asynchronous change — Clojure's coherent answer to the concurrency crisis.
You'll learn defrecord by defining a record with a few named fields, constructing an instance, accessing fields with keyword lookup, and confirming records still behave like maps. You'll see that records are faster than plain maps for known-shape data because field access compiles to direct method calls and that they participate in protocols, then build a record from a plain map and watch a record take part in a protocol.
You'll learn defprotocol and extend-protocol by defining a Shape protocol with an area method, implementing it for two record types, and dispatching by calling area on each. You'll see how protocols give polymorphic dispatch like Java interfaces but defined separately from the types, so you can extend types you don't own — even a built-in type like a number — then extend the protocol to handle one more shape.
You'll learn multimethods, using defmulti to dispatch on a custom function — like the :type keyword in a map — and defmethod to register an implementation per value. You'll see why this is more flexible than protocols, since the dispatch function can examine anything at runtime, at the cost of speed, then write a multimethod that dispatches on a computed value and one that dispatches on multiple arguments.
You'll handle errors by wrapping a failing computation in try, catching a specific exception type, reading information from it, and running cleanup in finally. Then you'll learn ex-info to throw an exception carrying a structured map of data and ex-data to read it back — Clojure's idiomatic way to carry domain detail — and throw and catch an ex-info from a small validation function.
You'll learn with-open by opening a string-based reader, reading from it line by line, and watching it close automatically when the block exits even if an exception is thrown. You'll see that with-open is Clojure's answer to Python's context managers and Java's try-with-resources, built as a macro on try/finally, then use it around a small reader-backed operation to prevent resource leaks.
You'll learn clojure.spec by defining specs for a non-empty string and a map with required keys, validating values with s/valid?, and explaining failures with s/explain. You'll see spec as the dynamic-typing-friendly answer to type systems — describing data shapes precisely and validating at runtime — then write and validate a spec for a map against both a valid and an invalid example.
You'll understand laziness as a concept: most sequence-producing functions like map, filter, and range return lazy sequences that compute elements on demand, making infinite sequences and composable pipelines natural. You'll also learn the honest costs — leaked side effects, held heads, confusing stack traces, and the chunking that realizes 32 elements at a time — so the pull-on-demand model becomes intuitive.
You'll get a mental map of two features that set Clojure apart: transducers, composable transformation pipelines decoupled from the collection or stream they run on, so one pipeline can transform a vector, a channel, or socket data unchanged; and protocols, interfaces defined separately from the types they describe, which solve the expression problem and let you extend built-in types like String without subclassing.
This course contains the use of artificial intelligence.
Clojure is the most widely deployed Lisp in industry, and it rewards programmers who understand not just how to write it but why it was designed the way it is. This course teaches you both at once. You'll write real Clojure from the very first lecture at the REPL, and every coding section opens with a short, story-driven lecture that gives you the context behind the code you're about to write, so the syntax never feels arbitrary.
You start at the keyboard: evaluating expressions, printing values, and learning the parenthesized prefix notation that defines the language. From there you build steadily through bindings, the core data types, equality and conditionals, and the four immutable collections at the heart of every Clojure program. You'll define functions, close over values, and learn to iterate the Clojure way with recursion, comprehensions, and the functional toolbox of map, filter, reduce, threading macros, and transducers. The advanced sections take you into concurrency and state with atoms, refs, futures, and core.async, then into lazy sequences, records, protocols, multimethods, spec, and data-rich error handling.
What sets this course apart is its woven structure. Across ten sections, hands-on coding lectures carry the bulk of the learning, but they're framed by conceptual lectures that explain the ideas a senior Clojure engineer takes for granted: why Rich Hickey built the language in 2007, what hosting on the JVM buys you and costs you, how persistent data structures stay fast while immutable, why code is data, and how identity, state, and time form a coherent model of the world. The course then closes with a run of deeper conceptual lectures that tie the whole language together, ending on the big ideas of lazy evaluation and the sequence abstraction, and the two quietly profound features — transducers and protocols — that set Clojure apart.
By the end you'll be comfortable reading and writing idiomatic Clojure, you'll understand the design decisions behind it well enough to make good choices in your own code, and you'll have hands-on experience with the concurrency and data-shaping tools that make Clojure a favorite for data-heavy, correctness-critical systems. Whether you're coming from Python, JavaScript, Java, or another Lisp, this course meets you where you are and takes you to fluency.