Migrate: lodash

filter

Use isTruthy as the predicate.

// Lodash
compact(DATA);

// Remeda
filter(DATA, isTruthy);
difference
View source on GitHub
difference
  • Remeda’s difference treats the inputs as multisets/bags which respect item duplication, whereas in Lodash all matching values are filtered out. This means Remeda removes only one copy of each matching item, while Lodash removes all copies.

  • When the input arrays might contain duplicates and you want to filter all values use a composition of filter, isNot, and isIncludedIn to replicate the Lodash semantics.

  • Lodash’s difference accepts multiple values arrays as separate arguments and flattens them, while Remeda’s difference takes a single values array. When migrating these arrays need to be flattened or spread into a single array manually.

  • Lodash supports calling difference trivially, with no exclusion array at all which results in a shallow clone of the input array. In Remeda the exclusion array is required.

  • Lodash accepts null and undefined values for the array (and treats them as an empty array). In Remeda this nullish value needs to be handled explicitly either by skipping the call to difference, or by coalescing the input to an empty array.

Without duplicate values (unique)

// Lodash
_.difference(uniqueData, uniqueValues);

// Remeda
difference(uniqueData, uniqueValues);

With duplicate values

// Lodash
_.difference(dataWithDuplicates, uniqueValues);
_.difference(uniqueData, valuesWithDuplicates);
_.difference(dataWithDuplicates, valuesWithDuplicates);

// Remeda
filter(dataWithDuplicates, isNot(isIncludedIn(uniqueValues)));
filter(uniqueData, isNot(isIncludedIn(valuesWithDuplicates)));
filter(dataWithDuplicates, isNot(isIncludedIn(valuesWithDuplicates)));

// valuesWithDuplicates doesn't need to be deduped when used inside
// `isIncludedIn`, but can be for efficiency if needed via `unique`
filter(dataWithDuplicates, isNot(isIncludedIn(unique(valuesWithDuplicates))));

Multiple exclusion arrays

// Lodash
_.difference(DATA, a, b, c);

// Remeda
difference(DATA, [...a, ...b, ...c]);

Missing 2nd (exclusions) parameter

// Lodash
_.difference(DATA);

// Remeda
difference(DATA, []);

// Or directly via Native JS:
[...DATA];

Nullish inputs

// Lodash
_.difference(DATA, values);

// Remeda
isNonNullish(DATA) ? difference(DATA, values) : [];

// Or
difference(DATA ?? [], values);
differenceBy
View source on GitHub
filter

Not provided by Remeda.

  • Use a composition based on filter, isNot isIncludedIn, and map to recreate the logic for differenceBy.

  • Additionally, when the iteratee parameter is defined as a property name, use prop as the iteratee function instead.

  • Lodash accepts null and undefined values for the array (and treats them as an empty array). In Remeda this nullish value needs to be handled explicitly either by skipping the call to differenceBy, or by coalescing the input to an empty array.

  • When the iteratee parameter is not provided to the Lodash differenceBy function (or is provided as undefined) it behaves like a call to difference.

Function iteratee

// Lodash
_.differenceBy(DATA, values, iteratee);

// Remeda
filter(DATA, piped(iteratee, isNot(isIncludedIn(map(values, iteratee)))));

// the mapped values don't need to be deduped when used inside `isIncludedIn`,
// but could be for efficiency if needed via `unique`
filter(
  DATA,
  piped(iteratee, isNot(isIncludedIn(unique(map(values, iteratee))))),
);

Property iteratee

// Lodash
_.differenceBy(DATA, values, "x");

// Remeda
filter(DATA, piped(prop("x"), isNot(isIncludedIn(map(values, prop("x"))))));

// the mapped values don't need to be deduped when used inside `isIncludedIn`,
// but could be for efficiency if needed via `unique`
filter(
  DATA,
  piped(prop("x"), isNot(isIncludedIn(unique(map(values, prop("x")))))),
);

Multiple exclusion arrays

// Lodash
_.differenceBy(DATA, a, b, c, iteratee);

// Remeda
filter(
  DATA,
  piped(iteratee, isNot(isIncludedIn(map([...a, ...b, ...c], iteratee)))),
);

// the mapped values don't need to be deduped when used inside `isIncludedIn`,
// but could be for efficiency if needed via `unique`
filter(
  DATA,
  piped(
    iteratee,
    isNot(isIncludedIn(unique(map([...a, ...b, ...c], iteratee)))),
  ),
);

Nullish inputs

// Lodash
_.differenceBy(DATA, values, iteratee);

// Remeda
isNonNullish(DATA)
  ? filter(DATA, piped(iteratee, isNot(isIncludedIn(map(values, iteratee)))))
  : [];

// Or
filter(DATA ?? [], piped(iteratee, isNot(isIncludedIn(map(values, iteratee)))));

Missing iteratee function

// Lodash
_.differenceBy(DATA, values);

// Convert to Lodash's `difference` and then refer to the `difference` migration
// docs
_.difference(DATA, values);
differenceWith
View source on GitHub
differenceWith
  • Lodash’s differenceWith accepts a variadic list of values arrays which are then effectively flattened, but Remeda only accepts a single values array; Multiple arrays need to be spread manually instead.

  • Lodash accepts null and undefined values for the array (and treats them as an empty array). In Remeda this nullish value needs to be handled explicitly either by skipping the call to differenceWith, or by coalescing the input to an empty array.

  • When the comparator parameter is not provided to the Lodash differenceWith function (or is provided as undefined) it behaves like a call to difference.

Single exclusion array

// Lodash
_.differenceWith(DATA, values, comparator);

// Remeda
differenceWith(DATA, values, comparator);

Multiple exclusion arrays

// Lodash
_.differenceWith(DATA, a, b, c, comparator);

// Remeda
differenceWith(DATA, [...a, ...b, ...c], comparator);

Nullish inputs

// Lodash
_.differenceWith(DATA, values, comparator);

// Remeda
isNonNullish(DATA) ? differenceWith(DATA, values, comparator) : [];

// Or
differenceWith(DATA ?? [], values, comparator);

Missing iteratee function

// Lodash
_.differenceWith(DATA, values);

// Convert to Lodash's `difference` and then refer to the `difference` migration
// docs
_.difference(DATA, values);
flat

Either use with no params, or with a depth of 1.

// Lodash
flatten(DATA);

// Remeda
flat(DATA);
flat(DATA, 1 /* depth */);
flattenDeep
View source on GitHub
flat

Unlike flatten in Lodash, the Remeda flat function is always bound by the depth param. To replicate the Lodash behavior use a high, const value. Infinity and Number.MAX_INTEGER are not consts and would result in inefficient typing.

// Lodash
flattenDeep(DATA);

// Remeda
flat(DATA, 100); // ✅
flat(DATA, Infinity); // ❌
intersection
View source on GitHub
intersection

In Remeda intersection treats the inputs as multisets/bags which respect item duplication, whereas in Lodash the result is always de-dupped. This only matters for cases where both arrays might have duplicate values. To achieve the same output pass the result to unique.

No duplicates

// Lodash
intersection([2, 1], [2, 3]);

// Remeda
intersection([2, 1], [2, 3]);

With duplicates

// Lodash
intersection([1, 1], [1, 1]);

// Remeda
unique(intersection([1, 1], [1, 1]));

Not provided by Remeda.

  • For index 0 use first.
  • For index -1 use last.
  • For arbitrary non-negative indices use the native JS data[n].
  • Or use Array.prototype.at for any index.
// Lodash
nth(DATA);
nth(DATA, 0);

// Remeda
first(DATA);

// Lodash
nth(DATA, 1);

// Native
DATA[1];
DATA.at(1);

// Lodash
nth(DATA, -1);

// Remeda
last(DATA);

// Lodash
nth(DATA, -2);

// Native
DATA.at(-2);

Not provided by Remeda.

CAUTION: pull mutates the input array in-place! All functions in Remeda are pure and never mutate the input. These mutations might introduce side-effects and implicit dependencies in your codebase that need to be handled before migrating to Remeda!

  • pull is equivalent to Lodash’s difference function. To migrate to Remeda first migrate calls to Lodash _.difference and then use the migration docs for difference to complete the migration. IMPORTANT: The Remeda difference function isn’t a drop-in replacement for the Lodash _.difference function, we do not recommend migrating directly from pull to Remeda’s difference.

  • pull takes a variadic array of items to remove; difference take an explicit array instead. You will need to wrap your items in an array when migrating.

  • If the mutability of the input array is desired, make sure the variable is assignable (e.g., using let instead of const), and assign back the result of difference back to it. Note that if the input array is part of an object or nested array, you will need to manually reconstruct this outer containing object with the updated array manually.

  • If mutability wasn’t desired, and instead the input was cloned (shallowly) before calling pull, that cloning is now redundant.

Exclusion Items

// pull
_.pull(DATA, a, b, c, ...additional);

// difference
_.difference(DATA, [a, b, c, ...additional]);

In-place mutation

// pull
const DATA = ["a", "b", "c", "d"];
_.pull(DATA, ...values);

// difference
let DATA = ["a", "b", "c", "d"];
DATA = _.difference(DATA, values);

Non-mutating usage

// pull
const pulled = _.pull([...DATA], ...values);

// difference
const pulled = _.difference(DATA, values);

Nested inside an object

// pull
const DATA = { a: ["a", "b", "c"], b: "hello, world" };
_.pull(DATA.a, ...values);

// difference
let DATA = { a: ["a", "b", "c"], b: "hello, world" };
DATA = { ...DATA, a: _.difference(DATA.a, values) };

Not provided by Remeda.

CAUTION: pullAll mutates the input array in-place! All functions in Remeda are pure and never mutate the input. These mutations might introduce side-effects and implicit dependencies in your codebase that need to be handled before migrating to Remeda!

  • pullAll is equivalent to Lodash’s difference function. To migrate to Remeda first migrate calls to Lodash _.difference and then use the migration docs for difference to complete the migration. IMPORTANT: The Remeda difference function isn’t a drop-in replacement for the Lodash _.difference function, we do not recommend migrating directly from pullAll to Remeda’s difference.

  • If the mutability of the input array is desired, make sure the variable is assignable (e.g., using let instead of const), and assign back the result of difference back to it. Note that if the input array is part of an object or nested array, you will need to manually reconstruct this outer object with the updated array manually.

  • If mutability wasn’t desired, and instead the input was cloned (shallowly) before calling pullAll, that cloning is now redundant.

In-place mutation

// pullAll
const DATA = ["a", "b", "c", "d"];
_.pullAll(DATA, values);

// difference
let DATA = ["a", "b", "c", "d"];
DATA = _.difference(DATA, values);

Non-mutating usage

// pullAll
const pulled = _.pullAll([...DATA], values);

// difference
const pulled = _.difference(DATA, values);

Nested inside an object

// pullAll
const DATA = { a: ["a", "b", "c"], b: "hello, world" };
_.pullAll(DATA.a, values);

// difference
let DATA = { a: ["a", "b", "c"], b: "hello, world" };
DATA = { ...DATA, a: _.difference(DATA.a, values) };

Not provided by Remeda.

CAUTION: pullAllBy mutates the input array in-place! All functions in Remeda are pure and never mutate the input. These mutations might introduce side-effects and implicit dependencies in your codebase that need to be handled before migrating to Remeda!

  • pullAllBy is equivalent to Lodash’s differenceBy function. To migrate to Remeda first migrate calls to Lodash _.differenceBy and then use the migration docs for differenceBy to complete the migration.

  • If the mutability of the input array is desired, make sure the variable is assignable (e.g., using let instead of const), and assign back the result of differenceBy back to it. Note that if the input array is part of an object or nested array, you will need to manually reconstruct this outer object with the updated array manually.

  • If mutability wasn’t desired, and instead the input was cloned (shallowly) before calling pullAllBy, that cloning is now redundant.

In-place mutation

// pullAllBy
const DATA = [{ x: 1 }, { x: 2 }, { x: 3 }];
_.pullAllBy(DATA, values, iteratee);

// differenceBy
let DATA = [{ x: 1 }, { x: 2 }, { x: 3 }];
DATA = _.differenceBy(DATA, values, iteratee);

Non-mutating usage

// pullAllBy
const pulled = _.pullAllBy([...DATA], values, iteratee);

// differenceBy
const pulled = _.differenceBy(DATA, values, iteratee);

Nested inside an object

// pullAllBy
const DATA = { items: [{ x: 1 }, { x: 2 }], meta: "info" };
_.pullAllBy(DATA.items, values, iteratee);

// differenceBy
let DATA = { items: [{ x: 1 }, { x: 2 }], meta: "info" };
DATA = { ...DATA, items: _.differenceBy(DATA.items, values, iteratee) };
pullAllWith
View source on GitHub

Not provided by Remeda.

CAUTION: pullAllWith mutates the input array in-place! All functions in Remeda are pure and never mutate the input. These mutations might introduce side-effects and implicit dependencies in your codebase that need to be handled before migrating to Remeda!

  • pullAllWith is equivalent to Lodash’s differenceWith function. To migrate to Remeda first migrate calls to Lodash _.differenceWith and then use the migration docs for differenceWith to complete the migration. IMPORTANT: The Remeda differenceWith function isn’t a drop-in replacement for the Lodash _.differenceWith function, we do not recommend migrating directly from pullAllWith to Remeda’s differenceWith.

  • If the mutability of the input array is desired, make sure the variable is assignable (e.g., using let instead of const), and assign back the result of differenceWith back to it. Note that if the input array is part of an object or nested array, you will need to manually reconstruct this outer object with the updated array manually.

  • If mutability wasn’t desired, and instead the input was cloned (shallowly) before calling pullAllWith, that cloning is now redundant.

In-place mutation

// pullAllWith
const DATA = [{ x: 1 }, { x: 2 }, { x: 3 }];
_.pullAllWith(DATA, values, comparator);

// differenceWith
let DATA = [{ x: 1 }, { x: 2 }, { x: 3 }];
DATA = _.differenceWith(DATA, values, comparator);

Non-mutating usage

// pullAllWith
const pulled = _.pullAllWith([...DATA], values, comparator);

// differenceWith
const pulled = _.differenceWith(DATA, values, comparator);

Nested inside an object

// pullAllWith
const DATA = { items: [{ x: 1 }, { x: 2 }], meta: "info" };
_.pullAllWith(DATA.items, values, comparator);

// differenceWith
let DATA = { items: [{ x: 1 }, { x: 2 }], meta: "info" };
DATA = { ...DATA, items: _.differenceWith(DATA.items, values, comparator) };
reverse

Lodash mutates the input but in Remeda a new array is returned instead.

// Lodash
const DATA = [1, 2, 3];
reverse(DATA);

// Remeda
let DATA = [1, 2, 3];
DATA = reverse(DATA);
sortedUniq
View source on GitHub
unique

Not provided by Remeda.

There is no equivalent function in Remeda because both unique and sortedUniq run at O(n) time complexity. In space complexity (the amount of memory needed to compute them), unique is O(k) where k is the number of unique values in the array, while sortedUniq is O(1). In most real-life use cases, the two implementations would have near-identical performance. We highly recommend always using unique unless you know that the difference matters.

If you still need this algorithm, use filter with the following predicate:

// Lodash
sortedUniq(DATA);

// Remeda
unique(DATA);
filter(DATA, (item, index, array) => index === 0 || item !== array[index - 1]);

Not provided by Remeda.

  • without is equivalent to Lodash’s difference function. To migrate to Remeda first migrate calls to Lodash _.difference and then use the migration docs for difference to complete the migration. IMPORTANT: The Remeda difference function isn’t a drop-in replacement for the Lodash _.difference function, we do not recommend migrating directly from without to Remeda’s difference.

  • without takes variadic arguments; difference takes an explicit array. You will need to wrap your items in an array when migrating.

// without
_.without(DATA, a, b, c, ...additional);

// difference
_.difference(DATA, [a, b, c, ...additional]);

Not provided by Remeda.

Compose zip and fromEntries:

// Lodash
zipObject(keys, values);

// Remeda
fromEntries(zip(keys, values));
every
View source on GitHub
flatMapDeep
View source on GitHub

Not provided by Remeda.

This function could be recreated in Remeda via a composition of map, and flat with a large, const depth parameter.

// Lodash
flatMapDeep(DATA, mappingFunction);

// Remeda
flat(map(DATA, mappingFunction), 10 /* depth */);

// Or as a pipe
pipe(DATA, map(mappingFunction), flat(10 /* depth */));
flatMapDepth
View source on GitHub

Not provided by Remeda.

This function could be recreated in Remeda via map, flatMap, or flat (or a combination of them, depending on the parameters used).

// Lodash
flatMapDepth(DATA);

// Remeda
flat(DATA);
// Lodash
flatMapDepth(DATA, mappingFunction);

// Remeda
flatMap(DATA, mappingFunction);
// Lodash
flatMapDepth(DATA, mappingFunction, depth);

// Remeda
flat(map(DATA, mappingFunction), depth);

// Or as a pipe
pipe(DATA, map(mappingFunction), flat(depth));
forEach
View source on GitHub
forEach
  • In Lodash forEach first checks if the data argument is array-like (by looking for a length prop). In Remeda the function only accepts arrays and calling it with an object would result in a TypeScript error. Use forEachObj instead for those cases.
  • In Lodash forEach stops iterating early when the callback returns false. In Remeda the callback cannot return a value (it must return void) and this feature isn’t available. You can build a pipe that would replicate this logic.

Array

// Lodash
forEach([1, 2, 3], (item) => console.log(item));

// Remeda
forEach([1, 2, 3], (item) => {
  console.log(item);
});

Object

// Lodash
forEach({ a: 1, b: 2, c: 3 }, (value) => console.log(value));

// Remeda
forEachObj({ a: 1, b: 2, c: 3 }, (value) => {
  console.log(value);
});

Early exit

// Lodash
forEach([1, 2, 3], (item) => (item === 2 ? false : console.log(item)));

// Remeda
// ❌ You can't return a value in a forEach callback in Remeda:
forEach([1, 2, 3], (item) => (item === 2 ? false : console.log(item)));

// ✅ Instead you can rely on lazy evaluation of pipes:
pipe(
  [1, 2, 3],
  takeWhile((item) => item !== 2),
  forEach((item) => {
    console.log(item);
  }),
);
forEachRight
View source on GitHub

Not provided by Remeda.

  • This function could be replicated using a native JS for loop.
  • If the order of iteration doesn’t matter, you can use forEach for arrays, or forEachObj for objects.
  • The order in which object properties are iterated over is well-defined, but might not be the order you expect (MDN); nevertheless, entries maintains the same order, and could be used to replicate forEachRight.
  • If the native solution doesn’t suffice please open an issue at Remeda’s GitHub project so we can learn about your use-case.

Arrays

const DATA = [1, 2, 3];

// Lodash
forEachRight(DATA, (item) => (item === 2 ? false : console.log(item)));

// Native
for (const index = DATA.length - 1; index >= 0; index--) {
  const item = DATA[item]!;
  if (item === 2) {
    break;
  }

  console.log(item);
}

Objects

const DATA = { a: 1, b: 2, c: 3 };

// Lodash
forEachRight(DATA, (value) => (value === 2 ? false : console.log(value)));

// Native
const keys = Object.keys(DATA);
for (const index = keys.length - 1; index >= 0; index--) {
  const value = DATA[key]!;
  if (value === 2) {
    break;
  }

  console.log(value);
}
groupBy
View source on GitHub
groupBy
  • When using groupBy with a callback function use Remeda’s groupBy function.
  • When using groupBy on an array of objects and with a property name use Remeda’s groupByProp instead.
  • When using groupBy on an array of primitives (e.g., string) and with a property name use Remeda’s groupBy composed with prop.

Callback

const DATA = [6.1, 4.2, 6.3];

// Lodash
groupBy(DATA, Math.floor);

// Remeda
groupBy(DATA, Math.floor);

Property Name (on objects)

const DATA = [
  { type: "cat", name: "felix" },
  { type: "dog", toys: 3 },
];

// Lodash
groupBy(DATA, "type");

// Remeda
groupByProp(DATA, "type");

Property Name (on primitives)

const DATA = ["one", "two", "three"];

// Lodash
groupBy(DATA, "length");

// Remeda
groupBy(DATA, prop("length"));
includes
View source on GitHub
isIncludedIn
  • In Lodash includes also works on strings and objects; In Remeda only arrays are supported. For objects, use values first. For strings prefer the native JS String.prototype.includes.
  • The 3rd optional parameter fromIndex isn’t supported in Remeda. You can replicate it’s usage with drop (for non-negative indices), or takeLast (for non-positive indices).
  • The order of the parameters is flipped in Remeda, the item is first, and then the array.

Arrays

// Lodash
includes([1, 2, 3], 1);

// Remeda
isIncludedIn(1, [1, 2, 3]);

Objects

// Lodash
includes({ a: 1, b: 2 }, 1);

// Remeda
isIncludedIn(1, values({ a: 1, b: 2 }));

Strings

const DATA = "Hello, World!";

// Lodash
includes(DATA, "lo");

// Native
DATA.includes("lo");

fromIndex: non-negative

// Lodash
includes([1, 2, 3], 1, 2);

// Remeda
isIncludedIn(1, drop([1, 2, 3], 2));

fromIndex: non-positive

// Lodash
includes([1, 2, 3], 1, -2);

// Remeda
isIncludedIn(1, takeLast([1, 2, 3], 2));
invokeMap
View source on GitHub
map
  • map can be used instead of invokeMap by using a callback functions instead of (string) function names.
  • If you need to invoke functions dynamically via a provided function name, narrow the name first (so you can handle errors explicitly), and then use the name to access the object.

No arguments

// Lodash
invokeMap(
  [
    [5, 1, 7],
    [3, 2, 1],
  ],
  "sort",
);

// Remeda
map(
  [
    [5, 1, 7],
    [3, 2, 1],
  ],
  (array) => array.sort(),
);

With arguments

// Lodash
invokeMap([123, 456], String.prototype.split, "");

// Remeda
map([123, 456], (num) => num.toString().split(""));

Dynamic

const DATA = [
  { foo: (x: number) => x + 1, bar: (x: number) => x - 1 },
  { foo: (x: number) => x * 2, bar: (x: number) => x / 2 },
] as const;

// Lodash
invokeMap(DATA, funcName, 3);

// Remeda
if (funcName === "foo" || funcName === "bar") {
  map(DATA, ({ [funcName]: func }) => func(3));
} else {
  // Error!
}

// Or
map(DATA, (obj) => {
  if (funcName === "foo" || funcName === "bar") {
    return obj[funcName](3);
  } else {
    // Error
  }
});
orderBy
View source on GitHub
sortBy

Unlike in Lodash, in Remeda the sort criteria can only be a callback (and not a prop name), it is paired with the order modifier (“asc”/“desc”, if needed), and it’s provided as a variadic argument and not two array arguments.

const DATA = [
  { user: "fred", age: 48 },
  { user: "barney", age: 34 },
  { user: "fred", age: 40 },
  { user: "barney", age: 36 },
];

// Lodash
orderBy(DATA, ["user", "age"], ["asc", "desc"]);

// Remeda
sortBy(DATA, prop("user"), [prop("age"), "desc"]);
reduceRight
View source on GitHub
reduce

Use reverse on the input.

// Lodash
reduceRight(DATA, reducer, accumulator);

// Remeda
reduce(reverse(DATA), reducer, accumulator);

// Or with a pipe
pipe(DATA, reverse(), reduce(reducer, accumulator));
reject
View source on GitHub
filter

Wrap the callback with isNot.

// Lodash
reject(DATA, predicate);

// Remeda
filter(DATA, isNot(predicate));
sample
View source on GitHub
sample
  • The sample function in Remeda takes a required second parameter of the size of the sample. Use 1.
  • The Remeda function returns an array; to extract the value either use native destructuring or index access, or use only.
  • For objects, first call values.
  • If you are using sample in an extremely hot path where you need the most efficient implementation possible, prefer a native implementation instead.

Simple

// Lodash
sample(DATA);

// Remeda
only(sample(DATA, 1 /* size */));

// or with a pipe
pipe(DATA, sample(1 /* size */), only());

// Or with native accessors
sample(DATA, 1 /* size */)[0];
sample(DATA, 1 /* size */).at(0)!;
const [result] = sample(DATA, 1 /* size */);

Native

// Lodash
sample(DATA);

// Native
DATA[Math.floor(Math.random() * DATA.length)]!;
DATA.at(Math.floor(Math.random() * DATA.length))!;
sampleSize
View source on GitHub
sample
  • Unlike Lodash, In Remeda the sample function returns items in the same order they appear in the input. Use the shuffle function if you also need to randomize their order.
  • The second parameter to sampleSize in Lodash is optional, and defaults to 1 when not provided. In Remeda this is a required param.
// Lodash
sampleSize(DATA);

// Remeda
sample(DATA, 1);
// Lodash
sampleSize(DATA, 2);

// Remeda
// ❌ The result isn't shuffled!
sample(DATA, 2);

// ✅ Add `shuffle` after `sample` (if order is important).
shuffle(sample(DATA, 2));

// Or with a pipe
pipe(DATA, sample(2), shuffle());
sortBy
View source on GitHub
sortBy

The sortBy function in Remeda does not support defining the sort criteria using prop names, only callback functions. Also, the sort criteria is provided as a variadic argument of sort criteria, and not a single array argument.

const DATA = [
  { user: "fred", age: 48 },
  { user: "barney", age: 34 },
  { user: "fred", age: 40 },
  { user: "barney", age: 36 },
];

// Lodash
sortBy(DATA, [
  function (o) {
    return o.user;
  },
]);
sortBy(DATA, ["user", "age"]);

// Remeda
sortBy(DATA, prop("user"));
sortBy(DATA, prop("user"), prop("age"));
debounce
View source on GitHub
funnel
  • debounce can be implemented using the funnel utility. A reference implementation is provided below, and a more expanded version with inline documentation and tests is available in the test file funnel.lodash-debounce.test.ts.

  • A more complete reference implementation that also maintains Lodash’s capability to store the callback’s return value is available below, and in funnel.lodash-debounce-with-cached-value.test.ts.

  • These implementations can be copied as-is into your project, but might contain redundant parts which are not relevant for your specific use cases. By inlining only the parts you need you can take advantage of capabilities not available in Lodash.

Reference

function debounce<F extends (...args: any) => void>(
  func: F,
  wait = 0,
  {
    leading = false,
    trailing = true,
    maxWait,
  }: {
    readonly leading?: boolean;
    readonly trailing?: boolean;
    readonly maxWait?: number;
  } = {},
) {
  const {
    call,
    isIdle: _isIdle,
    ...rest
  } = funnel(
    () => {
      if (leading || trailing) {
        func();
      }
    },
    {
      minQuietPeriodMs: wait,
      ...(maxWait !== undefined && { maxBurstDurationMs: maxWait }),
      triggerAt: trailing ? (leading ? "both" : "end") : "start",
    },
  );
  return Object.assign(call, rest);
}

With call arguments

function debounce<F extends (...args: any) => void>(
  func: F,
  wait = 0,
  {
    leading = false,
    trailing = true,
    maxWait,
  }: {
    readonly leading?: boolean;
    readonly trailing?: boolean;
    readonly maxWait?: number;
  } = {},
) {
  const {
    call,
    isIdle: _isIdle,
    ...rest
  } = funnel(
    (args: Parameters<F>) => {
      if (leading || trailing) {
        func(...args);
      }
    },
    {
      reducer: (_, ...args: Parameters<F>) => args,
      minQuietPeriodMs: wait,
      ...(maxWait !== undefined && { maxBurstDurationMs: maxWait }),
      triggerAt: trailing ? (leading ? "both" : "end") : "start",
    },
  );
  return Object.assign(call, rest);
}

With cached value

function debounce<F extends (...args: any) => any>(
  func: F,
  wait = 0,
  {
    leading = false,
    trailing = true,
    maxWait,
  }: {
    readonly leading?: boolean;
    readonly trailing?: boolean;
    readonly maxWait?: number;
  } = {},
) {
  let cachedValue: ReturnType<F> | undefined;

  const { call, flush, cancel } = funnel(
    (args: Parameters<F>) => {
      if (leading || trailing) {
        cachedValue = func(...args) as ReturnType<F>;
      }
    },
    {
      reducer: (_, ...args: Parameters<F>) => args,
      minQuietPeriodMs: wait,
      ...(maxWait !== undefined && { maxBurstDurationMs: maxWait }),
      triggerAt: trailing ? (leading ? "both" : "end") : "start",
    },
  );
  return Object.assign(
    (...args: Parameters<F>) => {
      call(...args);
      return cachedValue;
    },
    {
      flush: () => {
        flush();
        return cachedValue;
      },

      cancel,
    },
  );
}
partial
View source on GitHub
partialBind

The placeholder argument _ is not supported. Some cases can be written as partialLastBind; otherwise, use an arrow function.

function greet(greeting, firstName, lastName) {
  return greeting + " " + firstName + " " + lastName;
}

// Lodash
_.partial(greet, _, "john", "doe");
_.partial(greet, "hi", _, "doe");

// Remeda
partialLastBind(greet, "john", "doe");
// (not supported)

// Native
(greeting) => greet(greeting, "john", "doe");
(firstName) => greet("hi", firstName, "doe");
partialRight
View source on GitHub
partialLastBind

The placeholder argument _ is not supported. Some cases can be written as partialBind; otherwise, use an arrow function.

function greet(greeting, firstName, lastName) {
  return greeting + " " + firstName + " " + lastName;
}

// Lodash
_.partialRight(greet, "hi", "john", _);
_.partialRight(greet, "hi", _, "doe");

// Remeda
partialBind(greet, ["hi", "john"]);
// (not supported)

// Native
(lastName) => greet("hi", "john", lastName);
(firstName) => greet("hi", firstName, "doe");
throttle
View source on GitHub
funnel
  • throttle can be implemented using the funnel utility. A reference implementation is provided below, and a more expanded version with inline documentation and tests is available in the test file funnel.lodash-throttle.test.ts.

  • A more complete reference implementation that also maintains Lodash’s capability to store the callback’s return value is available below, and in funnel.lodash-throttle-with-cached-value.test.ts.

  • These implementations can be copied as-is into your project, but might contain redundant parts which are not relevant for your specific use cases. By inlining only the parts you need you can take advantage of capabilities not available in Lodash.

Reference

function throttle<F extends (...args: any) => void>(
  func: F,
  wait = 0,
  {
    leading = true,
    trailing = true,
  }: { readonly leading?: boolean; readonly trailing?: boolean } = {},
) {
  const {
    call,
    isIdle: _isIdle,
    ...rest
  } = funnel(
    () => {
      if (leading || trailing) {
        func();
      }
    },
    {
      burstCoolDownMs: wait,
      maxBurstDurationMs: wait,
      invokedAt: trailing ? (leading ? "both" : "end") : "start",
    },
  );
  return Object.assign(call, rest);
}

With call arguments

function throttle<F extends (...args: any) => void>(
  func: F,
  wait = 0,
  {
    leading = true,
    trailing = true,
  }: { readonly leading?: boolean; readonly trailing?: boolean } = {},
) {
  const {
    call,
    isIdle: _isIdle,
    ...rest
  } = funnel(
    (args: Parameters<F>) => {
      if (leading || trailing) {
        func(...args);
      }
    },
    {
      reducer: (_, ...args: Parameters<F>) => args,
      minQuietPeriodMs: wait,
      maxBurstDurationMs: wait,
      triggerAt: trailing ? (leading ? "both" : "end") : "start",
    },
  );
  return Object.assign(call, rest);
}

With cached value

function throttle<F extends (...args: any) => any>(
  func: F,
  wait = 0,
  {
    leading = true,
    trailing = true,
  }: { readonly leading?: boolean; readonly trailing?: boolean } = {},
) {
  let cachedValue: ReturnType<F> | undefined;

  const { call, flush, cancel } = funnel(
    (args: Parameters<F>) => {
      if (leading || trailing) {
        cachedValue = func(...args) as ReturnType<F>;
      }
    },
    {
      reducer: (_, ...args: Parameters<F>) => args,
      minQuietPeriodMs: wait,
      maxBurstDurationMs: wait,
      triggerAt: trailing ? (leading ? "both" : "end") : "start",
    },
  );

  return Object.assign(
    (...args: Parameters<F>) => {
      call(...args);
      return cachedValue;
    },
    {
      flush: () => {
        flush();
        return cachedValue;
      },

      cancel,
    },
  );
}
firstBy

Use identity as the criteria and "desc" as the direction.

// Lodash
max(DATA);

// Remeda
firstBy(DATA, [identity(), "desc"]);
firstBy

Use "desc" as the direction.

// Lodash
maxBy(DATA, criteria);

// Remeda
firstBy(DATA, [criteria, "desc"]);
firstBy

Use identity as the criteria.

// Lodash
min(DATA);

// Remeda
firstBy(DATA, identity());
randomInteger
  • When Lodash’s random function is called with a non-integer param it returns any number, not just integers (e.g. 2.5)! Remeda’s randomInteger function always returns integers, effectively rounding the parameters to fit the range of possible integers.
  • If you want to generate any number in the range (and not just integers) see the solutions provided in “You don’t need Lodash”.
  • Lodash’s random parameters are optional. In Remeda all parameters are required.

Two integer params

// Lodash
random(1, 10);

// Remeda
randomInteger(1, 10);

Single integer param

// Lodash
random(10);

// Remeda
randomInteger(0, 10);

No params

// Lodash
random();

// Remeda
randomInteger(0, 1);

Not supported: floating-point numbers

random(1.5, 3.5);
random(1.5);
random(10, true);
random(5, 10, true);
random(true);
merge
  • In Lodash the merge is done in-place, on the first argument to the function. In Remeda a new object is always returned, and none of the input objects are mutated.
  • In Lodash assign can be used to merge any number of objects by accepting a variadic argument list. In Remeda, if you are merging only 2 objects use merge, but if you need to merge more than 2 objects use mergeAll instead, which takes an Array of objects.
// Lodash
const DATA = { a: 1, b: 2 };
assign(DATA, b);
assign(DATA, b, c, d, e);

// Remeda
let DATA = { a: 1, b: 2 };
DATA = merge(DATA, b);
DATA = mergeAll([DATA, b, c, d, e]);
prop
  • In Lodash get takes an array where each element is a property in the path to a deeply-nested property (e.g. ['a', 'b', '0', 'c']). In Remeda the path is provided directly as a variadic arguments list.
  • In Lodash all property keys are string, even when accessing numbers. In Remeda the exact type of each prop should be used instead (e.g., number when accessing arrays).
  • Lodash also supports a string that defines the path to the deeply-nested property (e.g., a.b[0].c). In Remeda this isn’t supported directly; instead, use the helper function stringToPath to convert the path string to a path array, which could then be spread into prop.
  • Lodash supports an optional last argument to get which defines a default fallback in cases where the prop being accessed does not exist on the input object. To achieve this wrap your call to prop with a call to defaultTo, or use the native Nullish coalescing operator ?? instead if the fallback is of a different type.

Shallow Access

const DATA = { a: 123 };

// Lodash
_.get(DATA, ["a"]);

// Remeda
prop(DATA, "a");

Array indices

const DATA = [1, 2, 3];

// Lodash
_.get(DATA, ["0"]);

// Remeda
prop(DATA, 0);

Path Arrays

const DATA = { a: [{ b: { c: 3 } }] };

// Lodash
_.get(DATA, ["a", "0", "b", "c"]);

// Remeda
prop(DATA, "a", 0, "b", "c");

Path Strings

const DATA = { a: [{ b: { c: 3 } }] };

// Lodash
_.get(DATA, "a[0].b.c");

// Remeda
prop(DATA, ...stringToPath("a[0].b.c"));

Default Fallback

const DATA = { a: [] as Array<{ b: number }> };

// Lodash
_.get(DATA, ["a", "0", "b"], "default");

// Remeda
defaultTo(prop(DATA, "a", 0, "b"), 123);
// Or natively
prop(DATA, "a", 0, "b") ?? "default";
setPath
  • In Lodash the set function supports two ways of defining the path parameter: a string representation of the path (similar to XPath: e.g. a.b[0].c), and an array representation of the path (e.g. ['a', 'b', 0, 'c']). In Remeda only the array representation is accepted. Use the helper function stringToPath to translate string paths to array paths.
  • Unlike the Lodash set function, In Remeda the provided value must match the type of the prop at that path, the function does not support creating “sparse” objects.
  • For better type-safety, Remeda offers two additional functions to handle paths of length 1. Use set to update an existing prop in an object (best type-safety and with IDE type-ahead), and addProp to add a new prop to the object (or to override its type)
  • In Lodash set mutates the input object. In Remeda a new object is returned instead. The input object is never mutated.
let data = { a: "hello", deep: [{ z: true }] };

// Lodash
set(data, "a", "world");
set(data, ["a"], "foo");

set(data, "a", 123);
set(data, ["a"], 456);
set(data, "b", 123);
set(data, ["b"], 456);

set(data, "deep[0].z", false);
set(data, ["deep", 0, "z"], false);

// ❌ Not supported:
set(data, "deep[0].newProp", 123);
set(data, ["deep", 0, "newProp"], 123);

// Remeda
data = set(data, "a", "world");
data = set(data, "a", "foo");

data = addProp(data, "a", 123);
data = addProp(data, "a", 456);
data = addProp(data, "b", 123);
data = addProp(data, "b", 456);

data = setPath(data, stringToPath("deep[0].z"), false);
data = setPath(data, ["deep", 0, "z"], false);
camelCase
View source on GitHub
toCamelCase
  • Lodash attempts pseudo-linguistic word splitting to handle special characters, which might lead to inaccurate results. Remeda uses a simpler word splitting approach based on type-fest’s definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-in Intl.Segmenter instead.
  • Remeda treats consecutive uppercase characters differently from Lodash. Use { preserveConsecutiveUppercase: false } as the second parameter to get the same results.
  • Lodash performs normalization on the input before splitting it, including deburr and removing apostrophes. Remeda’s word splitting is simpler and doesn’t include these normalizations, so they need to be done manually if required.
  • Lodash allows calling camelCase without any input, or with an undefined input, which isn’t supported in Remeda. Handle these cases before calling the function.

Simple strings

// Lodash
_.camelCase(input);

// Remeda
toCamelCase(input, { preserveConsecutiveUppercase: false });

Normalized

// Lodash
_.camelCase(input);

// Remeda + Native
toCamelCase(
  input
    // "Promote" diacritics to be independent characters in the string.
    .normalize("NFD")
    // Remove apostrophes and all independent diacritic characters.
    .replace(/['\u2019\u0300-\u036f]/g, ""),
  { preserveConsecutiveUppercase: false },
);

Missing input

// Lodash
_.camelCase();
_.camelCase(input);

// Remeda
("");
input !== undefined
  ? toCamelCase(input, { preserveConsecutiveUppercase: false })
  : "";

// Or
toCamelCase(input ?? "", { preserveConsecutiveUppercase: false });
capitalize
View source on GitHub
capitalize

Runtime

// Lodash
_.capitalize(input);

// Remeda
capitalize(toLowerCase(input));

CSS

// Lodash
<div>{_.capitalize(input)}</div>

// CSS
<style>
  .capitalize {
    text-transform: lowercase;
  }

  .capitalize::first-letter {
    text-transform: uppercase;
  }
</style>
<div class="capitalize">{input}</div>

Not provided by Remeda.

deburr uses hard-coded heuristics which can lead to inaccurate results:

  • Misses some accented characters (e.g., ḩ, ố, ῷ and й).
  • Erroneously “simplifies” ligatures that don’t formally have any diacritics that require “deburring” (e.g., Æ, IJ, Œ, and ʼn).
  • Swaps characters because they look similar although they are semantically unrelated to each other (e.g., Ð, Ø, Þ, ß, ſ).

We strongly recommend using linguistically-aware alternatives instead that are purpose-built for this task:

  • When comparing strings, prefer Intl.Collator with sensitivity: 'base' to compare them while ignoring accents and case.
  • When simplifying strings, prefer normalize("NFD") to decompose characters, then use replace to remove diacritics. This solution is more accurate, but is not a drop-in replacement for deburr because of the differences mentioned above.
  • When querying for a string, prefer purpose built collations and other built-in mechanisms of databases and other systems that offer more robust query normalization.

Comparison

// Lodash
_.deburr("café") === "cafe";

// Native JS
const collator = new Intl.Collator("en", { sensitivity: "base" });
collator.compare("café", "cafe") === 0;

Simplifying

// Lodash
_.deburr(input);

// Native JS
input
  .normalize("NFD")
  .replace(
    /[\u0300-\u036f]/g /* The "Combining Diacritical Marks" Unicode block. */,
    "",
  );
endsWith
  • Lodash supports an optional position parameter to check only part of the string, while Remeda doesn’t support this parameter. To replicate this behavior, use sliceString to truncate the string before checking.
  • Lodash allows the suffix to be undefined (converts to "undefined" string) but this is not supported by Remeda and needs to be handled explicitly.

Basic usage

// Lodash
_.endsWith(input, suffix);

// Remeda
endsWith(input, suffix);

// Native
input.endsWith(suffix);

Position parameter

// Lodash
_.endsWith(input, suffix, position);

// Remeda
endsWith(sliceString(input, 0, position), suffix);

// Native
input.endsWith(suffix, position);

Undefined suffix

// Lodash
_.endsWith(input, suffix);

// Remeda
suffix !== undefined && endsWith(input, suffix);

Not provided by Remeda.

escape is not needed in the vast majority of modern web development - all modern frameworks and tools perform this kind of escaping (and more!) automatically wherever it matters. If you are working on a low-level application that writes directly to the DOM, prefer libraries that take a holistic approach to this problem (like DOMPurify) instead of basic string substitutions. In the rare case you must have an escaping function, copy the reference code below as-is:

const ESCAPED: Readonly<Record<string, `&${string};`>> = {
  // https://github.com/lodash/lodash/blob/main/lodash.js#L399-L403
  "&": "&amp;",
  "<": "&lt;",
  ">": "&gt;",
  '"': "&quot;",
  "'": "&#39;",
};

const CHARS_RE = new RegExp(`[${Object.keys(ESCAPED).join("")}]`, "g");

const escape = (input: string): string =>
  input.replace(CHARS_RE, (char) => ESCAPED[char]!);
escapeRegExp
View source on GitHub

Not provided by Remeda.

escapeRegExp is effectively a wrapper around the regular expression /[\\^$.*+?()[\]{}|]/g (which matches on the characters: ^, $, ., *, +, ?, (), {}, [], and |) that also does replacement. It is only useful in very niche situations where you’d want to use externally-provided strings directly to the RegExp constructor and you’d want to sanitize them first. These situations might be solvable differently by either validating the input itself, or by using more nuanced string utilities provided by databases and other data sources that might already do sanitization for you. In the rare case you must have an escaping function, copy the reference code below as-is.

const SPECIAL_CHARACTERS_RE = /[\\^$.*+?()[\]{}|]/g;

const escapeRegExp = (input: string): string =>
  input.replace(SPECIAL_CHARACTERS_RE, "\\$&");
kebabCase
View source on GitHub
toKebabCase
  • Lodash attempts pseudo-linguistic word splitting to handle special characters, which might lead to inaccurate results. Remeda uses a simpler word splitting approach based on type-fest’s definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-in Intl.Segmenter instead.
  • Lodash performs normalization on the input before splitting it, including deburr and removing apostrophes. Remeda’s word splitting is simpler and doesn’t include these normalizations, so they need to be done manually if required.
  • Lodash allows calling kebabCase without any input, or with an undefined input, which isn’t supported in Remeda. Handle these cases before calling the function.

Simple strings

// Lodash
_.kebabCase(input);

// Remeda
toKebabCase(input);

Normalized

// Lodash
_.kebabCase(input);

// Remeda + Native
toKebabCase(
  input
    // "Promote" diacritics to be independent characters in the string.
    .normalize("NFD")
    // Remove apostrophes and all independent diacritic characters.
    .replace(/['\u2019\u0300-\u036f]/g, ""),
);

Missing input

// Lodash
_.kebabCase();
_.kebabCase(input);

// Remeda
("");
input !== undefined ? toKebabCase(input) : "";

// Or
toKebabCase(input ?? "");
lowerCase
View source on GitHub
toTitleCase
  • Use toLowerCase on the results of toTitleCase, which reformats the string into space-delimited words.
  • Lodash attempts pseudo-linguistic word splitting to handle special characters, which might lead to inaccurate results. Remeda uses a simpler word splitting approach based on type-fest’s definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-in Intl.Segmenter instead.
  • Lodash performs normalization on the input before splitting it, including deburr and removing apostrophes. Remeda’s word splitting is simpler and doesn’t include these normalizations, so they need to be done manually if required.
  • Lodash allows calling lowerCase without any input, or with an undefined input, which isn’t supported in Remeda. Handle these cases before calling the function.

Basic usage

// Lodash
_.lowerCase(input);

// Remeda
toLowerCase(toTitleCase(input));

Normalized

// Lodash
_.lowerCase(input);

// Remeda + Native
toLowerCase(
  toTitleCase(
    input
      // "Promote" diacritics to be independent characters in the string.
      .normalize("NFD")
      // Remove apostrophes and all independent diacritic characters.
      .replace(/['\u2019\u0300-\u036f]/g, ""),
  ),
);

Missing input

// Lodash
_.lowerCase();
_.lowerCase(input);

// Remeda
("");
input !== undefined ? toLowerCase(toTitleCase(input)) : "";

// Or
toLowerCase(toTitleCase(input ?? ""));
lowerFirst
View source on GitHub
uncapitalize
  • For display purposes, prefer using CSS; use ::first-letter to target just the first letter of the word and text-transform: lowercase to lowercase it.
  • Lodash allows calling lowerFirst without any input (or with an undefined input), which results in an empty string "". Remeda’s uncapitalize requires a string input.

Basic usage

// Lodash
_.lowerFirst(input);

// Remeda
uncapitalize(input);

CSS

// Lodash
<div>{_.lowerFirst(input)}</div>

// CSS
<style>
  .lowerFirst::first-letter {
    text-transform: lowercase;
  }
</style>
<div class="lowerFirst">{input}</div>

Missing input data

// Lodash
_.lowerFirst();
_.lowerFirst(input);

// Remeda
input !== undefined ? uncapitalize(input) : "";

// Or
uncapitalize(input ?? "");

Not provided by Remeda.

  • If pad is used with whitespace padding (which is also the default) in order to center text within an HTML component, prefer using CSS instead. Use width with a length in ch units, and text-align: center.
  • For other uses pad could be replicated via padStart and padEnd, by computing the amount of effective padding you need on one side.

CSS

// Lodash
<pre>{_.pad(input, 20)}</pre>

// CSS
<div style="text-align:center;width:20ch">{input}</div>

Reference Implementation

const pad = (data: string, length: number, padding = " ") =>
  data.padStart((length + data.length) / 2, padding).padEnd(length, padding);

Not provided by Remeda.

  • Use native String.prototype.padEnd instead.
  • If padEnd is used with whitespace padding (which is also the default) in order to position text within an HTML component, prefer using CSS instead. Use width with a length in ch units, and text-align: left.

Basic Usage

// Lodash
_.padEnd(input, n);
_.padEnd(input, n, padding);

// Native
input.padEnd(n);
input.padEnd(n, padding);

CSS

// Lodash
<pre>{_.padEnd(input, 20)}</pre>

// CSS
<div style="text-align:left;width:20ch">{input}</div>

Not provided by Remeda.

  • Use native String.prototype.padStart instead.
  • If padStart is used with whitespace padding (which is also the default) in order to position text within an HTML component, prefer using CSS instead. Use width with a length in ch units, and text-align: right.

Basic Usage

// Lodash
_.padStart(input, n);
_.padStart(input, n, padding);

// Native
input.padStart(n);
input.padStart(n, padding);

CSS

// Lodash
<pre>{_.padStart(input, 20)}</pre>

// CSS
<div style="text-align:right;width:20ch">{input}</div>

Not provided by Remeda.

  • Use native Number.parseInt instead.
  • When used as a callback, parseInt protects against the index parameter unintentionally redefining the radix. To avoid this issue, wrap the callback with an arrow function.

Basic usage

// Lodash
_.parseInt(input);
_.parseInt(input, radix);

// Native
Number.parseInt(input);
Number.parseInt(input, radix);

Callback

// Lodash
data.map(_.parseInt);

// Native
data.map((item) => Number.parseInt(item, 10)); // ✅
data.map(Number.parseInt); // ❌

Not provided by Remeda.

Use native String.prototype.repeat instead.

// Lodash
_.repeat(input, n);

// Native
input.repeat(n);

Not provided by Remeda.

  • Use native String.prototype.replace instead.
  • Lodash typing incorrectly allows calling replace with two arguments. These calls return strings as-is, while RegExp inputs are converted to strings.

Regular Usage

// Lodash
_.replace(input, pattern, replacement);

// Native
input.replace(pattern, replacement);

Invalid 2-parameter usage

// Lodash
_.replace(input, replacement);
_.replace(/pattern/, replacement);

// Native
input; // Returns input unchanged
String(/pattern/); // "/pattern/" - RegExp converted to string
snakeCase
View source on GitHub
toSnakeCase
  • Lodash attempts pseudo-linguistic word splitting to handle special characters, which might lead to inaccurate results. Remeda uses a simpler word splitting approach based on type-fest’s definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-in Intl.Segmenter instead.
  • Lodash performs normalization on the input before splitting it, including deburr and removing apostrophes. Remeda’s word splitting is simpler and doesn’t include these normalizations, so they need to be done manually if required.
  • Lodash allows calling snakeCase without any input, or with an undefined input, which isn’t supported in Remeda. Handle these cases before calling the function.

Simple strings

// Lodash
_.snakeCase(input);

// Remeda
toSnakeCase(input);

Normalized

// Lodash
_.snakeCase(input);

// Remeda + Native
toSnakeCase(
  input
    // "Promote" diacritics to be independent characters in the string.
    .normalize("NFD")
    // Remove apostrophes and all independent diacritic characters.
    .replace(/['\u2019\u0300-\u036f]/g, ""),
);

Missing input

// Lodash
_.snakeCase();
_.snakeCase(input);

// Remeda
("");
input !== undefined ? toSnakeCase(input) : "";

// Or
toSnakeCase(input ?? "");
split
  • Lodash allows calling split without a separator parameter (or setting it to undefined) which returns the input string as a single-element array (the same happens when split is used as a callback). Remeda needs the input wrapped in an array manually.
  • Lodash accepts null and undefined input values, converting them to empty strings. Remeda needs these values handled separately.
  • When the separator parameter is "", Lodash uses complex logic to parse compound Unicode characters (like family emojis 👨‍👩‍👧‍👦 or flags with modifiers 🏳️‍🌈) as single graphemes. Remeda uses native String.prototype.split which splits these into component parts. Use Intl.Segmenter with granularity: "grapheme" for proper Unicode handling. This is rarely needed - simple Unicode characters (like non-Latin alphabets) work fine.

Basic usage

// Lodash
_.split(input, separator);
_.split(input, separator, limit);

// Remeda
split(input, separator);
split(input, separator, limit);

Without separator

// Lodash
_.split(input);
_.map(data, _.split);

// Native
[input];
map(data, (item) => [item]);

Nullish inputs

// Lodash
_.split(input, separator);
_.split(input, separator, limit);

// Remeda
input != null ? split(input, separator) : [""];
input != null ? split(input, separator, limit) : [""];

// Or
split(input ?? "", separator);
split(input ?? "", separator, limit);

Unicode grapheme splitting

// Lodash
_.split(input, "");
_.split(input, "", limit);

// Native
[...new Intl.Segmenter("en", { granularity: "grapheme" }).segment(input)].map(
  ({ segment }) => segment,
);
[...new Intl.Segmenter("en", { granularity: "grapheme" }).segment(input)]
  .map(({ segment }) => segment)
  .slice(0, limit);
startCase
View source on GitHub
toTitleCase
  • Lodash attempts pseudo-linguistic word splitting to handle special characters, which might lead to inaccurate results. Remeda uses a simpler word splitting approach based on type-fest’s definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-in Intl.Segmenter instead.
  • Remeda treats consecutive uppercase characters differently from Lodash. Use { preserveConsecutiveUppercase: false } as the second parameter to get the same results.
  • Lodash performs normalization on the input before splitting it, including deburr and removing apostrophes. Remeda’s word splitting is simpler and doesn’t include these normalizations, so they need to be done manually if required.
  • Lodash allows calling startCase without any input, or with an undefined input, which isn’t supported in Remeda. Handle these cases before calling the function.

Simple strings

// Lodash
_.startCase(input);

// Remeda
toTitleCase(input, { preserveConsecutiveUppercase: false });

Normalized

// Lodash
_.startCase(input);

// Remeda + Native
toTitleCase(
  input
    // "Promote" diacritics to be independent characters in the string.
    .normalize("NFD")
    // Remove apostrophes and all independent diacritic characters.
    .replace(/['\u2019\u0300-\u036f]/g, ""),
  { preserveConsecutiveUppercase: false },
);

Missing input

// Lodash
_.startCase();
_.startCase(input);

// Remeda
("");
input !== undefined
  ? toTitleCase(input, { preserveConsecutiveUppercase: false })
  : "";

// Or
toTitleCase(input ?? "", { preserveConsecutiveUppercase: false });
startsWith
View source on GitHub
startsWith
  • Lodash supports an optional position parameter to check from a specific index, while Remeda only checks from the beginning of the string. Compose sliceString with startsWith to replicate this behavior.
  • Lodash allows calling the function without a target parameter (or using undefined for it). These would result in the function always returning true. In Remeda the target is required and handling undefined should be done before calling startsWith.
  • Lodash also allows calling the function without an input parameter (or using undefined for it). These would result in the function always returning false (unless target is also "" or undefined). This parameter is also required in Remeda; handle the undefined explicitly.

Basic usage

// Lodash
_.startsWith(input, target);

// Remeda
startsWith(input, target);

Position parameter

// Lodash
_.startsWith(input, target, position);

// Remeda
startsWith(sliceString(input, position), target);

Missing parameters

// Lodash
_.startsWith();
_.startsWith(input);

// Native
true;
true;

Undefined target

// Lodash
_.startsWith(input, target);

// Remeda
target === undefined || startsWith(input, target);

// Or
startsWith(input, target ?? "");

Undefined input

// Lodash
_.startsWith(input, target);

// Remeda
startsWith(input ?? "", target);

// Or (if you know target is not `undefined` or `""`)
input !== undefined && startsWith(input, target);

Not provided by Remeda.

Lodash’s template is a complex templating engine that compiles templates into executable JavaScript functions. Due to its complexity and security implications, it is outside the scope that Remeda offers.

  • In most cases it can be substituted by native template literals.
  • In many other cases it is redundant with the templating capabilities of frameworks such as React, Express, Vue, Fastify, and many others.
  • For the most complex cases there are dedicated libraries that offer robust templating features (e.g., Handlebars).

Template Literals

// Lodash
const greet = _.template("Hello <%= firstName %> <%= lastName %>!");

// Template literals
const greet = ({
  firstName,
  lastName,
}: {
  firstName: string;
  lastName: string;
}) => `Hello ${firstName} ${lastName}!`;

HTML

// Lodash
const bold = _.template("<b><%- value %></b>");
bold({ value: "<script>" });

// React
function Bold({ value }: { value: string }) {
  return <b>{value}</b>;
}

<Bold value="<script>" />;

With complex logic

// Lodash
const list = _.template(
  "<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>",
);
list({ users: ["fred", "barney"] });

// React
function List({ users }: { users: string[] }) {
  return users.map((user) => <li key={user}>{user}</li>);
}

<List users={["fred", "barney"]} />;
toLowerCase
  • For display purposes, prefer using CSS text-transform: lowercase.
  • Lodash allows calling toLower without any input, or with an undefined input, which isn’t supported in Remeda. Handle these cases before calling the function.

Basic usage

// Lodash
_.toLower(input);

// Remeda
toLowerCase(input);

CSS

// Lodash
<div>{_.toLower(input)}</div>

// CSS
<div style="text-transform:lowercase">{input}</div>

Missing input

// Lodash
_.toLower();
_.toLower(input);

// Remeda
("");
input !== undefined ? toLowerCase(input) : "";

// Or
toLowerCase(input ?? "");
toUpperCase
  • For display purposes, prefer using CSS text-transform: uppercase.
  • Lodash allows calling toUpper without any input, or with an undefined input, which isn’t supported in Remeda. Handle these cases before calling the function.

Basic usage

// Lodash
_.toUpper(input);

// Remeda
toUpperCase(input);

CSS

// Lodash
<div>{_.toUpper(input)}</div>

// CSS
<div style="text-transform:uppercase">{input}</div>

Missing input

// Lodash
_.toUpper();
_.toUpper(input);

// Remeda
("");
input !== undefined ? toUpperCase(input) : "";

// Or
toUpperCase(input ?? "");

Not provided by Remeda.

  • When the second characters parameter is not provided to Lodash (or when it is undefined), all whitespace characters would be trimmed. This is the same as the native String.prototype.trim.
  • The native trim doesn’t support the additional characters parameter that allows changing the trimmed characters. Instead, create a regex that would match characters anchored to either the start or the end of the string (^[${characters}]+|[${characters}]+$) and then use String.prototype.replace to replace them with the empty string (""). Don’t forget the g RegExp flag to properly catch everything, and the u RegExp flag if you need to handle Unicode characters.
  • Lodash does complex grapheme parsing, but this is usually not needed unless the characters parameter itself contains complex Unicode graphemes (like family emojis 👨‍👩‍👧‍👦 or flags with modifiers 🏳️‍🌈 that you want to trim). In these cases use Intl.Segmenter with granularity: "grapheme" to split the string, then use Remeda’s dropWhile (for left trimming) and dropLastWhile (for right trimming), and rejoin the array back with join.
  • Lodash allows calling trim without any input (or with an undefined input), which results in an empty string "". This requires explicit handling in replacements.

Whitespaces

// Lodash
_.trim(input);

// Native
input.trim();

Callback

// Lodash
_.map(data, _.trim);

// Native
data.map(String.prototype.trim);

Characters

// Lodash
_.trim(input, characters);

// Native
input.replace(new RegExp(`^[${characters}]+|[${characters}]+$`, "gu"), "");

Graphemes

// Lodash
_.trim(input, characters);

// Remeda
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
const inputGraphemes = Array.from(segmenter.segment(input), prop("segment"));
const graphemes = Array.from(segmenter.segment(characters), prop("segment"));
join(
  dropLastWhile(
    dropWhile(inputGraphemes, isIncludedIn(graphemes)),
    isIncludedIn(graphemes),
  ),
  "",
);

Missing input data

// Lodash
_.trim();
_.trim(input);

// Native
("");
input?.trim() ?? "";

Not provided by Remeda.

  • When the second characters parameter is not provided to Lodash (or when it is undefined), all whitespace characters would be trimmed. This is the same as the native String.prototype.trimEnd.
  • The native trimEnd doesn’t support the additional characters parameter that allows changing the trimmed characters. Instead, create a regex that would match characters anchored to the end of the string ([${characters}]+$) and then use String.prototype.replace to replace them with the empty string (""). Don’t forget the g RegExp flag to properly catch everything, and the u RegExp flag if you need to handle Unicode characters.
  • Lodash does complex grapheme parsing, but this is usually not needed unless the characters parameter itself contains complex Unicode graphemes (like family emojis 👨‍👩‍👧‍👦 or flags with modifiers 🏳️‍🌈 that you want to trim). In these cases use Intl.Segmenter with granularity: "grapheme" to split the string, then use Remeda’s dropLastWhile, and rejoin the array back with join.
  • Lodash allows calling trimEnd without any input (or with an undefined input), which results in an empty string "". This requires explicit handling in replacements.

Whitespaces

// Lodash
_.trimEnd(input);

// Native
input.trimEnd();

Callback

// Lodash
_.map(data, _.trimEnd);

// Native
data.map(String.prototype.trimEnd);

Characters

// Lodash
_.trimEnd(input, characters);

// Native
input.replace(new RegExp(`[${characters}]+$`, "gu"), "");

Graphemes

// Lodash
_.trimEnd(input, characters);

// Remeda
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
const inputGraphemes = Array.from(segmenter.segment(input), prop("segment"));
const graphemes = Array.from(segmenter.segment(characters), prop("segment"));
join(dropLastWhile(inputGraphemes, isIncludedIn(graphemes)), "");

Missing input data

// Lodash
_.trimEnd();
_.trimEnd(input);

// Native
("");
input?.trimEnd() ?? "";
trimStart
View source on GitHub

Not provided by Remeda.

  • When the second characters parameter is not provided to Lodash (or when it is undefined), all whitespace characters would be trimmed. This is the same as the native String.prototype.trimStart.
  • The native trimStart doesn’t support the additional characters parameter that allows changing the trimmed characters. Instead, create a regex that would match characters anchored to the start of the string (^[${characters}]+) and then use String.prototype.replace to replace them with the empty string (""). Don’t forget the g RegExp flag to properly catch everything, and the u RegExp flag if you need to handle Unicode characters.
  • Lodash does complex grapheme parsing, but this is usually not needed unless the characters parameter itself contains complex Unicode graphemes (like family emojis 👨‍👩‍👧‍👦 or flags with modifiers 🏳️‍🌈 that you want to trim). In these cases use Intl.Segmenter with granularity: "grapheme" to split the string, then use Remeda’s dropWhile, and then rejoin the array back with join.
  • Lodash allows calling trimStart without any input (or with an undefined input), which results in an empty string "". This requires explicit handling in replacements.

Whitespaces

// Lodash
_.trimStart(input);

// Native
input.trimStart();

Callback

// Lodash
_.map(data, _.trimStart);

// Native
data.map(String.prototype.trimStart);

Characters

// Lodash
_.trimStart(input, characters);

// Native
input.replace(new RegExp(`^[${characters}]+`, "gu"), "");

Graphemes

// Lodash
_.trimStart(input, characters);

// Remeda
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
const inputGraphemes = Array.from(segmenter.segment(input), prop("segment"));
const graphemes = Array.from(segmenter.segment(characters), prop("segment"));
join(dropWhile(inputGraphemes, isIncludedIn(graphemes)), "");

Missing input data

// Lodash
_.trimStart();
_.trimStart(input);

// Native
("");
input?.trimStart() ?? "";
truncate
  • Remeda requires an explicit length parameter, while Lodash defaults to 30 when it isn’t provided.
  • Lodash computes the length of the input and omission strings in graphemes, unlike Remeda that counts Unicode characters. In the vast majority of cases these are identical, but when these strings contain complex Unicode characters (like family emojis 👨‍👩‍👧‍👦 or flags with modifiers 🏳️‍🌈) the input might not be truncated at the same index.
  • The Remeda function would never return a string longer than length, opting to truncate the omission itself when it’s too long. Lodash will never truncate the omission and therefore might return an output that exceeds length.

Default length

// Lodash
_.truncate(input);

// Remeda
truncate(input, 30);

Custom length

// Lodash
_.truncate(input, { length });

// Remeda
truncate(input, length);

With options

// Lodash
_.truncate(input, { omission, separator });
_.truncate(input, { length, omission, separator });

// Remeda
truncate(input, 30, { omission, separator });
truncate(input, length, { omission, separator });

Not provided by Remeda.

unescape is rarely needed in modern web development. The main use case is processing HTML documents/snippets. For comprehensive HTML processing, prefer dedicated HTML/XML parsing libraries. In the rare case you must have an unescaping function, copy the reference code below as-is:

const UNESCAPED: Readonly<Record<`&${string};`, string>> = {
  // https://github.com/lodash/lodash/blob/main/lodash.js#L408-L412
  "&amp;": "&",
  "&lt;": "<",
  "&gt;": ">",
  "&quot;": '"',
  "&#39;": "'",
};

const ENTITIES_RE = new RegExp(`${Object.keys(UNESCAPED).join("|")}`, "g");

const unescape = (input: string): string =>
  input.replace(ENTITIES_RE, (entity) => UNESCAPED[entity]!);
upperCase
View source on GitHub
toTitleCase
  • Use toUpperCase on the results of toTitleCase, which reformats the string into space-delimited words.
  • Lodash attempts pseudo-linguistic word splitting to handle special characters, which might lead to inaccurate results. Remeda uses a simpler word splitting approach based on type-fest’s definition that should only be used for simple strings like identifiers and internal keys. For linguistic processing where language and locale nuances matter, use the built-in Intl.Segmenter instead.
  • Lodash performs normalization on the input before splitting it, including deburr and removing apostrophes. Remeda’s word splitting is simpler and doesn’t include these normalizations, so they need to be done manually if required.
  • Lodash allows calling upperCase without any input, or with an undefined input, which isn’t supported in Remeda. Handle these cases before calling the function.

Basic usage

// Lodash
_.upperCase(input);

// Remeda
toUpperCase(toTitleCase(input));

Normalized

// Lodash
_.upperCase(input);

// Remeda + Native
toUpperCase(
  toTitleCase(
    input
      // "Promote" diacritics to be independent characters in the string.
      .normalize("NFD")
      // Remove apostrophes and all independent diacritic characters.
      .replace(/['\u2019\u0300-\u036f]/g, ""),
  ),
);

Missing input

// Lodash
_.upperCase();
_.upperCase(input);

// Remeda
("");
input !== undefined ? toUpperCase(toTitleCase(input)) : "";

// Or
toUpperCase(toTitleCase(input ?? ""));
upperFirst
View source on GitHub
capitalize
  • For display purposes, prefer using CSS; use ::first-letter to target just the first letter of the word and text-transform: uppercase to capitalize it.
  • Lodash allows calling upperFirst without any input (or with an undefined input), which results in an empty string "". Remeda’s capitalize requires a string input.

Basic usage

// Lodash
_.upperFirst(input);

// Remeda
capitalize(input);

CSS

// Lodash
<div>{_.upperFirst(input)}</div>

// CSS
<style>
  .upperFirst::first-letter {
    text-transform: uppercase;
  }
</style>
<div class="upperFirst">{input}</div>

Missing input data

// Lodash
_.upperFirst();
_.upperFirst(input);

// Remeda
input !== undefined ? capitalize(input) : "";

// Or
capitalize(input ?? "");

Not provided by Remeda.

  • words is often used to convert between different cases of identifiers and keys. Use toCamelCase, toKebabCase, toSnakeCase, or toTitleCase instead.
  • If words is used for simple splitting tasks, it can often be replaced with String.prototype.split using simple regular expressions like /\s+/, /\W+/, /[\p{Z}\p{P}]+/u, or ones tailored specifically for your use-case.
  • Lodash performs a lot of pseudo-linguistic heuristics in order to detect special characters like diacritics, emojis, and complex graphemes. If you need accurate language-aware splitting of words, prefer Intl.Segmenter with granularity: "word".
  • When provided with the optional pattern parameter, words defers the call to String.prototype.match as-is.

Case conversion

// Lodash
_.words(input).map(_.toLowerCase).join("-");
_.words(input).map(_.toLowerCase).join("_");
_.words(input).map(_.capitalize).join(" ");
_.words(input).map(_.toUpperCase).join("-");
_.words(input).map(_.toUpperCase).join("_");

// Remeda
toKebabCase(input);
toSnakeCase(input);
toTitleCase(input, { preserveConsecutiveUppercase: false });
toUpperCase(toKebabCase(input));
toUpperCase(toSnakeCase(input));

Naive splitting

// Lodash
_.words(input);

// Remeda
split(input, /\s+/); // spaces
split(input, /\W+/); // spaces, punctuation
split(input, /[\p{Z}\p{P}]+/u); // support for unicode spaces and punctuation.

Proper NLP

// Lodash
_.words(input);

// Native
const segmenter = new Intl.Segmenter("en", { granularity: "word" });
Array.from(segmenter.segment(input)).map(({ segment }) => segment);

Custom pattern

// Lodash
_.words(input, pattern);

// Native
input.match(pattern);
defaultTo
  • Number.NaN is not considered nullish and would not result in returning the default value. Use when with the built-in Number.isNaN and a constant for the fallback value.
  • Unlike in Remeda, Lodash allows the fallback value to be of any type, even those that are incompatible with the data type. It also allows the data type to be non-nullish; for those cases use the built-in Nullish coalescing operator ?? directly, or use when with isNullish and constant.

Nullish

const DATA: number | undefined | null;

// Lodash
_.defaultTo(DATA, 456);

// Remeda
defaultTo(DATA, 456);

NaN

const DATA = Number.NaN;

// Lodash
_.defaultTo(DATA, 10);

// Remeda
when(DATA, Number.isNaN, constant(10));

Both

const DATA: number | null | undefined;

// Lodash
_.defaultTo(DATA, 10);

// Remeda
when(
  DATA,
  (x) => x === undefined || x === null || Number.isNaN(x),
  constant(10),
);

Non-matching fallback

const DATA: string | null | undefined;

// Lodash
_.defaultTo(DATA, 123);

// Remeda
when(DATA, isNullish, constant(123));

// or natively
DATA ?? 123;
identity

In Remeda the identity function is a factory that creates an identity function; it needs to be called to be used.

// Lodash
map(identity);

// Remeda
map(identity());
doNothing
  • If a return value is needed, use constant with undefined.
  • Otherwise, use doNothing (which returns void).
// Lodash
noop;

// Remeda
constant(undefined);
doNothing();