JavaScript Pattern Matching with the TC39 Pattern Matching Proposal

The TC39 Pattern Matching proposal (Stage 2 as of 2025) introduces a match expression to JavaScript that is far more powerful than switch: it supports destructuring, guard clauses, binding, and checks against both value and shape. You can experiment with it today using the @babel/plugin-proposal-pattern-matching Babel plugin.

Problem: JavaScript conditional logic based on data structure — parsing a REST API response that can be a single item or an array, or a WooCommerce cart that may or may not have a coupon — requires nested if/instanceof checks that obscure intent.

Solution: Use the TC39 Pattern Matching proposal (stage 2, available via Babel plugin) — match (value) { when Array: ..., when { type: 'product' }: ... }. Patterns can match on type, shape, and value simultaneously, making discriminated union handling readable and exhaustive.


The examples below show basic value matching, object-shape matching with guards, array pattern matching, and a real-world fetch-result handler that replaces a chain of if/else if blocks.


// ── 1. Basic value matching ──────────────────────────────────────────────
const status = 404;

const message = match (status) {
    when (200) { 'OK' }
    when (201) { 'Created' }
    when (400) { 'Bad Request' }
    when (404) { 'Not Found' }
    when (500) { 'Server Error' }
    else       { `Unknown status ${status}` }
};

// ── 2. Object-shape matching ─────────────────────────────────────────────
function describeShape(shape) {
    return match (shape) {
        when ({ kind: 'circle',    radius })    { `Circle r=${radius}` }
        when ({ kind: 'rect',      w, h })      { `Rect ${w}×${h}` }
        when ({ kind: 'triangle',  base, h })   { `Triangle b=${base} h=${h}` }
        else { 'Unknown shape' }
    };
}

// ── 3. Guard clauses ─────────────────────────────────────────────────────
function classify(n) {
    return match (n) {
        when (x) if (x < 0)   { 'negative' }
        when (0)               { 'zero' }
        when (x) if (x < 10)  { 'small positive' }
        else                   { 'large positive' }
    };
}

// ── 4. Real-world fetch result handler ───────────────────────────────────
async function fetchUser(id) {
    const res = await fetch(`/api/users/${id}`);
    const body = res.ok ? await res.json() : null;

    return match ({ ok: res.ok, status: res.status, body }) {
        when ({ ok: true,  body: { name, email } }) { `${name} <${email}>` }
        when ({ status: 401 })                       { throw new Error('Unauthorised') }
        when ({ status: 404 })                       { null }
        when ({ status: s }) if (s >= 500)           { throw new Error(`Server error ${s}`) }
        else                                         { throw new Error('Unexpected response') }
    };
}


NOTE: The pattern-matching proposal syntax is not yet in any stable browser runtime — use it only behind a Babel transform in production; for today's JavaScript use explicit if/else chains or a small helper library like ts-pattern (TypeScript) that provides the same ergonomics.