Skip to content

A modern Lodash alternative ​

modern-fns covers the Lodash methods most applications actually use, with native ES modules, types generated from the source, and immutability as a rule rather than a per-method detail.

bash
npm install modern-fns
ts
// Lodash
import _ from 'lodash';
_.groupBy(users, 'role');

// modern-fns
import { groupBy } from 'modern-fns';
groupBy(users, 'role');

Why people migrate ​

  • Bundle size. import _ from 'lodash' pulls the whole library. modern-fns is one function per file with sideEffects: false, so import { chunk } costs 0.20 kB gzipped.
  • No @types package. Lodash ships no types of its own; you install and version @types/lodash alongside it. modern-fns generates declarations from its own source.
  • Native ESM. Lodash's main package has no exports field and no module entry — the ESM build is the separate lodash-es package. modern-fns ships both from one package.
  • Predictable mutation. In Lodash, _.set, _.merge, _.pull and _.remove mutate their argument while _.map and _.sortBy do not. In modern-fns nothing mutates.

Migration mapping ​

Arrays ​

Lodashmodern-fns
_.chunkchunk
_.uniqunique
_.uniqByuniqueBy
_.groupBygroupBy
_.keyByindexBy
_.partitionpartition
_.sortBysortBy
_.orderByorderBy
_.flatten / _.flattenDeepflatten / flattenDeep
_.compactcompact
_.difference / _.intersection / _.uniondifference / intersection / union
_.zip / _.unzipzip / unzip
_.head / _.lastfirst / last
_.take / _.takeRight / _.drop / _.dropRightsame names
_.rangerange

Objects ​

Lodashmodern-fnsNote
_.getget
_.setsetLodash mutates; modern-fns returns a new object.
_.has / _.unsethas / unsetunset is immutable here
_.pick / _.omitpick / omit
_.cloneDeepdeepClone
_.mergedeepMergeLodash mutates its first argument; deepMerge returns a new object.
_.mapValues / _.mapKeysmapValues / mapKeys
_.isEqualisEqual
_.isEmptyisEmpty

Strings, numbers and types ​

Lodashmodern-fns
_.capitalize / _.upperFirstcapitalize
_.camelCase / _.kebabCase / _.snakeCasesame names
_.startCasecapitalizeWords
_.truncatetruncate
_.escape / _.unescapeescapeHtml / unescapeHtml
_.clamp, _.round, _.floor, _.ceil, _.randomsame names in number
_.toNumbertoNumber — also parses "₹1,299.50"
_.isNil, _.isString, _.isNumber, _.isBoolean, _.isArray, _.isFunctionsame names in value
_.isPlainObjectisObject
_.defaultTodefaultTo

Functions and collections ​

Lodashmodern-fns
_.debounce / _.throttledebounce / throttle
_.memoize / _.oncememoize / once
_.identity / _.noopidentity / noop
_.flow / _.flowRightpipe / compose
_.forEach, _.map, _.filter, _.find, _.some, _.every, _.sizecollection module — same names, each instead of forEach

Behavioural differences to watch for ​

set and deepMerge do not mutate. This is the one migration change that bites silently:

ts
// Lodash — mutates in place, return value often ignored
_.set(state, 'user.city', 'Kochi');

// modern-fns — you must use the return value
state = set(state, 'user.city', 'Kochi');

isEmpty(0) is false. Lodash returns true for numbers because they have no length. modern-fns treats 0 and false as values, not absences.

debounce has no maxWait. It supports leading, trailing, cancel(), flush() and pending(). If you rely on maxWait, keep Lodash for that call site.

Selectors are keyof T or a function. Lodash's _.matches / _.property shorthands (_.filter(users, { active: true })) are not supported — write the predicate out.

What modern-fns does not have ​

No equivalent exists for _.template, _.curry, _.partial, the lodash/fp module, _.countBy, _.sumBy, _.maxBy, _.minBy, _.times, _.sample, _.shuffle, _.deburr, _.zipObject, _.fromPairs or _.toPairs.

Several of those are one-liners in modern JavaScript — Object.fromEntries, Object.entries, Array.from({ length: n }), Math.max(...values.map(fn)) — which is exactly why they were left out. See the design philosophy.

The two libraries coexist happily in one project, so a partial migration is a perfectly reasonable outcome. Read the detailed comparison before deciding.