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

Tauan Camargo @tauantcamargo

About: Senior FullStack Engineer 💻 web | 📱 mobile | 🖥️desktop | 8+ Years Experience | Let's connect! 🌟

Location:
Anápolis - Go
Joined:
May 10, 2019

How ts-pattern can improve your code readability?

Publish Date: Sep 13 '24
211 54

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.

Comments 54 total

  • 朱重迁
    朱重迁Sep 14, 2024

    这里面跟rust里面的语法有点像

  • Himanshu Sorathiya
    Himanshu Sorathiya Sep 14, 2024

    Hmm, looks interesting, never heard about this, thanks for sharing

    • Tauan Camargo
      Tauan CamargoSep 14, 2024

      you are welcome. will be sharing here some useful tools and patterns that have been helping me a lot.

  • stevediaz
    stevediazSep 14, 2024

    This sounds amazing as I found it very much useful and informative to be honest.

    • Tauan Camargo
      Tauan CamargoSep 14, 2024

      you are welcome. will be sharing here some useful tools and patterns that have been helping me a lot.

  • rimbin
    rimbinSep 15, 2024

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

  • Alex Lohr
    Alex LohrSep 15, 2024

    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.

    • Jeremy Gollehon
      Jeremy GollehonSep 15, 2024

      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. 💪

    • Tauan Camargo
      Tauan CamargoSep 16, 2024

      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.

      • Alex Lohr
        Alex LohrSep 16, 2024

        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.

  • Helix Pranay
    Helix PranaySep 15, 2024

    This would have been perfect if only its size is a bit less

    Edit - Current npm package size = 417 kb

    • Stanislav
      StanislavSep 16, 2024

      The size of a published npm package is not the same as the package's usage size in an application bundle.
      The approximate size when using the package can be viewed on websites like bundlejs.com.
      For ts-pattern, it will be less than 2.5 kB (gzip)

  • Justin Chase
    Justin ChaseSep 15, 2024

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

    • Marcos Siqueira Junior
      Marcos Siqueira JuniorSep 15, 2024

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

      • Dimitar
        DimitarSep 15, 2024

        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

        • Joshua Enfield
          Joshua EnfieldSep 15, 2024

          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.

      • Fenroe
        FenroeSep 15, 2024

        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.

    • Tauan Camargo
      Tauan CamargoSep 16, 2024

      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.

  • gian-didom
    gian-didomSep 15, 2024

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

  • Justin Young
    Justin YoungSep 15, 2024

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

  • Kim Jensen
    Kim JensenSep 15, 2024

    As a old Delphi programmer I always tought that when I was programming in Java, everything was made complicated, Pascal have been around for a long time I was always thinking why didn't they look at the simplicity of other programming tools when they created Java.
    Case(status)
    Begin
    True: do this
    True: do this
    End

  • Craig Strickland
    Craig StricklandSep 15, 2024

    Appreciate the alternative. This seems more like a solution in search of a problem but the approach is a novel idea ... function chain conditional logic.

  • wyattb
    wyattbSep 15, 2024

    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...

  • Hồng Phát
    Hồng PhátSep 15, 2024

    I would prefer using an object over switch/case:

    ({
      chrome: () => {},
      firefox: () => {},
      safari: () => {} 
    })["chrome"]();
    
    Enter fullscreen mode Exit fullscreen mode
  • John McCabe
    John McCabeSep 15, 2024

    Probably not a great example as all it appears to be doing is getting rid of the 'break' statement that, in decent (IMO) languages, i.e. those not derived from C, isn't there anyway! Also, looks like someone has decided to try to make TS look more like Rust.

    • João Victor VP - N
      João Victor VP - NNov 19, 2024

      Also, looks like someone has decided to try to make TS look more like Rust
      Which in itself is not an inherently bad thing, Rust has a bunch of good design decisions that other languages can learn from. But I think the examples are not the best as well.

  • Roger Senna
    Roger SennaSep 15, 2024

    Typescript should definitely have switch/match expressions (as opposed to statements). If ts-pattern was based on something like "true macros" (like, say, how it's done in Rust), I wouldn't mind it as much: the cost for this ts-pattern would be paid at compile (or, if you prefer, "transpile") time.

    But with those closures being used far and wide I'm pretty sure everything is being done at runtime. And why would you do that just for the sake of making your code look arguably more "cool"...?

    TL;DR If you want a better language, then use one. Typescript lacks switch expressions, and it's better to either accept it, or move on to greener fields...

    • Roger Senna
      Roger SennaSep 15, 2024

      By the way, this reminds me of a story.

      Once upon a time, in a galaxy far away, I worked with this junior developer in a medium sized call center company...

      One day he presented us his "masterpiece": a mechanism for storing Java bytecode in a relational database, which could be loaded dynamically later, and executed by our main web application.

      It was really cool seeing him so happy. And it was very awkward for us. Nobody wanted to criticize his work.

      But of course, we sent the whole thing later to the Big Stack in the Sky. We garbage-collected it, one might even say... And we did it very quickly, because what actually needed to be done did not require that monstrosity at all...!

      That "solution" had many issues, such as difficult maintainability. From a security point of view, also a bit concerning. It mixed data and code in a very unholy union. It was a (very weird) solution for a problem that we simply didn't have...

      That poor, naïve dev did not know better, but fortunately we did. He was trying to reinvent the wheel, which sometimes can even be useful, but certainly not in that case...

      • João Victor VP - N
        João Victor VP - NNov 19, 2024

        I don't think this story is remotely comparable with the package being shown, but sure, whatever makes you feel better.

  • Peter Vivo
    Peter VivoSep 15, 2024

    A simple switch return can solve this problem without need including any more dependency to our program.

    const statusInfo = (status) => {
      switch (status) {
        case "loading": return "Loading...";
        case "success": return "Data loaded successfully!";
        case "error": return "Failed to load data.";
        default: return "Unknown state.";
      }
    }
    
    Enter fullscreen mode Exit fullscreen mode

    My advice is: keep the package.json as small as possible.

    • Tauan Camargo
      Tauan CamargoSep 16, 2024

      Thanks for the comment, yeah I know that… I don’t know if you read the title , how this can improve the readability … not how you can launch a rocket 😊 but hell yeah thanks for read it and commented

  • Kirill Birger
    Kirill BirgerSep 15, 2024

    There are design patterns and principles that mitigate this issue.

    If your code is littered with http status checks and other control logic, then it doesn't matter if you use one library, or ten.

    It would be better to use object oriented design, and single responsibility principle to address these code smells.

    Consider: you should have a Client class for every http API you are using. It is better to use performant, idiomatic code to implement the logic in it. You should then package up the responses in a nice, object oriented way that makes them easy to consume.

    Adding custom syntax and functional indirection is rarely a good idea

  • Ishtiaq Mahmood Amin
    Ishtiaq Mahmood AminSep 15, 2024

    I understand that the conventional examples are intentionally badly written, but the solutions made me feel, when you have a hammer everything looks like a nail.

    • Tauan Camargo
      Tauan CamargoSep 16, 2024

      Dude, it’s basic examples just to introduce someone to the library, I’m not trying to create an article to explain how compilers works…. Wtf

  • Tom Anderson
    Tom AndersonSep 15, 2024

    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
  • Abraham Ortíz
    Abraham OrtízSep 15, 2024

    Install a complete library to replace the native switch… Why not?

    • Tauan Camargo
      Tauan CamargoSep 16, 2024

      Simple, just don’t install 😊. I don’t know if you read the title , how this can improve the readability … not how you can launch a rocket 😊 but hell yeah thanks for read it and commented

  • Tauan Camargo
    Tauan CamargoSep 16, 2024

    Read this other article guys

  • Lucas Lamonier
    Lucas LamonierSep 16, 2024

    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.

  • Federico Kereki
    Federico KerekiSep 16, 2024

    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
    • Norman
      NormanSep 16, 2024

      Thanks so much for the link!

  • Adnan Mushtaque
    Adnan MushtaqueSep 16, 2024

    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!

  • Norman
    NormanSep 16, 2024

    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.

    • Tauan Camargo
      Tauan CamargoSep 16, 2024

      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.

  • Sohil Ahmed
    Sohil AhmedSep 17, 2024

    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.

  • Martin Falk Johansson
    Martin Falk JohanssonSep 17, 2024

    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.

  • leob
    leobSep 17, 2024

    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) !

    • Tauan Camargo
      Tauan CamargoSep 17, 2024

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

Add comment