Make your code clearer by removing redundant variables

Guillaume Briday
5 minutes
One thing I often notice when reviewing Pull Requests, whether on private projects or open-source repositories, is the use of unnecessary variables. These variables add noise, clutter the codebase, and make it harder to read, debug, or simply understand.
One of the best ways to improve your code is to make it as readable as possible for other humans.
I've already written a blog post with 13 tips to write better Rails code. While it's framed around Rails, the advice is broadly applicable to any language or framework, as the tips are more general than Rails-specific.
In this post, I want to focus on a particular issue: redundant or unnecessary variables. Naming things in programming is hard, but we don't always have to do it. Sometimes, the best name is no name at all.
This blog post isn't about boosting performance or minimizing memory allocations, it's entirely focused on improving readability for humans. And that's what matters 99% of the time, because readable code leads to faster development, greater confidence in your codebase, fewer bugs, overall better velocity, and it also helps delay that all-too-familiar urge to abandon the codebase the moment you open it. As always, adapt these suggestions to your need.
In every case, the code performs exactly the same function, but in a more concise and easier-to-understand way.
You can inline computations directly instead of preparing them in advance, it reduces unnecessary variables and keeps the logic closer to where it's used.
// ❌ Don't
function getParams(persons) {
const names = []
const ages = []
persons.forEach((person) => {
names.push(person.name)
ages.push(person.age)
})
return { ages, names }
}
// ✅ Do
function getParams(persons) {
return {
names: persons.map((person) => person.name),
ages: persons.map((person) => person.age),
}
}
There's no need to create a variable, as the method's name already clearly indicates that params will be returned.
// ❌ Don't
function getParams() {
const params = {
a: 'a',
b: 'b',
}
return params
}
// ✅ Do
function getParams() {
return {
a: 'a',
b: 'b',
}
}
There's no need to assign options
to a separate variable, since it's only used as a parameter for doSomething
and won't be reused elsewhere.
// ❌ Don't
const options = {
active: true,
format: 'long',
}
doSomething(options)
// ✅ Do
doSomething({
active: true,
format: 'long',
})
Declaring a variable far from where it's used makes it harder for our brain to keep track of its value. It takes more mental effort to recall or re-evaluate it, whereas inlining it allows us to read and understand the code more naturally, almost like plain English.
// ❌ Don't
const messageType = messageType.toUpperCase()
const { ... } = await response
const { ... } = payload
// More code here
switch (messageType) {
...
}
// ✅ Do
const { ... } = await response
const { ... } = payload
// More code here
switch (messageType.toUpperCase()) {
...
}
Having to name a variable can introduce the risk of giving it a name that doesn't accurately reflect its purpose. By avoiding the variable altogether, that potential inconsistency is eliminated.
// ❌ Don't
const options = {
active: true,
}
items.add({
data: data,
meta: options,
})
// ✅ Do
items.add({
data: data,
meta: {
active: true,
},
})
Make it clear which variables will be reused and which won't. Only extract a variable if it will be referenced more than once or if naming it helps clarify intent. Otherwise, keeping values inlined can make the code more direct and easier to read.
// ❌ Don't
const dropdownRect = dropdown.getBoundingClientRect()
const windowWidth = window.innerWidth
if (dropdownRect.right > windowWidth) {
dropdown.classList.add('dropdown-left')
dropdown.classList.remove('dropdown-right')
} else {
dropdown.classList.add('dropdown-right')
dropdown.classList.remove('dropdown-left')
}
// ✅ Do
const isOverflowing = dropdown.getBoundingClientRect().right > window.innerWidth
dropdown.classList.toggle('dropdown-left', isOverflowing)
dropdown.classList.toggle('dropdown-right', !isOverflowing)
Most of the time, a few unnecessary assignments aren't a big deal. As humans, we mainly care about whether the value is correct and easy to understand. Clear, readable code often matters more than perfectly optimized assignments.
// ❌ Don't
const newValue = isActive(foo)
if (options[index] !== newValue) {
options[index] = newValue
}
// ✅ Do
options[index] = isActive(foo)
Using variables can often lead to mismatches between variable names and the functions or values they represent, which introduces inconsistency and makes the code harder to follow. Inlining values when appropriate can help avoid this disconnect and keep the code more aligned with its intent.
// ❌ Don't
return items.map((item) => {
const isActive = isActiveItem(item)
return isActive
})
// ✅ Do
return items.map((item) => isActiveItem(item))
In this example, it's hard to immediately see that the logic for periodOne
and periodTwo
is identical. The duplication makes the code harder to follow, and it adds cognitive load to remember that the main goal is simply to populate the periods
array. Merging the logic and removing the intermediate variables would make the intent much clearer and the code easier to read.
// ❌ Don't
const periods = []
if (periodOne.from && periodOne.to) {
periods.push({ from: periodOne.from, to: periodOne.to })
}
if (periodTwo.from && periodTwo.to) {
periods.push({ from: periodTwo.from, to: periodTwo.to })
}
return periods
// ✅ Do
return [periodOne, periodTwo]
.filter(({ from, to }) => from && to)
.map(({ from, to }) => ({ from, to }))
This variable exists solely to be inverted, which adds unnecessary indirection. We can simplify the code by inverting the condition directly where it's used, making the logic more concise and easier to follow.
// ❌ Don't
const isExist = items.filter((item) => item.active).length > 1
return !isExist
// ✅ Do
return items.filter((item) => item.active).length <= 1
There's no need to declare the properties in two separate steps, we can inline them to make the code more concise and immediately understandable.
// ❌ Don't
const formData = {}
formData.id = 'lol'
// ✅ Do
const formData = {
id: 'lol',
}
// ❌ Don't
if (condition) {
const newValues = Object.keys(...).reduce(...)
params[key] = newValues
} else {
params[key] = {}
}
// ✅ Do
params[key] = {}
if (condition) {
params[key] = Object.keys(...).reduce(...)
}
You can create the object on the fly and directly access the key that matches the value, there's no need to introduce a temporary variable in between.
// ❌ Don't
function getStatusColor(status) {
const statusColor = {
pending: 'blue',
approved: 'green',
rejected: 'yellow',
}
return statusColor[status]
}
// ✅ Do
function getStatusColor(status) {
return {
pending: 'blue',
approved: 'green',
rejected: 'yellow',
}[status]
}
With a more complex example:
// ❌ Don't
let sortBy
if (key === 'user.firstname') {
sortBy = 'name'
} else if (key === 'user.age') {
sortBy = 'age'
} else if (key === 'project.title') {
sortBy = 'project_title'
}
// ✅ Do
const sortBy =
{
'user.firstname': 'name',
'user.age': 'age',
'project.title': 'project_title',
}[key] || sortBy
A classic case: if the condition value is the same as the parameter, you can just use it directly.
// ❌ Don't
doSomething(false)
if (shouldIDoIt) {
doSomething(true)
}
// ✅ Do
doSomething(shouldIDoIt)
Conclusion
Remember KISS, keep it simple, stupid.
That’s it for this blog post, hope you found it helpful! Happy coding!