mrkeyoor.com_
Tue 22 Sept 00:47 UTC
npmUtilsupdated 21 Sept 2026

remeda review

Remeda is a JavaScript collection and object utility library built around TypeScript inference and two call shapes. A function can receive data first in an ordinary call or data last inside pipe; supported pipe steps evaluate lazily. The package provides ESM and CommonJS entry conditions with no runtime dependencies. Current version 2.44.0 fixes filter and groupByProp type predicates whose narrowed type is not a subtype of the input item. Our measured install used 2.42.0, where both require and ESM import worked and a whole-package browser import measured 9 KB gzipped.

Verdict

Remeda earns its place in TypeScript code that benefits from narrowing and lazy data-last pipelines. Skip it for plain JavaScript or a project already served by native methods, and verify the declaration artifact because our measured 2.42.0 package check found none.

We installed it

Lab card: what happened when we installed remedaScreenshot of remeda documentation
Install✓ · 0.5s1 package on disk · 5 MB
ImportESM import works · require() works · ESM package with exports map
Browser9 KBgzipped (27.8 KB minified), bundled with esbuild
Typesno TypeScript types found
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does remeda install cleanly?

Yes. In a fresh container with an empty cache, npm install remeda finished in 0.5s, leaving 1 package and 5 MB on disk. npm audit reported no known vulnerabilities.

How much does remeda add to a browser bundle?

9 KB gzipped (27.8 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does remeda work with both ESM and CommonJS?

Yes. Both import 'remeda' and require('remeda') worked in Node 22 in our run. The package is published as ESM with an exports map.

Does remeda include TypeScript types?

No type declarations were found in our install, so TypeScript users need their own declarations.

remeda or lodash-es: which should you use?

lodash-es: Use it when Lodash API coverage and team familiarity matter more than the sharpest TypeScript inference. Remeda earns its place in TypeScript code that benefits from narrowing and lazy data-last pipelines.

When should you not use remeda?

The project is plain JavaScript. Most of Remeda's advantage is encoded in TypeScript signatures and editor feedback

API stability4/5The 2.x line retains the data-first and data-last convention, pipe, and named utility imports while adding functions and type corrections. Deprecations point to replacements, such as debounce moving toward funnel. Rapid minor releases can change inference even when runtime behavior stays fixed: 2.44.0 specifically broadens accepted predicates for filter and groupByProp, which may alter previously failing TypeScript calls.
Docs5/5remedajs.com documents each function, call form, examples, return types, and pipe behavior, with dedicated migration tables for Lodash and Ramda. The README states Node, ESM, CommonJS, tree-shaking, and lazy evaluation expectations. Precise types can still yield compiler errors that point at a whole pipe rather than the mistaken step, so small experiments are sometimes clearer than the reference signature.
Maintenance5/5GitHub reports 5,418 stars, 16 open issues and pull requests, an unarchived repository, and a latest push on August 24, 2026. Releases 2.40 through 2.44 arrived within eleven days, fixing runtime utilities, prop index signatures, zip's empty data-last call, pipe short-circuiting, shared pipe objects, and predicate typing. That is current library work rather than repository-only activity.
Ecosystem4/5npm counted 9,991,329 downloads in the completed week ending August 23, 2026. Remeda has migration material for the two libraries developers most often leave and exposes both module systems at the package root. Lodash still has broader API familiarity, while newer helper libraries compete for greenfield projects, so Remeda adoption remains a deliberate TypeScript team choice.

Use it if

  • TypeScript inference must preserve narrowed unions, object keys, tuples, and predicate results through utility calls
  • Pipelines should accept data-last operations while ordinary code keeps a data-first form
  • Filtering, mapping, and taking a few results should stop early instead of materializing every intermediate array
  • A Lodash or Ramda migration benefits from function-by-function documentation
Skip it if

Setup reality

We installed Remeda 2.42.0 in a clean Node 22 Bookworm container. npm completed in 0.5 seconds and left one package using 5 MB. The package has zero direct and zero peer dependencies, with 4,736 KB unpacked, and requires Node 18 or newer. npm audit found no known vulnerabilities at any severity. Our package inspection found no TypeScript declaration files in that measured artifact.

Version 2.42.0 is an ESM package with an exports map, yet both require() and ESM import worked because the root export supplies separate conditions. Do not assume internal subpaths or package.json are importable through that map. Current registry version 2.44.0 is newer than the measured sandbox and fixes type-predicate support in filter and groupByProp, so repeat your own artifact and declaration checks when upgrading.

A whole-package esbuild import measured 27.8 KB minified and 9 KB gzipped. Named imports and production tree shaking can reduce application output, but that depends on the bundler and import style; the measured number is the safe reference for a namespace import. There is no plugin or runtime configuration. Most setup time goes into agreeing on data-first calls outside pipe and data-last calls inside it.

Lazy evaluation applies only to compatible pipeline operations. A step such as sortBy or groupBy must consume the whole input and becomes a materialization boundary. Zero-argument data-last helpers still need parentheses, such as unique(), because the call constructs the operation. Prefer funnel over the deprecated debounce helper documented with known implementation issues.

Patterns

Stop a lazy pipeline after enough matches lazy-pipe

import { pipe, map, filter, take } from "remeda";

let calls = 0;
const result = pipe(
  [1, 2, 3, 4, 5, 6],
  map((x) => { calls++; return x * 2; }),
  take(2),
);
console.log(result, calls); // [2, 4] 2

Lazy-capable steps pull values one at a time. Sorting or grouping must first consume the complete collection.

Switch between the two call shapes data-first-vs-data-last

import { pipe, filter, unique } from "remeda";

// data-first: standalone call
const evens = filter([1, 2, 3, 4], (x) => x % 2 === 0);

// data-last: inside a pipe, data argument omitted
const deduped = pipe([1, 2, 2, 3], filter((x) => x > 1), unique());

A zero-argument operation still needs a call, such as unique(), to create its data-last form.

Remove nullish values and narrow the type narrow-nullish

import { pipe, filter, map, isNonNullish } from "remeda";

const ids: (string | null | undefined)[] = raw;
const clean: string[] = pipe(
  ids,
  filter(isNonNullish),
  map((id) => id.trim()),
);

isNonNullish rejects null and undefined; isDefined keeps null while removing undefined.

Group records or index them by a field group-and-index

import { groupBy, groupByProp, indexBy, prop } from "remeda";

const byStatus = groupBy(orders, prop("status"));
// { paid: Order[], pending: Order[] }

const byStatusTyped = groupByProp(orders, "status"); // better key typing

const usersById = indexBy(users, prop("id"));

groupBy can omit an item when the key callback returns undefined. groupByProp preserves property-driven key types.

Sort by keys or find one extreme sort-and-pick-top

import { sortBy, firstBy, prop } from "remeda";

sortBy(rows, prop("team"), prop("score"));          // ascending, then ascending
sortBy(rows, [prop("score"), "desc"]);              // descending

const best = firstBy(rows, [prop("score"), "desc"]); // no full sort

firstBy avoids sorting the entire array when only one winning item is needed.

Reshape object keys reshape-objects

import { pick, omit, mapValues } from "remeda";

const summary = pick(user, ["id", "email"]);  // { id: number; email: string }
const safe = omit(user, ["passwordHash"]);     // every key except that one
const doubled = mapValues({ a: 1, b: 2 }, (v) => v * 2); // { a: number; b: number }

These helpers copy shallowly, so nested object references remain shared with the source.

Aggregate numeric properties aggregate-numbers

import { sumBy, meanBy, countBy, prop } from "remeda";

sumBy(lineItems, prop("cents"));
meanBy(scores, prop("value"));
countBy(events, (e) => (e.ok ? "ok" : "failed")); // { ok: 12, failed: 3 }

sumBy returns zero for an empty input, while meanBy returns NaN. Handle empty chart series explicitly.

Partition or chunk a collection split-collections

import { partition, chunk } from "remeda";

const [passed, failed] = partition(results, (r) => r.ok);

for (const batch of chunk(ids, 100)) {
  await api.deleteMany(batch);
}

partition returns a typed pair. chunk rejects sizes below one and leaves the final group short.

Merge or transform selected fields merge-and-evolve

import { mergeDeep, evolve } from "remeda";

const config = mergeDeep(defaults, overrides);
// { a: { x: 1, y: 2 } } from { a: { x: 1 } } and { a: { y: 2 } }

const normalized = evolve(row, {
  email: (e) => e.toLowerCase(),
  createdAt: (d) => new Date(d),
});

mergeDeep replaces arrays instead of concatenating them and only recurses through plain objects.

Control bursts with funnel debounce-with-funnel

import { funnel } from "remeda";

const search = funnel(() => runSearch(inputRef.current.value), {
  minQuietPeriodMs: 250,
});
search.call();

const scroll = funnel(() => measure(), { minGapMs: 100, triggerAt: "start" });

Use funnel for debounce or throttle timing; the older debounce helper is deprecated because of known issues.

Run a conditional pipe step conditional-transform

import { pipe, when, conditional, constant } from "remeda";

pipe(200, when((p) => p > 100, (p) => p * 0.9)); // 180
pipe(50, when((p) => p > 100, (p) => p * 0.9));  // 50

const label = conditional(
  status,
  [(s) => s === "paid", () => "Paid"],
  [(s) => s === "open", () => "Awaiting payment"],
  constant("Unknown"),
);

when passes the original value through on a failed predicate. Add a fallback case when conditional is not exhaustive.

Build a reusable operation pipeline reusable-pipeline

import { piped, map, filter, isNonNullish } from "remeda";

const activeNames = piped(
  filter((u: User) => u.active),
  map((u) => u.displayName),
  filter(isNonNullish),
);

const names = activeNames(users);

Annotate the first input when inference has no concrete data value from which to start.

Alternatives

PackageRegistryPick it when
lodash-esnpmUse it when Lodash API coverage and team familiarity matter more than the sharpest TypeScript inference.
rambdanpmUse it for a smaller Ramda-style functional API with automatic currying expectations.
radashnpmUse it for dependency-free helpers with straightforward signatures and less emphasis on lazy pipes.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.