100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Pattern Matching in Haskell

Learn how Haskell lets you destructure values directly in function definitions and case expressions to write clear, exhaustive branching logic.

Core ConceptsBeginner9 min readJul 10, 2026
Analogies

Matching on Constructors and Literals

Pattern matching lets you define a function as a series of equations, each matching a different shape of input, rather than writing nested if statements — describe 0 = "zero" and describe n = "nonzero: " ++ show n dispatch on whether the argument literally equals 0 or falls through to the catch-all variable pattern n. The same mechanism destructures algebraic data types: given data Shape = Circle Double | Rectangle Double Double, a function area (Circle r) = pi * r * r and area (Rectangle w h) = w * h pulls the radius or width/height straight out of the constructor without calling a separate accessor function. GHC checks pattern matches for exhaustiveness and will warn (or error, with -Wall -Werror) if you forget a constructor, catching a class of bugs that's easy to miss with manual if/else chains.

🏏

Cricket analogy: Pattern matching a dismissal type is like an umpire's decision tree: 'bowled' matches one pattern, 'caught behind' matches another, and 'lbw' a third — each shape of dismissal is handled by its own named rule rather than one giant nested if.

Deconstructing Lists and Tuples

Lists have their own natural patterns built from [] and :, so sumList [] = 0 and sumList (x:xs) = x + sumList xs matches the empty list as the base case and splits any non-empty list into its head x and tail xs for the recursive case. Tuples destructure positionally, so midpoint (x1, y1) (x2, y2) = ((x1+x2)/2, (y1+y2)/2) pulls out coordinates without any explicit fst/snd calls, and nested patterns like (x:_:rest) let you grab the first element, skip the second with the wildcard _, and bind the remainder in a single line. Guards can be combined with patterns for extra precision, as in classify (x:xs) | x > 0 = "starts positive", letting you match structurally and then further refine with a boolean condition.

🏏

Cricket analogy: Matching (x:xs) on a list of scores is like an analyst pulling the first ball of an over separately from the remaining five balls, to comment on the opening delivery before summarizing the rest.

haskell
data Shape = Circle Double | Rectangle Double Double

area :: Shape -> Double
area (Circle r) = pi * r * r
area (Rectangle w h) = w * h

sumList :: [Int] -> Int
sumList [] = 0
sumList (x:xs) = x + sumList xs

describeFirst :: [Int] -> String
describeFirst (x:_) | x > 0 = "starts positive"
                     | x < 0 = "starts negative"
describeFirst (0:_) = "starts with zero"
describeFirst [] = "empty list"

Case Expressions and Exhaustiveness

The case expression brings the same pattern-matching power into any expression position, not just top-level function equations: case shape of Circle r -> ...; Rectangle w h -> ... lets you match locally, inside a let or where, or as an argument to another function. Because Haskell's type system knows every constructor of a data type, GHC's exhaustiveness checker can prove at compile time whether a case covers every possibility — enabling -Wincomplete-patterns to flag a forgotten branch before the program ever runs, unlike dynamically typed languages where a missing else branch might only surface as a runtime crash. This compile-time safety net is one of the biggest practical reasons Haskell developers lean on algebraic data types and pattern matching instead of manual type tags and casts.

🏏

Cricket analogy: A case expression is like a match referee's decision matrix used anywhere in the report, not just the final scorecard — 'six', 'four', 'dot ball', or 'wicket' can each be matched and handled inline wherever the commentary needs it.

The wildcard pattern _ matches anything without binding a name, and is commonly used for the catch-all final equation, e.g. dayType _ = "weekday" after specific matches for "Saturday" and "Sunday" — this keeps the compiler's exhaustiveness checker satisfied while ignoring values you don't need.

Never rely on pattern-match order alone to feel exhaustive without checking — a partial function like firstElement (x:_) = x compiles fine but will crash with a runtime pattern-match failure on an empty list; always enable -Wincomplete-patterns (or -Wall) so GHC warns you about missing cases at compile time instead of finding out in production.

  • Pattern matching dispatches on the shape (constructor, literal, or structure) of a value, replacing nested if/else chains.
  • Function equations can match multiple patterns, including literals, constructors, and the wildcard _.
  • Lists pattern-match naturally via [] and (x:xs), splitting head and tail in one step.
  • Tuples destructure positionally, e.g. (x, y), without needing fst/snd accessor calls.
  • Guards (| condition) can be combined with patterns for finer-grained branching.
  • case expressions bring pattern matching into any expression position, not just top-level equations.
  • GHC's exhaustiveness checker (with -Wincomplete-patterns) flags missing cases at compile time, preventing runtime crashes.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#HaskellStudyNotes#PatternMatchingInHaskell#Pattern#Matching#Haskell#Constructors#StudyNotes#SkillVeris#ExamPrep