lodash vs immutable vs underscore vs ramda
Functional and Immutable Data Utilities in JavaScript
lodashimmutableunderscoreramdaSimilar Packages:
Functional and Immutable Data Utilities in JavaScript

immutable, lodash, ramda, and underscore are widely used JavaScript utility libraries that help developers work with data more effectively. immutable provides persistent, immutable data structures like Map, List, and Set that enable efficient updates without mutating original values. lodash offers a comprehensive suite of utility functions for common programming tasks, including array manipulation, object operations, and functional helpers, with a focus on performance and broad compatibility. ramda emphasizes a functional programming style with automatic currying, immutable operations, and a data-last argument order that supports point-free composition. underscore is an early utility library that provides foundational functional helpers and was influential in shaping JavaScript’s utility landscape, though it has seen reduced adoption in modern projects.

Npm Package Weekly Downloads Trend
3 Years
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
lodash103,089,19561,6121.41 MB111a month agoMIT
immutable31,707,09033,096709 kB1214 months agoMIT
underscore18,312,36127,369906 kB522 years agoMIT
ramda12,663,49924,1021.2 MB1474 months agoMIT

Functional and Immutable Data Utilities: immutable vs lodash vs ramda vs underscore

When working with data in JavaScript—especially in complex frontend applications—you often need more than the built-in Array and Object methods. The libraries immutable, lodash, ramda, and underscore each offer different approaches to handling data transformations, immutability, and functional composition. Let’s compare them through real-world engineering lenses.

🧱 Core Philosophy: Mutation vs Immutability vs Functional Purity

immutable enforces true immutability using persistent data structures. Every update returns a new instance, but shares unchanged parts under the hood for efficiency.

import { Map } from 'immutable';

const original = Map({ name: 'Alice', age: 30 });
const updated = original.set('age', 31);

console.log(original.get('age')); // 30
console.log(updated.get('age'));  // 31
console.log(original === updated); // false — but memory-efficient

lodash provides mutable and immutable helpers, but defaults to returning new values (e.g., _.cloneDeep, _.map). It doesn’t enforce immutability but makes it easy to achieve.

import _ from 'lodash';

const original = { name: 'Alice', age: 30 };
const updated = _.assign({}, original, { age: 31 });

// Or use _.set with a clone
const updated2 = _.set(_.cloneDeep(original), 'age', 31);

ramda assumes immutability by default and never mutates inputs. All functions return new values, and it encourages pure, side-effect-free transformations.

import * as R from 'ramda';

const original = { name: 'Alice', age: 30 };
const updated = R.assoc('age', 31, original);

// Original unchanged; updated is a new object

underscore behaves similarly to early lodash—it offers utility functions that typically return new values but doesn’t enforce immutability or provide deep immutable structures.

import _ from 'underscore';

const original = { name: 'Alice', age: 30 };
const updated = _.extend({}, original, { age: 31 });

⚠️ Note: As of 2024, underscore is in maintenance mode. The official npm page states it’s “no longer under active development.” Avoid it in new projects.

🔁 Data Transformation: Chaining and Composition

lodash supports method chaining via _.chain() or the _(value) wrapper, which is intuitive for imperative-style pipelines.

import _ from 'lodash';

const result = _(users)
  .filter(u => u.active)
  .map(u => u.name)
  .take(5)
  .value();

ramda favors function composition over chaining. Its data-last signature enables natural point-free style with R.compose or R.pipe.

import * as R from 'ramda';

const getActiveNames = R.pipe(
  R.filter(R.prop('active')),
  R.map(R.prop('name')),
  R.take(5)
);

const result = getActiveNames(users);

immutable uses method chaining on its own data types, which is fluent but locks you into its ecosystem.

import { List } from 'immutable';

const usersList = List(users);
const result = usersList
  .filter(u => u.active)
  .map(u => u.name)
  .take(5);

underscore supports chaining too, but without tree-shaking or modern optimizations.

import _ from 'underscore';

const result = _.chain(users)
  .filter(u => u.active)
  .map(u => u.name)
  .first(5)
  .value();

📦 Interoperability with Plain JavaScript

immutable requires conversion to/from plain JS objects using .toJS() and fromJS(), which can hurt performance if overused.

import { fromJS } from 'immutable';

const jsObj = { a: 1 };
const imMap = fromJS(jsObj);
const backToJs = imMap.toJS(); // expensive for large structures

lodash, ramda, and underscore all work directly with standard JavaScript types—no conversion needed.

// All of these work natively with { a: 1 }
_.get(obj, 'a');
R.prop('a', obj);
_.property('a')(obj); // underscore

This makes lodash and ramda easier to integrate incrementally into existing codebases.

🧩 Function Arity and Currying

ramda automatically curries all functions, enabling partial application without extra syntax.

import * as R from 'ramda';

const pickName = R.pick(['name']);
const names = R.map(pickName, users); // or compose further

lodash provides explicit currying via _.curry() but doesn’t curry by default.

import _ from 'lodash';

const add = _.curry((a, b) => a + b);
const add5 = add(5);

immutable and underscore do not support currying.

🛠️ Real-World Trade-offs

When You Need Deep Immutability & Performance at Scale

Use immutable if:

  • Your app relies on reference equality checks (e.g., React.memo, Redux selectors).
  • You frequently update deeply nested structures.
  • You’re okay with managing a separate type system.

Avoid if you need seamless JSON interoperability or are sensitive to bundle size from non-tree-shakable APIs.

When You Want a Swiss Army Knife for Everyday Tasks

Use lodash if:

  • You need reliable, well-documented utilities for arrays, objects, strings, etc.
  • Your team prefers readability over strict functional purity.
  • You want fine-grained imports (import map from 'lodash/map') for tree-shaking.

It’s the safest choice for mixed-paradigm codebases.

When You’re All-In on Functional Programming

Use ramda if:

  • Your team embraces FP concepts like composition, purity, and point-free style.
  • You want to build reusable transformation pipelines.
  • You prefer declarative over imperative code.

Be cautious if your team isn’t familiar with FP—it can raise the cognitive barrier.

Legacy Considerations

underscore should not be used in new projects. Its functionality is largely superseded by lodash, which began as a fork of underscore and has since evolved with better performance, modularity, and modern JavaScript support.

📊 Summary Table

Featureimmutablelodashramdaunderscore
Immutability✅ Enforced (custom types)✅ Optional (plain JS)✅ Default (plain JS)⚠️ Not enforced
Currying✅ Manual✅ Automatic
Chaining✅ (on Immutable types)✅ (via pipe/compose)
Plain JS Interop❌ (requires conversion)
Modern Ecosystem Fit⚠️ Niche (Redux era)✅ Excellent✅ Strong (FP teams)❌ Deprecated/maintenance
Tree-shaking Support⚠️ Limited✅ Yes✅ Yes❌ No

💡 Final Guidance

  • For most modern apps: Start with lodash for general-purpose utilities. It’s pragmatic, fast, and widely understood.
  • For functional-heavy codebases: Choose ramda to unlock expressive, composable data flows.
  • For strict immutability with structural sharing: Use immutable only if you’ve measured performance gains and accept the ecosystem cost.
  • Avoid underscore in new work—it’s a relic of JavaScript’s past.

All four libraries solve real problems, but only three remain relevant today. Choose based on your team’s style, performance requirements, and long-term maintainability—not just feature checklists.

How to Choose: lodash vs immutable vs underscore vs ramda
  • lodash:

    Choose lodash when you need a battle-tested, highly optimized utility belt for everyday tasks across arrays, objects, strings, and functions. Its modular design allows tree-shaking, and its consistent API works well in both browser and Node.js environments. It’s especially valuable in large codebases requiring robust, readable utilities without enforcing a specific programming paradigm.

  • immutable:

    Choose immutable when you need guaranteed immutability with persistent data structures that support structural sharing for performance. It's ideal for applications using Redux or other state management systems where deep immutability and change detection are critical. However, be aware that it introduces a learning curve due to its custom types and requires interop utilities when interfacing with plain JavaScript objects.

  • underscore:

    Choose underscore only for maintaining legacy codebases that already depend on it. While it pioneered many utility patterns in JavaScript, it lacks modern features like tree-shaking support and hasn't kept pace with current ecosystem expectations. For new projects, prefer lodash or ramda depending on your architectural needs.

  • ramda:

    Choose ramda when you're building a codebase that embraces functional programming principles like pure functions, immutability, and composition. Its automatic currying and data-last parameter order make it easy to create reusable, composable pipelines. It’s best suited for teams comfortable with FP concepts and looking to reduce boilerplate in data transformation logic.

README for lodash

lodash v4.17.23

The Lodash library exported as Node.js modules.

Installation

Using npm:

$ npm i -g npm
$ npm i --save lodash

In Node.js:

// Load the full build.
var _ = require('lodash');
// Load the core build.
var _ = require('lodash/core');
// Load the FP build for immutable auto-curried iteratee-first data-last methods.
var fp = require('lodash/fp');

// Load method categories.
var array = require('lodash/array');
var object = require('lodash/fp/object');

// Cherry-pick methods for smaller browserify/rollup/webpack bundles.
var at = require('lodash/at');
var curryN = require('lodash/fp/curryN');

See the package source for more details.

Note:
Install n_ for Lodash use in the Node.js < 6 REPL.

Support

Tested in Chrome 74-75, Firefox 66-67, IE 11, Edge 18, Safari 11-12, & Node.js 8-12.
Automated browser & CI test runs are available.