Written by Kapeel Kokane✏️
Imagine this scenario – you’re planning to start a new project. Regardless of whether it’s a frontend project or a backend repo, what’s the first thing you do?
Well, first you look for a starter kit or a boilerplate that can help you get started quickly instead of doing all the heavy lifting. For our example, let’s say you are starting a new frontend project.
In that case, you use Next.js because who uses create-react-app anymore? Not even the official React docs recommend it. What next? You install Tailwind CSS right away, because who wants to write CSS from scratch? Then comes state management. You can use Redux, MobX, Recoil, or even the new kid on the block, Zustand. I would do the same thing.
But what do we do next? We usually install a utility package that can help us with some common tasks. Who wants to write their mapping, sorting functions, debounce utilities, or even their deep clone methods?
But which utility package should we use? There are so many options: Lodash, Underscore, Ramda, etc. The two most popular ones are Lodash and Underscore, which is what we’ll explore today. We’ll compare their functionalities and explore whether these packages are even necessary these days.
Comparing Lodash and Underscore
Underscore was created by Jeremy Ashkenas (the creator of Backbone.js) in 2009 to provide a set of utility functions that JavaScript lacked at the time. It was also created to work with Backbone.js, but it slowly became a favorite among developers who needed utility functions that they could just call and get stuff done with without having to worry about the inner implementations and browser compatibility.
Lodash was created by John-David Dalton in 2012 as a fork of Underscore. It was created to provide a more consistent API and better performance. It also provided some additional utilities that are not a part of Underscore.
Bundle sizes
If we compare the npm repositories for both packages, we can see that Lodash has a larger bundle size (1.41 MB) compared to Underscore (906kB). This means that when used in an npm project, Lodash would take an additional 500 kilobytes of network bandwidth while installing the node modules. But, with that additional size, we get some extra features. Let’s talk about them next.
Functionalities
Lodash provides some additional capabilities when compared to Underscore, including:
-
_.clone
-- Used for deep cloning objects -
_.merge
-- Can be used to merge two objects with common keys -
_.set
-- Sets a value for any path we want
Apart from that, Lodash also provides some additional string utilities like [_.kebabCase](https://lodash.com/docs/4.17.15#kebabCase)
and [_.camelCase](https://lodash.com/docs/4.17.15#camelCase)
, that convert any string supplied to the particular case styles. There is also a [_.capitalize](https://lodash.com/docs/4.17.15#capitalize)
method that capitalizes the first letter of any string.
Here are a few examples using these utilities that are currently not possible to achieve with Underscore:
// _.clone example
const user = {
name: 'John Doe',
age: 30,
email: 'test@gmail.com',
address: {
city: 'New York',
country: {
code: 'US',
name: 'United States'
}
}
};
const clonedUser = _.clone(user);
// _.set example
const user = {
name: 'John Doe',
age: 30,
};
_.set(user, 'address.city', 'New York');
_.set(user, 'address.country.code', 'US');
_.kebabCase('Hello World'); // hello-world
_.camelCase('Hello World'); // helloWorld
_.capitalize('hello world'); // Hello world
We’ll look at the merge example later in the article when we look at vanilla JS alternatives of common utility methods.
Popularity
If we look at the npm downloads, Lodash has more downloads at 70.3 million per week compared to Underscore, which has far less, at 14.5 million per week. This makes sense because once Lodash was available, people chose the "more recent" version of the utility package compared to the older one, which automatically became "stale.”
Do we even need utility packages?
Now, let’s look at the main use case for which these libraries were initially created – utility functions. In the past, the JavaScript spec lacked some basic functionality. And even if it did introduce new capabilities by adding them to the spec, they were not implemented by all browsers. Let’s look at some of them.
Filtering an array based on a condition
The Array.prototype.filter
method was introduced in ES5. It creates a new array with all elements that pass the test implemented by the provided function. However, it only became generally available in all major browsers in 2015. Before that, if you wanted to write functional code to filter an array, you would have to use the utility methods from Underscore or Lodash like so:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evenNumbers = _.filter(numbers, (number) => number % 2 === 0);
But after ES2015, it can easily be implemented in vanilla JavaScript like so:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const evenNumbers = numbers.filter((number) => number % 2 === 0);
Reducing an array down to a single result
The same case happened with the Array.prototype.reduce
method. Before 2015, if you wanted to write functional code to reduce an array, you would have to use the utility methods from Underscore or Lodash like so:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const sum = _.reduce(numbers, (acc, number) => acc + number, 0);
After ES2015, it can easily be implemented in vanilla JavaScript:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const sum = numbers.reduce((acc, number) => acc + number, 0);
Performing an operation on every array element
Let’s now look into the Array.prototype.forEach
method. If we wanted to perform an operation on each element of an array, we would have to use the for
loop or the utility methods from Underscore or Lodash like so:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
_.forEach(numbers, (number) => console.log(number));
But, after ES2015, it can be implemented in vanilla JavaScript like this:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
numbers.forEach((number) => console.log(number));
Checking whether a variable is an array
If we wanted to check if a variable is an array, we would have to use the utility methods from Underscore or Lodash like so:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const isNumbersArray = _.isArray(numbers);
With ES5, we can easily implement it in vanilla JavaScript like so:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const isNumbersArray = Array.isArray(numbers);
Picking properties from an object
Now let’s look at some object operations. If we wanted to pick some keys from an object, we would have to use the utility methods from Underscore or Lodash like so:
const user = {
name: 'John Doe',
age: 30,
email: '
};
const pickedUser = _.pick(user, ['name', 'age']);
This creates a new object by using the keys from the user object, which, in this case, would be name
and age
. But, from ES5 onwards, we can easily implement it in vanilla JavaScript like so:
const user = {
name: 'John Doe',
age: 30,
email: 'test@gmail.com'
};
const pickedUser = Object.fromEntries(Object.entries(user).filter(([key]) => ['name', 'age'].includes(key)));
Merging two objects
If we wanted to merge two objects so that the new object would have the superset of keys from the two objects, we would have to use the utility methods from Lodash like so:
const user = {
name: 'John Doe',
age: 30,
email: 'test@gmail.com'
}
const newUser = {
age: 31
}
const mergedUser = _.merge({}, user, newUser);
Note that Underscore doesn’t have this functionality, but in ES5, we can easily implement it in vanilla JavaScript using the spread operator like so:
const mergedUser = {...user, ...newUser};
We could also use the [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
method like so:
const mergedUser = Object.assign({}, user, newUser);
Deep cloning an object
If we wanted to deep clone an object, we would have to use the utility methods from Underscore or Lodash like so:
const user = {
name: 'John Doe',
age: 30,
email: 'test@gmail.com'
};
const clonedUser = _.cloneDeep(user);
But, modern JavaScript has JSON utilities like parse
and stringify
can be used to deep clone an object like so:
const clonedUser = JSON.parse(JSON.stringify(user));
Now, let's move on to some more complex use cases.
Throttling a function call
If we wanted to throttle a function, which is to limit the number of function executions to just one in a given timeframe, we would have to use the utility methods from Underscore or Lodash like so:
const throttledFunction = _.throttle(() => console.log('Throttled function'), 1000);
In this case, the function would get executed only once in a second, i.e., 1000 milliseconds.
The same can be implemented in vanilla JavaScript like so:
function throttle(func, delay) {
let lastTime = 0;
return function() {
const now = Date.now();
if (now - lastTime >= delay) {
lastTime = now;
func.apply(this, arguments);
}
};
}
const throttledFunction = throttle(() => console.log('Throttled function'), 1000);
Debouncing a function call
If we wanted to debounce a function, which is to stop the execution of a function for a particular time since the last function execution, we would have to use the utility methods from Underscore or Lodash like so:
const debouncedFunction = _.debounce(() => console.log('Debounced function'), 1000);
But the same can be implemented in vanilla JavaScript like so:
function debounce(func, delay) {
let timeoutId;
return function() {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, arguments);
}, delay);
};
}
const debouncedFunction = debounce(() => console.log('Debounced function'), 1000);
Conclusion
So, do we even need utility libraries like Lodash or Underscore? Not in the year 2024.
With the introduction of ES2015 and later versions of JavaScript, the language has come a long way. It has introduced a lot of utility methods that can be used to perform common tasks. And even if you need some additional functionalities, you can easily implement them in vanilla JavaScript using the latest features of the language as we did with the spread operator in this article.
So, the next time you find yourself reaching out for Lodash or Underscore while setting up a new project, think again. Save yourself some network bandwidth (from the npm module) and write your own utility functions. Not only will it help you understand the language better but it’ll also make you a better developer.
Are you adding new JS libraries to build new features or improve performance? What if they’re doing the opposite?
There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.
LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.
LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.
Build confidently — start monitoring for free.
Top comments (0)