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.
npm install modern-fns// 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 withsideEffects: false, soimport { chunk }costs 0.20 kB gzipped. - No
@typespackage. Lodash ships no types of its own; you install and version@types/lodashalongside it. modern-fns generates declarations from its own source. - Native ESM. Lodash's main package has no
exportsfield and nomoduleentry — the ESM build is the separatelodash-espackage. modern-fns ships both from one package. - Predictable mutation. In Lodash,
_.set,_.merge,_.pulland_.removemutate their argument while_.mapand_.sortBydo not. In modern-fns nothing mutates.
Migration mapping
Arrays
| Lodash | modern-fns |
|---|---|
_.chunk | chunk |
_.uniq | unique |
_.uniqBy | uniqueBy |
_.groupBy | groupBy |
_.keyBy | indexBy |
_.partition | partition |
_.sortBy | sortBy |
_.orderBy | orderBy |
_.flatten / _.flattenDeep | flatten / flattenDeep |
_.compact | compact |
_.difference / _.intersection / _.union | difference / intersection / union |
_.zip / _.unzip | zip / unzip |
_.head / _.last | first / last |
_.take / _.takeRight / _.drop / _.dropRight | same names |
_.range | range |
Objects
| Lodash | modern-fns | Note |
|---|---|---|
_.get | get | |
_.set | set | Lodash mutates; modern-fns returns a new object. |
_.has / _.unset | has / unset | unset is immutable here |
_.pick / _.omit | pick / omit | |
_.cloneDeep | deepClone | |
_.merge | deepMerge | Lodash mutates its first argument; deepMerge returns a new object. |
_.mapValues / _.mapKeys | mapValues / mapKeys | |
_.isEqual | isEqual | |
_.isEmpty | isEmpty |
Strings, numbers and types
| Lodash | modern-fns |
|---|---|
_.capitalize / _.upperFirst | capitalize |
_.camelCase / _.kebabCase / _.snakeCase | same names |
_.startCase | capitalizeWords |
_.truncate | truncate |
_.escape / _.unescape | escapeHtml / unescapeHtml |
_.clamp, _.round, _.floor, _.ceil, _.random | same names in number |
_.toNumber | toNumber — also parses "₹1,299.50" |
_.isNil, _.isString, _.isNumber, _.isBoolean, _.isArray, _.isFunction | same names in value |
_.isPlainObject | isObject |
_.defaultTo | defaultTo |
Functions and collections
| Lodash | modern-fns |
|---|---|
_.debounce / _.throttle | debounce / throttle |
_.memoize / _.once | memoize / once |
_.identity / _.noop | identity / noop |
_.flow / _.flowRight | pipe / compose |
_.forEach, _.map, _.filter, _.find, _.some, _.every, _.size | collection 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:
// 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.