DEV Community

Cover image for How ts-pattern can improve your code readability?
Tauan Camargo
Tauan Camargo

Posted on

How ts-pattern can improve your code readability?

My First Encounter with ts-pattern

A few months ago, I was reviewing a client’s codebase filled with numerous switch statements and object literals spread across many files, which was making the readability and maintainability a mess.

During a pair programming session with my teammate Erick, he shared his screen and showed me a library that he was testing out.

That was my first experience with ts-pattern 🤩, and honestly, it blew my mind! The improvement in readability and maintainability was incredible.

Here is the link for ts-pattern documentation.

Let me show you some use cases where ts-pattern can make a real difference. One of the first questions I had was: Can I use this in any TypeScript project? The answer is YES 🙌.

Use cases

1. Replacing Complex Switch Statements

Traditional switch statement:

const status = "success";

let message;
switch (status) {
  case "success":
    message = "Operation was successful!";
    break;
  case "error":
    message = "There was an error.";
    break;
  default:
    message = "Unknown status.";
}
console.log(message);  // Output: Operation was successful!
Enter fullscreen mode Exit fullscreen mode

Using ts-pattern:

import { match } from 'ts-pattern';

const status = "success";

const message = match(status)
  .with("success", () => "Operation was successful!")
  .with("error", () => "There was an error.")
  .otherwise(() => "Unknown status.");

console.log(message);  // Output: Operation was successful!
Enter fullscreen mode Exit fullscreen mode

Comparison:

Readability using ts-pattern is clean, no need for break statements, and pattern matches focus directly on the cases. Adding or removing conditions in ts-pattern is easier, and you don’t have to deal with the traps of forgetting breaks in switch cases.

2. Object Matching for API Responses

Using object matching:

const apiResponse = {
  status: 200,
  data: {
    user: {
      id: 1,
      name: 'John',
    },
  },
};

let userName;
if (apiResponse.status === 200 && apiResponse.data.user.name === 'John') {
  userName = "Hello, John!";
} else {
  userName = "User not found.";
}

console.log(userName);  // Output: Hello, John!
Enter fullscreen mode Exit fullscreen mode

Using ts-pattern:

const apiResponse = {
  status: 200,
  data: {
    user: {
      id: 1,
      name: 'John',
    },
  },
};

const userName = match(apiResponse)
  .with({ status: 200, data: { user: { name: "John" } } }, () => "Hello, John!")
  .otherwise(() => "User not found.");

console.log(userName);  // Output: Hello, John!
Enter fullscreen mode Exit fullscreen mode

Comparison:

ts-pattern reduces the need for deeply nested if conditions, making the logic cleaner and smoother. The pattern matching directly reflects the structure of the object, making it easier to understand and modify.

3. State Management

State management using switch:

const appState = { status: "loading" };

let displayMessage;
switch (appState.status) {
  case "loading":
    displayMessage = "Loading...";
    break;
  case "success":
    displayMessage = "Data loaded successfully!";
    break;
  case "error":
    displayMessage = "Failed to load data.";
    break;
  default:
    displayMessage = "Unknown state.";
}

console.log(displayMessage);  // Output: Loading...
Enter fullscreen mode Exit fullscreen mode

Using ts-pattern:

const appState = { status: "loading" };

const displayMessage = match(appState.status)
  .with("loading", () => "Loading...")
  .with("success", () => "Data loaded successfully!")
  .with("error", () => "Failed to load data.")
  .otherwise(() => "Unknown state.");

console.log(displayMessage);  // Output: Loading...
Enter fullscreen mode Exit fullscreen mode

Comparison:

ts-pattern simplifies state management by eliminating the need for repetitive case and break statements. As the number of states grows, ts-pattern scales better with fewer chances of logical errors.

By comparing switch statements, object literals, and traditional conditionals with ts-pattern, it’s clear that ts-pattern offers a more elegant and scalable approach. Whether you’re handling complex states, object matching, or validations, ts-pattern reduces boilerplate code, improves readability, and minimizes potential bugs. Give it a try.

Will try to be more consistent in posting articles here 🤪. Thanks.

Top comments (52)

Collapse
 
lexlohr profile image
Alex Lohr

It is interesting that the code of the comparison seems intentionally bad.

const stateMessage: Record<string, string> = = {
  success: "Operation was successful!",
  error: "There was an error.",
  unknown: "Unknown status."
};
const message = stateMessage[status] ?? stateMessage.unknown;

// ---

import deepEqual from 'fast-deep-equal';
const userName = deepEqual(
  apiResponse, 
  { status: 200, data: { user: { name: "John" } } })
  ? "Hello, John!"
  : "User not found.";
Enter fullscreen mode Exit fullscreen mode

Please try to compare on equal footing.

Collapse
 
tauantcamargo profile image
Tauan Camargo

Yeah dude i know you cna do it, I don't know what you guys are interpreting, where did I say that this was the best solution? The most efficient?

Dude, I'm just reporting a library that I thought was cool.

Anyway, thank you very much for taking the time to read the article and for the comment.

Collapse
 
lexlohr profile image
Alex Lohr

I know from experience that good examples are difficult, but if you do a before/after, make sure to optimize the code on both sides or it is going to look insincere. Or even better, avoid it and skip the "before" part. If the example cannot convince without the contrast, you should take the time to find a better one.

Thread Thread
 
tauantcamargo profile image
Tauan Camargo

Thanks for advise

Collapse
 
gollyjer profile image
Jeremy Gollehon

As reading i was thinking the same thing regarding object switches.

What i hadn't ever thought about was using deepEqual in that way. Thanks for the idea. 💪

Collapse
 
fkereki profile image
Federico Kereki

This is not the point, but code could be cleaner using the constant (K) combinator; I prefer naming it constant (value would be a valid alternative) to make code clearer:

const message = match(status)
  .with("success", constant("Operation was successful!"))
  .with("error", constant("There was an error."))
  .otherwise(constant("Unknown status."));
Enter fullscreen mode Exit fullscreen mode
Collapse
 
koehr profile image
Norman

Thanks so much for the link!

Collapse
 
koehr profile image
Norman

There are for sure some pretty opinionated comments in here. Some people are very quick to dismiss this as bloat for syntactic sugar. But this is not the case and that becomes clear, as soon you read the whole article, and the author could maybe have made this a bit clearer as well:
The major problem with switch statements is not lack of readability, but verbosity paired with very limited use. ts-pattern "fixes" that with an expressive syntax that can do more than a switch statement and is as powerful as a full flegded if-else cascade would be. Additionally, and this is more important in my opinion, it supports exhaustive checking, which a switch statement would only support with very explicit typing (i.e. using a result type).
Btw, the library is only 2.5kB according to Bundlephobia. No idea, how some commentors came to the conclusion it would add hundreds of kilobytes to your bundle.
I really recommend checking the examples on github.com/gvergnaud/ts-pattern before judging the library and even more to incorporate the intend of the author before judging the article.

Collapse
 
tauantcamargo profile image
Tauan Camargo

Thank you so much for the advise, and to took a time to read, as English is not my native language I’m using this to improve my self,I’ll try to add this items that you mentioned on my next articles. ❤️ appreciate that you understood what I was trying to share here.

Collapse
 
twocs profile image
Tom Anderson

I personally feel that switch statements, especially the ones posted above, are fairly readable. However, the main thing that packages like ts-pattern (and proposal github.com/tc39/proposal-pattern-m...) contribute is improved pattern matching with type safety. JavaScript only supports primitive types for switch statements, so a workaround for more complex comparisons has been to use:

switch (true) {
case !p: return 536;
case p.status === fancy: return 1886;
case p.zip ===ts: return 12;
default: return 5;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
leob profile image
leob

The way it "reads" is nice, but I can't help but notice how incredibly simple it is, lol ... is this all there is to this library, or does it do more?

I do like the coding style though - something like this should just be baked into JS (or into TS, haha) !

Collapse
 
tauantcamargo profile image
Tauan Camargo

It does a lot more, I will make another post covering more examples, thanks for the comment

Collapse
 
rimbin profile image
rimbin

How big is the performance downgrade? It doesn't look as a zero cost abstraction.

Collapse
 
cugarteblair profile image
Carlos E. Ugarte

It would only matter if it were inside a loop

Collapse
 
justinmchase profile image
Justin Chase

I'd hard block this PR. You're gonna regret it massively later. Just use the simple syntactic approach.

Collapse
 
tauantcamargo profile image
Tauan Camargo

I don't know what you guys are interpreting, where did I say that this was the best solution? The most efficient?

Dude, I'm just reporting a library that I thought was cool.

Anyway, thank you very much for taking the time to read the article and for the comment.

Collapse
 
marcosjr182 profile image
Marcos Siqueira Junior

Can you elaborate a bit more on why would you do that?

Collapse
 
fenroe profile image
Fenroe

Eventually someone is going to review the code base for improvements and notice a package is being used to basically make switch statements look cuter. After an initial bout of confusion, a ticket will be added to refactor all instances of ts-method into switch statements. Not only is this package unnecessary, but it reflects a tendency of one of the developers to introduce useless packages to the project, and those useless packages will eventually start taking a toll on performance and upkeep.

Thread Thread
 
kenny_kuhner_5bcbe071910f profile image
Kenny Kuhner

Exactly

Collapse
 
dtasev profile image
Dimitar

If's and switch'es will be there in 10 years. There's no guarantee ts-pattern will be there in a month. That's enough to bar it from touching any sensible production project.

Apart from that it's somewhat pointless semantic sugar that makes it harder for another developer to pick up your code and adds extra size to your app.

Could be fun for some standalone or amateur project, but I doubt any senior developer will, nor should, allow this in

Thread Thread
 
joshua_enfield_a6ea8e7c54 profile image
Joshua Enfield

If and switches may be, but Typescript may not 😆 core frameworks often change major paradigms over a decade. The extra size is probably nominal. Abstractions are everywhere. This isn't a major framework.

Collapse
 
martinfjant profile image
Martin Falk Johansson

A lot of people who complain here must've never ever used a language that has pattern matching. While one can question whether you need add extra dependency, you technically never really need one. You do not need react, express or vue. Occasionally you might want to add something that makes different patterns possible, and this looks like it would make a functional style pattern matching possible (switch does not, mind you).

I get the feeling that the very negative seniors are old OOP-java people that refuse anything that is newer than 10 years ago. Jeesh, give the guy some slack. He did write something and it's not AI slop or a top ten. It is entirely possible to say that you would not use this in a job setting whilst at the same time not being overly negative nor implying that the author is an idiot.

Collapse
 
tauantcamargo profile image
Tauan Camargo

Thanks for the comment, appreciate

Collapse
 
technvision profile image
Sohil Ahmed

Not on a ts tech stack but im sure this will be a performance downgrade somewhere cuz js need to be written in the same way and that js will get much complex if the ts code is something of work all by it self.

Not to mention build time will also suffer.

Collapse
 
wyattbiker profile image
wyattb

Just a weakness in that language. In modern programming languages it is a built in keyword and much more readable and flexible

docs.godotengine.org/en/stable/tut...

php.net/manual/en/control-structur...

doc.rust-lang.org/book/ch06-02-mat...

Collapse
 
adnanmushtaque profile image
Adnan Mushtaque

In coding, just like in driving games like Dr. Driving, efficiency and simplicity matter. When I was reviewing a client's codebase, filled with complex switch statements and object literals, it reminded me of navigating tight, chaotic streets. That’s when I discovered ts-pattern, which streamlined the code just like a well-designed driving path. Instead of cluttered switch cases, ts-pattern made everything clear and manageable, much like how Dr. Driving Official APK challenges you to navigate with precision. By cutting down unnecessary complexity, both the game and the library focus on smooth execution. Check out ts-pattern to simplify your TypeScript projects!

Collapse
 
giandidom profile image
gian-didom

«deeply nested if conditions,» --> a simple "if-else"... Just stop, this is making the code totally unreadable with little-to-no benefit...

Collapse
 
jyoung4242 profile image
Justin Young

Thanks for this article, i will be incorporating this package into my workflow!

Collapse
 
idrk profile image
Lucas Lamonier

In my humble opinion, you don't need an extra dependency and a long string of callbacks (reminiscent of .then era), you need better code formatting.

Collapse
 
tauantcamargo profile image
Tauan Camargo

Read this other article guys