Pattern Matching in JavaScript
Today, I want to share with you a coding pattern I frequently use when writing conditional JavaScript. I’ve made several tweets about it throughout the years and it’s high time that I finally write a blog post that I can use as a reference. In fact, I use this pattern so often that my manager at work started to call it the “Kyle pattern”. It shouldn’t be called that, but I’ll take it as a compliment.
I call what I’m about to show you “a poor person’s pattern matching” or “makeshift pattern matching”. It’s not quite the real thing, but it does a pretty good job of coming close to it and is plenty useful.
Before you go further, you should understand switch
statements pretty well. If you’re not familiar with how they work in JavaScript, I recommend reading the MDN docs on switch
. With that, let’s get to it.
What is “pattern matching”?
I first encountered “pattern matching” when I started to learn ReasonML a few years ago. In ReasonML, a strongly typed functional language, you have variants. A variant is a union type, but unlike TypeScript, we can have conditional logic based on the type of value passed in, not just the value itself. This is a really powerful mechanism for handling conditional functionality in our programs.
With ReasonML’s switch
, when we pass in a value, we look at the value’s type and choose different logical paths accordingly. On their home page, they share this example:
type schoolPerson = Teacher | Director | Student(string);
let greeting = person =>
switch (person) {
| Teacher => "Hey Professor!"
| Director => "Hello Director."
| Student("Richard") => "Still here Ricky?"
| Student(anyOtherName) => "Hey, " ++ anyOtherName ++ "."
}
The syntax highlighter I am using, Shiki, doesn’t currently support ReasonML. If you know where I can find a Textmate grammar for ReasonML, let me know and I’ll make a pull request to the project. As a stopgap measure, here’s the equivalent code written in OCaml, which is what the ReasonML syntax is based on.
type school_person = Teacher | Director | Student of string
let greeting person =
match person with
| Teacher -> "Hey Professor!"
| Director -> "Hello Director."
| Student "Richard" -> "Still here Ricky?"
| Student any_other_name -> "Hey, " ^ any_other_name ^ "."
This mechanism quickly becomes the most robust way of doing conditional logic as you use the language. As you work with it, you get comfortable thinking in variants instead of thinking in if/else
s or ternaries. But ReasonML’s switch
and JavaScript’s switch
are a bit different, so how can we recreate some of this magic in JavaScript?
Flipping switch
on its head
When using a switch
statement in JavaScript, we most commonly use the expression (the part in parentheses, which makes this parenthetical kind of meta) as if it’s the left half of a strictly equals check (===
), and each case
as the right half. An example of this I expect a lot of you to be familiar with would be a state reducer a la Redux and/or React.
const reducer = (state, action) => {
switch (action.type) {
case 'DATA_REQUESTED':
return { ...state, status: 'loading' }
case 'DATA_RECEIVED':
return { ...state, data: action.payload.data, status: 'loaded' }
case 'DATA_FAILED':
return { ...state, error: action.payload.error, status: 'error' }
default:
return state
}
}
As we make calls to reducer
, our switch
tries to match the action.type
to the case
with the same string. However, this limits how useful our switch
can be because we can only operate on a single value in the expression. Is there a way around this limitation?
There sure is.
Rhetorical question incoming. What are we doing with the switch
expression and the case
s exactly? We’re strictly comparing (===
) two values and seeing if they come out true
or false
.
Instead, what if we use switch (true)
and make each of our case
s an expression? This opens up some possibilities.
With each case
as an expression, we can do things we couldn’t before, such as:
- Do multiple comparisons on the same value
- Use predicate functions to match cases
- Use multiple values in those predicate functions
- Use
RegExp.prototype.test
to match patterns - and whatever else you can dream up
The key to using switch (true)
in this way is to think of each case
expression as a pattern. Sure, we can’t match on variants in the wonderful way that ReasonML can, but we can use the tools we have to get the matches we want. Let’s look at some examples.
In this first example, I’ve created an areWeThereYet
function. It generates responses to that annoying question based on the milesToGo
.
function areWeThereYet(milesToGo) {
switch (true) {
case milesToGo <= 1:
return "We're hereeeee!"
case milesToGo <= 10:
return 'Almost. Just a little further.'
case milesToGo <= 100:
return 'A little more than an hour.'
case milesToGo <= 250:
return 'Just a half day away.'
default:
return "We're not even close so stop asking!"
}
}
Personally, I find this easier to read than the same code with if/else if/else
s. You might disagree, that’s ok. Stick with me.
The next thing that makes switch (true)
similar to pattern matching is the ability to now use functions in each case
to generate matches. Let’s consider the ReasonML example above, and adapt it a bit to JavaScript.
// I need you to use your imagination here, and pretend
// that all of these predicate functions exist and work correctly :)
const greeting = person => {
switch (true) {
case isTeacher(person):
return 'Hey Professor!'
case isDirector(person):
return 'Hello Director.'
case isRicky(person):
return 'Still here Ricky?'
case isStudent(person):
return `Hey, ${person.name}.`
}
}
There are other benefits to using a switch
this way, such as case
fall-throughs. Rather than writing it as an OR expression (||
), you could write two case
s that use fall-throughs to respond the same way. I’ll “sanitize” a little snippet of an idea from work to show this.
// Again, use your imagination and envision a larger switch
switch (true) {
// ...other cases
case isEcommerceItems(items):
case isCMSItems(items):
return prepareForCollectionList(items)
}
There are a few other patterns to try with this, but I feel like I’ve given you a pretty good idea of how to get started. I’m trusting you to try it out and practice the pattern.
If you want an idea to get started with, you might try to solve the “Gilded Rose” kata with switch (true)
and regexes. I was once given this kata as an interview question and I found my pattern matching trick to be a useful way to solve the problem.
Additional Notes
There is a TC39 proposal to add pattern matching to JavaScript, but it’s been stalled at Stage 1 for quite some time. You can find the proposal repo here.
There is an interesting package called safety-match
created by my Webflow coworker @suchipi. Check it out if you’re interested in pattern matching with TypeScript.
Another popular TypeScript package for pattern matching is ts-pattern
. It’s highlighted feature is that it can be exhuastive, ensuring every possible type case is covered.
Update: Years after writing this post, I decided to try and make a simple, type-safe pattern matching library of my own. I called it kase
. It’s not remarkably more useful than simply using switch (true)
, but it was an interesting exercise that involved some TypeScript shenanigans with the “builder pattern”. You can find it here: https://github.com/kyleshevlin/kase