Disclaimer
AI was used to create parts of this article and create some of the examples
When writing JavaScript, there are often multiple ways to solve the same problem. One common scenario is implementing conditional logic based on different values. The classic approach is to use a switch
statement, but there's a more elegant solution using object literals that I've found to be cleaner and more maintainable in many situations.
The classic switch statement
First, let's remind ourselves how a typical switch
statement looks:
const getAnimalSound = animal => {
switch (animal) {
case 'dog':
return 'woof';
case 'cat':
return 'meow';
case 'cow':
return 'moo';
case 'fox':
return 'what does the fox say?';
default:
return 'unknown sound';
}
};
console.log(getAnimalSound('dog')); // 'woof'
console.log(getAnimalSound('fox')); // 'what does the fox say?'
console.log(getAnimalSound('tiger')); // 'unknown sound'
While this works fine, it's quite verbose and requires a lot of case
statements, break
statements (though we've avoided them here by using return
), and it's not very DRY (Don't Repeat Yourself).
Enter object literals
Let's refactor the above function using an object literal:
const getAnimalSound = animal => {
const sounds = {
dog: 'woof',
cat: 'meow',
cow: 'moo',
fox: 'what does the fox say?',
default: 'unknown sound'
};
return sounds[animal] || sounds.default;
};
console.log(getAnimalSound('dog')); // 'woof'
console.log(getAnimalSound('fox')); // 'what does the fox say?'
console.log(getAnimalSound('tiger')); // 'unknown sound'
The result is the same, but look at how much cleaner and more concise the code is! We've defined an object where each key is a potential value of our animal
parameter, and each value is the corresponding sound.
We then use the parameter to access the corresponding property in our object. The ||
operator provides a fallback if the property doesn't exist (just like our default
case in the switch).
Taking it further with functions
Object literals really shine when you need to do more than just return a simple value. Let's say we have a more complex example where we need to perform different operations based on a command:
const executeCommand = command => {
switch (command) {
case 'greet':
console.log('Hello there!');
break;
case 'farewell':
console.log('Goodbye!');
break;
case 'time':
console.log(`Current time: ${new Date().toLocaleTimeString()}`);
break;
default:
console.log('Unknown command');
}
};
executeCommand('greet'); // 'Hello there!'
executeCommand('time'); // 'Current time: 12:34:56'
We can refactor this using an object literal with functions as values:
const executeCommand = command => {
const commands = {
greet: () => console.log('Hello there!'),
farewell: () => console.log('Goodbye!'),
time: () => console.log(`Current time: ${new Date().toLocaleTimeString()}`),
default: () => console.log('Unknown command')
};
// Execute the command if it exists, otherwise execute the default command
(commands[command] || commands.default)();
};
executeCommand('greet'); // 'Hello there!'
executeCommand('time'); // 'Current time: 12:34:56'
Again, we've reduced verbosity and made the code more maintainable. Each command is clearly associated with its implementation, and there's no need for break
statements.
More complex examples
You can also handle more complex cases, where you might need to access the context or pass parameters to the functions:
const calculator = (a, b, operation) => {
const operations = {
add: (x, y) => x + y,
subtract: (x, y) => x - y,
multiply: (x, y) => x * y,
divide: (x, y) => y !== 0 ? x / y : 'Cannot divide by zero',
default: () => 'Unknown operation'
};
return (operations[operation] || operations.default)(a, b);
};
console.log(calculator(5, 3, 'add')); // 8
console.log(calculator(5, 3, 'multiply')); // 15
console.log(calculator(5, 0, 'divide')); // 'Cannot divide by zero'
console.log(calculator(5, 3, 'power')); // 'Unknown operation'
In this case, we're passing the a
and b
parameters to whichever operation function is selected.
When to use object literals vs switch
While object literals offer a cleaner syntax in many cases, there are situations where a switch
statement might still be preferable:
- When your case conditions aren't simple matches (e.g., they involve ranges or complex expressions)
- When you need to "fall through" multiple cases intentionally
- When your cases need access to variables in the surrounding scope
But for simple mapping between inputs and outputs or actions, object literals often provide a more elegant solution.
A practical real-world example
Let's say you're building a simple state machine for a UI component, where you need to handle different actions based on the current state:
const handleStateTransition = (currentState, action) => {
const stateTransitions = {
idle: {
start: 'loading',
reset: 'idle'
},
loading: {
success: 'loaded',
error: 'error',
cancel: 'idle'
},
loaded: {
refresh: 'loading',
clear: 'idle'
},
error: {
retry: 'loading',
clear: 'idle'
}
};
return stateTransitions[currentState]?.[action] || currentState;
};
let state = 'idle';
state = handleStateTransition(state, 'start'); // 'loading'
state = handleStateTransition(state, 'success'); // 'loaded'
state = handleStateTransition(state, 'clear'); // 'idle'
state = handleStateTransition(state, 'invalidAction'); // 'idle' (unchanged)
This implementation is much cleaner than a massive switch statement with nested conditions, and it makes the state machine's logic very clear and easy to modify.
Conclusions
Object literals provide a powerful alternative to switch statements in JavaScript, offering several advantages:
- More concise and readable code
- Better performance in many cases (browsers can optimize object lookups)
- Easier to maintain and extend
- No need for break statements or worrying about fall-through behavior
Next time you find yourself reaching for a switch statement, consider whether an object literal might make your code cleaner and more maintainable. It's a simple but effective technique that I use frequently in my JavaScript projects.
I hope this helps you write cleaner and more elegant code!
Top comments (6)
Totally my style, love that!
I would add, always remember to watch out for prototype attacks, as well.
Suppose someone makes a call like these:
These could give hackers access to a number of things if not handled properly, including changing keys, changing functionality, adding spy functions, etc...
You may want to consider using a Map instead of an Object, even though the Map may be slightly slower with small objects but would keep you safe.
Good article and good patterns, though.
A Map is a really nice approach actually!
While the change to object seems to be the same as the switch statement performance-wise, the function variant is consistently slower than the switch variant: Benchmark
There's a 5 to 6% loss consistently, so avoid doing this in hot paths.
That's true, you loose some performances, but does it matter?
Don't get me wrong, it is important to know what you lose, but there are always trade-offs.
You could make the same argument about
for
versusforEach
ormap
, but what it makes the code more readable and reusable?I think if you worry about losing 5/6% of performances, because you are using this loop hundreds of thousands of times, maybe you should ask yourself some of this questions:
Because while synthetic benchmarks tell you a story, that story often doesn't happen in real life.
If you are running that many iterations on the client side, you are at risk to crash the browser.
If you are running that many iterations on the server side, there are better languages that can deal with crunching large datasets in a more efficient ways.
The reality is that if your code has to run 200k operations a seconds, you shouldn't rely on a single node to do that job, because then that 5% of loss is the last of your problems.
Again, yes these benchmarks are interesting, it can lead to you to decide to write code in one way or another, but it's not the only factors that you need to consider.
I did say "hot paths". Avoid it on hot paths. 5% on a hot path is disastrous.
I am prefered the switch statement because don't need to use a extra step, even can use a multiple case for solution. But I prefered to using a single switch return in a function.
Object mapping is great if we combine those object instead a single declaration.
or another example is:
code fragments from: dev.to/pengeszikra/javascript-grea...