Switch case statements are attracting attention in the programming community for their drawbacks. Some developers claim that the verbosity and bulkiness of switch cases are reasons to abandon using them entirely. However, are switch case statements that bad?
This article will discuss the pros and cons of TypeScript’s switch case, cover some alternatives, and look at which alternatives are best for various use cases.
Jump ahead:
A switch case block is a control structure that performs different actions according to outputs provided by given conditions. In JavaScript, the switch keyword of a switch case block needs a condition to evaluate. In contrast, the other statements inside the block represent possible outcomes or cases of that condition and their associated actions.
When a match is found, the statements associated with that match execute. The default statement executes if no match is found, and the control leaves the switch case block.
Because TypeScript is a superset of JavaScript, most of the processes will stay the same, except for some type introductions.
The example below explains switch case usage in TypeScript:
const vitamin: string = "A"; switch(vitamin.toLowerCase()) { case "a": console.log("Spinach, Carrots, Sweet Potatoes, Mango"); break; case "b": console.log("Meat, Fish, Milk, Cheese, Eggs."); break; case "c": console.log("Oranges, Kiwi, Lemon, Strawberries, Tomatoes"); break; default: console.log("No sources available."); break; }
See this example in the TypeScript Playground.
In the code above, we added a string
type to the variable Vitamin
, which represents the type-safe nature of TypeScript.
Switch case blocks serve as substitutes for lengthy, nested if-else
statements. Although there are alternatives, switch cases still have some advantages:
if-else
blocksif-else
if-else
blocksDespite their superior performance to if-else
blocks switch case statements don’t look as impressive in advanced scenarios. Considering the modern-day software development process, switch cases have significant downsides.
While there is nothing wrong with the utility of switch cases, the real pain for developers is misapplying them, which is likely to occur because of the underlying problems of switch cases:
Developers nowadays tend to avoid verbosity in their code. Take a look at the following code block for an example of how switch cases lead to more verbose code:
const day: number = 1; switch (day) { case 1: console.log("Monday"); break; case 2: console.log("Tuesday"); break; case 3: console.log("Wednesday"); break; case 4: console.log("Thursday"); break; case 5: console.log("Friday"); break; case 6: console.log("Saturday"); break; case 7: console.log("Sunday"); break; default: console.log("Invalid Input !!!!"); }
In the code above, the repeated use of case
and break
makes the code bulky and lengthy.
Missing break
keywords in switch case clauses lead to frustrating errors. Although it may not appear serious in minor projects, these errors can cause severe bugs in projects with huge codebases.
The error-prone nature of switch case blocks makes them less useful in large projects and ultimately less appealing to developers.
Although switch cases have better maintainability in smaller projects than if-else
statements, their bulkiness and error-prone behavior damage their maintainability. Switch case statements are known to deteriorate code maintainability as projects grow.
Nowadays, developers prefer to use alternatives to switch cases to keep their codes more readable and maintainable.
In JavaScript and TypeScript, object lookup is the most common method for avoiding switch cases. This technique uses an object literal like a table, with possible cases and their associated actions, and consumes it:
const vitamin: string = "A", defaultSource = "No sources available." const sources: { [key: string]: string; } = { "a": "Spinach, Carrots, Sweet Potatoes, Mango", "b": "Meat, Fish, Milk, Cheese, Eggs", "c": "Oranges, Kiwi, Lemon, Strawberries, Tomatoes", }; console.log(sources[vitamin.toLowerCase()] || defaultSource);
See this example in the TypeScript Playground.
That looks much neater and easier compared to using a switch case block.
The most significant point of the object lookup technique is that, unlike switch case blocks, they don’t require adding a break
for every case. This prevents you from missing breaks
and causing bugs, which is helpful when working on a big codebase.
Object lookup tables are less verbose, easily readable, and easily scanned. Although this alternative to switch cases has benefits, it also has time and memory tradeoffs.
The object lookup approach might not be as fast as switch cases when there aren’t many cases to evaluate. Since an object is a data structure, accessing keys would require more work than simply checking values and returning them, as we do with the switch case.
A JavaScript object is cached in memory and later cleared by the garbage collector as soon as the object scope ceases to exist. More object keys take more memory and add to the space complexity.
It is always a good idea to test the performance of big object tables before finalizing their usage in your project.
Even though switch cases don’t have memory limitations, they are hard to maintain. As the project grows, adding more cases and missing one break
leads to big problems, including violating the open-closed principle of SOLID software design.
To understand how switch cases are prone to violating the open-closed principle, let’s look at this example:
enum Vitamin { A, B, C }; class VitaminHelper { public sources(vitamin: Vitamin) { switch(vitamin) { case Vitamin.A: return "Spinach, Carrots, Sweet Potatoes, Mango"; case Vitamin.B: return "Meat, Fish, Milk, Cheese, Eggs"; case Vitamin.C: return "Oranges, Kiwi, Lemon, Strawberries, Tomatoes"; default: return "No sources available."; } } public symptoms(vitamin: Vitamin) { switch(vitamin) { case Vitamin.A: return "Dry eyes, dry skin, frequent infections"; case Vitamin.B: return "Anaemia, fatigue, or poor balance, memory loss"; case Vitamin.C: return "Bruising, coiled hair, failure to thrive, irritability"; default: return "No deficiency symptoms available."; } } }
See this example in the TypeScript Playground.
For simplicity, I’ve avoided using the break
keyword in the example and used return
instead.
The code above shows the use of enum
in the VitaminHelper
class has two methods to handle the vitamin sources and the deficiency symptoms.
Both ways implement the switch case with the Vitamin
to determine the cases and take the appropriate actions. We must modify both methods if we add a few more vitamin
values to the enum
.
Implementing similar patterns of switch case statements across large projects adds to poor maintainability and opens the entities to modifications — violating the open-closed principle of software design.
In the above example, if we replaced the switch case with an object table, each method would still need to be updated whenever a new object key is added. It won’t solve the maintainability problem.
TypeScript supports the OOP paradigm of programming, which helps create more robust and maintainable code without using switch cases or object lookup tables. Let’s examine polymorphism and inheritance in OOP as alternatives to switch case blocks in TypeScript.
Inheritance allows us to derive a class from an existing base class. With polymorphism, we can change the behavior of the existing members of the base class. Let’s use these concepts to fix the poor maintainability of our current code.
First, we need to drop the scoped enumerations and write an abstract
class that includes two methods we will inherit in subsequent classes:
abstract class Vitamin { public sources() { /* Default sources */ } public symptoms() { /* Default symptoms */ } }
Following the polymorphism and inheritance concepts, we can write different classes for extending our base class and overriding its default methods:
class A extends Vitamin { public sources() { return "Spinach, Carrots, Sweet Potatoes, Mango"; } public symptoms() { return "Dry eyes, dry skin, frequent infections"; } } class B extends Vitamin { public sources() { return "Meat, Fish, Milk, Cheese, Eggs"; } public symptoms() { return "Anaemia, fatigue, or poor balance, memory loss"; } } class C extends Vitamin { public sources() { return "Oranges, Kiwi, Lemon, Strawberries, Tomatoes"; } public symptoms() { return "Bruising, coiled hair, failure to thrive, irritability"; } }
Now, whenever a new Vitamin
is introduced, we can repeat the process above and write a new Vitamin
association that extends our base class.
class D extends Vitamin { public sources() { return "Sunlight, Salmon, sardines, red meat, egg yolks"; } public symptoms() { return "Fatigue, bone pain or achiness, hair loss"; } }
See this example in the TypeScript Playground.
In the example above, the inheritance property of OOP in TypeScript allowed us to repeat the behavior of a base class in different child classes. Classes A
, B
, and C
are child classes of Vitamin
and inherit all its properties.
Polymorphism helped us change the form and behavior of our base class. This was demonstrated when overriding or redefining the sources()
and symptoms()
methods in each child class.
Unlike switch case statements, which require us to modify existing classes constantly, polymorphism avoids this and is more maintainable and easier to understand.
We can now define a child class that extends the base class Vitamin
and overrides its behavior whenever a new Vitamin
value needs to be introduced.
We learned about TypeScript’s switch case statements, the troubles they may cause, and some of their alternatives. Switch case statements are not harmful to small hobby projects, but it’s easier to use alternatives to avoid switch cases as your project grows.
If memory is not a concern, the object lookup technique is a great alternative to switch cases. For projects with the potential to grow, it is best to use polymorphism and inheritance to specify different cases clearly and keep maintainability.
I hope you enjoyed reading the article. Let me know your thoughts in the comments.
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowExplore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.
Build a real-time image background remover in Vue using Transformers.js and WebGPU for client-side processing with privacy and efficiency.
Optimize search parameter handling in React and Next.js with nuqs for SEO-friendly, shareable URLs and a better user experience.
2 Replies to "Evaluating alternatives to TypeScript’s switch case"
I like this approach however I don’t think it is ideal for the example given since you can’t externalize your strings and would need to make a code change to add or change definitions. With switch/case externalizing strings is simple.
More an issue with the example than the approach though.
I don’t think your approach is incorrect but the example given should probably use externalized stringsto allow new definitions to be added without code changes.