Repository: remeda/remeda
Pull request #555 (2024-03-03 12:28:35)

feat: Code quality improvements to all utilities (via lint and manual inspection)
This PR complements previous work on adding additional lint rules with the rules requiring manual fixes. This PR is long and risky. Most of the changes are to types or slight code structure and not actual logic, but some non-trivial changes appear in:
* _reduceLazy (bug surfaced: wasn't stopping iterations early when iterator returned "done: true")
* equals (removed a lot of JSisms)
* pipe (refactored to not mutate function params directly).
* sample (bug surfaced via new types: would return the input array instead of a copy)
* splitWhen (similar bug)

Other then these, the changes mainly made more stuff readonly, and centralized all pipe/lazy-evaluation types into the pipe file so they could be properly reused.

Notice the added `unknown` return value to the implementation function (the one that calls purry) for all functions. There was no way to configure the rule to ignore these functions but still check return types for the actual implementation functions, which was what I wanted to have lint check. With explicit return values we can now catch implementation issues hidden by the fact that `purry` would "erase" the types.

codesandbox-ci[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/remeda/remeda/pr/555/builds/479323) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 76125f6babad07e0a3f4791448fe6b60ee34da33:

|Sandbox| Source |
|--|--|
|[remeda-example-vanilla](https://codesandbox.io/s/remeda-example-vanilla-6z686c)| Configuration |


Patch:
diff --git a/eslint.config.js b/eslint.config.mjs
similarity index 62%
rename from eslint.config.js
rename to eslint.config.mjs
index 71408e2..769e230 100644
--- a/eslint.config.js
+++ b/eslint.config.mjs
@@ -1,13 +1,19 @@
-const js = require("@eslint/js");
-const tseslint = require("typescript-eslint");
+import core from "@eslint/js";
+import prettierConfig from "eslint-config-prettier";
+// @ts-expect-error [ts7016] -- I don't know why typing is broken here...
+import { configs as unicornConfigs } from "eslint-plugin-unicorn";
+import { config, configs as typescriptConfigs } from "typescript-eslint";
 
-module.exports = tseslint.config(
+export default config(
   {
     ignores: ["dist", "docs", "examples"],
   },
-  js.configs.recommended,
-  ...tseslint.configs.strictTypeChecked,
-  ...tseslint.configs.stylisticTypeChecked,
+  core.configs.recommended,
+  ...typescriptConfigs.strictTypeChecked,
+  ...typescriptConfigs.stylisticTypeChecked,
+  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access -- I don't know why typing is broken for unicorn...
+  unicornConfigs["flat/recommended"],
+  prettierConfig,
   {
     languageOptions: {
       parserOptions: {
@@ -53,13 +59,17 @@ module.exports = tseslint.config(
       "no-duplicate-imports": "off",
       "@typescript-eslint/consistent-type-imports": ["warn"],
 
-      // -----------------------------------------------------------------------
-      // TODO: These rules are part of the typescript configs but require manual fixes which I want to do in a separate PR for easier reviewing and reverting. Turning them off for now so that our CI is green.
-      "@typescript-eslint/class-literal-property-style": "off",
-      "@typescript-eslint/no-empty-function": "off",
-      "@typescript-eslint/prefer-nullish-coalescing": "off",
+      // TODO: Once we bump our typescript `target` we should enable these rules.
+      "unicorn/no-for-loop": "off",
+      "unicorn/prefer-at": "off",
+      "unicorn/prefer-number-properties": "off",
+      "unicorn/prefer-spread": "off",
 
-      // -----------------------------------------------------------------------
+      // TODO: These rules allow us to really standardize our codebase, but they
+      // also do sweeping changes to the whole codebase which is very noisy. We
+      // should do it in one sweep sometime in the future.
+      "@typescript-eslint/naming-convention": "off",
+      "unicorn/prevent-abbreviations": "off",
 
       // === ESLint ============================================================
       // (We are assuming that the config is extended by eslint's: recommended
@@ -78,10 +88,8 @@ module.exports = tseslint.config(
       "no-await-in-loop": "error",
       "no-constant-binary-expression": "error",
       "no-new-native-nonconstructor": "error",
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // "no-promise-executor-return": "error",
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // "no-self-compare": "error",
+      "no-promise-executor-return": "error",
+      "no-self-compare": "error",
       "no-template-curly-in-string": "warn",
       "no-unmodified-loop-condition": "error",
       "no-unreachable-loop": "error",
@@ -91,22 +99,17 @@ module.exports = tseslint.config(
       "arrow-body-style": "warn",
       curly: "error",
       "default-case-last": "warn",
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // "dot-notation": "warn",
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // eqeqeq: ["error", "always", { null: "always" }],
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // "func-style": ["error", "declaration", { allowArrowFunctions: true }],
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // "guard-for-in": "error",
+      "dot-notation": "warn",
+      eqeqeq: ["error", "always", { null: "always" }],
+      "func-style": ["error", "declaration", { allowArrowFunctions: true }],
+      "guard-for-in": "error",
       "logical-assignment-operators": "warn",
       "operator-assignment": "warn",
       "object-shorthand": "warn",
       "prefer-arrow-callback": ["error", { allowNamedFunctions: true }],
       "prefer-const": "error",
       "prefer-exponentiation-operator": "error",
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // "prefer-named-capture-group": "warn",
+      "prefer-named-capture-group": "warn",
       "prefer-numeric-literals": "error",
       "prefer-object-spread": "error",
       "prefer-promise-reject-errors": ["error", { allowEmptyReject: false }],
@@ -114,14 +117,12 @@ module.exports = tseslint.config(
       "prefer-spread": "error",
       "prefer-template": "error",
       "require-await": "error",
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // "require-unicode-regexp": "warn",
+      "require-unicode-regexp": "warn",
       "symbol-description": "warn",
 
       "no-alert": "error",
       "no-array-constructor": "error",
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // "no-bitwise": "warn",
+      "no-bitwise": "warn",
       "no-caller": "error",
       "no-console": "error",
       "no-else-return": ["warn", { allowElseIf: false }],
@@ -140,14 +141,11 @@ module.exports = tseslint.config(
       "no-negated-condition": "warn",
       "no-new": "error",
       "no-new-func": "error",
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // "no-new-wrappers": "error",
+      "no-new-wrappers": "error",
       "no-object-constructor": "error",
       "no-octal-escape": "error",
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // "no-param-reassign": "error",
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // "no-plusplus": ["warn", { allowForLoopAfterthoughts: true }],
+      "no-param-reassign": "error",
+      "no-plusplus": ["warn", { allowForLoopAfterthoughts: true }],
       "no-proto": "error",
       "no-return-assign": ["warn", "always"],
       "no-script-url": "error",
@@ -189,21 +187,19 @@ module.exports = tseslint.config(
       // These aren't enabled by default
 
       // Style & Conventions
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // "prefer-destructuring": "off",
-      // "@typescript-eslint/prefer-destructuring": [
-      //   "warn",
-      //   { array: true, object: true },
-      //   { enforceForRenamedProperties: true },
-      // ],
+      "prefer-destructuring": "off",
+      "@typescript-eslint/prefer-destructuring": [
+        "warn",
+        {
+          VariableDeclarator: { array: true, object: true },
+          AssignmentExpression: { array: false, object: false },
+        },
+        { enforceForRenamedProperties: true },
+      ],
       "@typescript-eslint/sort-type-constituents": "warn",
       "@typescript-eslint/promise-function-async": "error",
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // "no-unused-expressions": "off",
-      // "@typescript-eslint/no-unused-expressions": [
-      //   "error",
-      //   { enforceForJSX: true },
-      // ],
+      "no-unused-expressions": "off",
+      "@typescript-eslint/no-unused-expressions": "error",
       "no-use-before-define": "off",
       "@typescript-eslint/no-use-before-define": [
         "error",
@@ -211,31 +207,48 @@ module.exports = tseslint.config(
         // other than that we don't need this rule...
         { functions: false, variables: false },
       ],
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // "@typescript-eslint/strict-boolean-expressions": [
-      //   "warn",
-      //   {
-      //     // As strict as possible...
-      //     allowString: false,
-      //     allowNumber: false,
-      //     allowNullableObject: false,
-      //   },
-      // ],
-      // @see https://github.com/prettier/eslint-config-prettier#forbid-unnecessary-backticks
-      quotes: "off",
-      "@typescript-eslint/quotes": [
+      "@typescript-eslint/strict-boolean-expressions": [
         "warn",
-        "double",
-        { avoidEscape: true, allowTemplateLiterals: false },
+        {
+          // As strict as possible...
+          allowString: false,
+          allowNumber: false,
+          allowNullableObject: false,
+        },
       ],
 
       // Security & Correctness
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // "@typescript-eslint/prefer-readonly-parameter-types": "error",
+      "@typescript-eslint/explicit-function-return-type": [
+        "error",
+        { allowExpressions: true },
+      ],
+      "@typescript-eslint/explicit-module-boundary-types": [
+        "warn",
+        { allowTypedFunctionExpressions: false },
+      ],
+      "@typescript-eslint/prefer-readonly-parameter-types": [
+        "error",
+        {
+          ignoreInferredTypes: true,
+          allow: [
+            {
+              from: "lib",
+              name: [
+                // Built-ins that aren't read-only but aren't detected as such by this rule...
+                "Error",
+                "Function",
+                "IArguments",
+                "Iterable",
+                "Promise",
+                "RegExp",
+              ],
+            },
+          ],
+        },
+      ],
       "@typescript-eslint/no-useless-empty-export": "error",
       "@typescript-eslint/prefer-regexp-exec": "error",
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // "@typescript-eslint/require-array-sort-compare": "error",
+      "@typescript-eslint/require-array-sort-compare": "error",
       "@typescript-eslint/switch-exhaustiveness-check": [
         "error",
         { requireDefaultForNonUnion: true },
@@ -248,29 +261,55 @@ module.exports = tseslint.config(
       "no-loop-func": "off",
       "@typescript-eslint/no-loop-func": "error",
       "@typescript-eslint/no-require-imports": "error",
-      // TODO: Requires manual fixes, enable in a separate PR.
-      // "no-shadow": "off",
-      // "@typescript-eslint/no-shadow": [
-      //   "error",
-      //   { ignoreTypeValueShadow: false },
-      // ],
+      "no-shadow": "off",
+      "@typescript-eslint/no-shadow": "error",
       "@typescript-eslint/no-unsafe-unary-minus": "error",
+
+      // === Unicorn ==========================================================
+      // (We are assuming that the config is extended by unicorns's:
+      // flat/recommended extension)
+
+      "unicorn/consistent-destructuring": "warn",
+      "unicorn/custom-error-definition": "warn",
+      "unicorn/filename-case": ["error", { case: "camelCase" }],
+      "unicorn/no-keyword-prefix": "warn",
+      "unicorn/no-unused-properties": "warn",
+      "unicorn/no-useless-undefined": ["warn", { checkArguments: false }],
+      "unicorn/switch-case-braces": ["error", "avoid"],
     },
   },
   {
-    files: ["*.config.js"],
+    files: ["*.config.mjs"],
     rules: {
-      "no-undef": "off",
-      "@typescript-eslint/no-require-imports": "off",
-      "@typescript-eslint/no-unsafe-call": "off",
-      "@typescript-eslint/no-var-requires": "off",
+      "unicorn/filename-case": "off",
     },
   },
   {
-    files: ["src/type-fest/**.ts"],
+    files: ["src/type-fest/**/*.ts"],
     rules: {
       // Type-fest uses a lot of "banned" types...
       "@typescript-eslint/ban-types": "off",
+      "unicorn/filename-case": "off",
+    },
+  },
+  {
+    files: ["src/**/*.test.ts"],
+    rules: {
+      "unicorn/no-array-callback-reference": "off",
+      "unicorn/no-null": "off",
+      "unicorn/no-useless-undefined": [
+        "warn",
+        { checkArguments: false, checkArrowFunctionBody: false },
+      ],
+    },
+  },
+  {
+    files: ["test/**/*.ts"],
+    rules: {
+      "@typescript-eslint/explicit-function-return-type": "off",
+      "@typescript-eslint/explicit-module-boundary-types": "off",
+      "unicorn/filename-case": "off",
+      "unicorn/no-null": "off",
     },
   },
 );
diff --git a/lint-staged.config.js b/lint-staged.config.mjs
similarity index 87%
rename from lint-staged.config.js
rename to lint-staged.config.mjs
index 92c667b..7f2f67b 100644
--- a/lint-staged.config.js
+++ b/lint-staged.config.mjs
@@ -1,8 +1,9 @@
-module.exports = {
+export default {
   // The Typescript compiler can't type-check a single file, it needs to run on
   // the whole project. To do that we use a function (instead of a string or
   // array) so that no matter what file or how many, we will always run the same
   // command.
+  // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -- this is stupid...
   "*.ts?(x)": () => "tsc -p tsconfig.json --noEmit",
 
   // Javascript and Typescript (including commonJs and esm variants)
diff --git a/package-lock.json b/package-lock.json
index 055cdab..b13de11 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,8 +11,11 @@
       "devDependencies": {
         "@eslint/js": "^8.57.0",
         "@types/eslint__js": "^8.42.3",
+        "@types/eslint-config-prettier": "^6.11.3",
         "@vitest/coverage-v8": "^1.3.1",
         "eslint": "^8.57.0",
+        "eslint-config-prettier": "^9.1.0",
+        "eslint-plugin-unicorn": "^51.0.1",
         "husky": "^9.0.11",
         "lint-staged": "^15.2.2",
         "prettier": "^3.2.5",
@@ -1406,6 +1409,12 @@
         "@types/eslint": "*"
       }
     },
+    "node_modules/@types/eslint-config-prettier": {
+      "version": "6.11.3",
+      "resolved": "https://registry.npmjs.org/@types/eslint-config-prettier/-/eslint-config-prettier-6.11.3.tgz",
+      "integrity": "sha512-3wXCiM8croUnhg9LdtZUJQwNcQYGWxxdOWDjPe1ykCqJFPVpzAKfs/2dgSoCtAvdPeaponcWPI7mPcGGp9dkKQ==",
+      "dev": true
+    },
     "node_modules/@types/estree": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
@@ -1986,6 +1995,50 @@
         "node": ">=8"
       }
     },
+    "node_modules/browserslist": {
+      "version": "4.23.0",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
+      "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "caniuse-lite": "^1.0.30001587",
+        "electron-to-chromium": "^1.4.668",
+        "node-releases": "^2.0.14",
+        "update-browserslist-db": "^1.0.13"
+      },
+      "bin": {
+        "browserslist": "cli.js"
+      },
+      "engines": {
+        "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+      }
+    },
+    "node_modules/builtin-modules": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
+      "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/cac": {
       "version": "6.7.14",
       "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
@@ -2004,6 +2057,26 @@
         "node": ">=6"
       }
     },
+    "node_modules/caniuse-lite": {
+      "version": "1.0.30001594",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001594.tgz",
+      "integrity": "sha512-VblSX6nYqyJVs8DKFMldE2IVCJjZ225LW00ydtUWwh5hk9IfkTOffO6r8gJNsH0qqqeAF8KrbMYA2VEwTlGW5g==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ]
+    },
     "node_modules/chai": {
       "version": "4.4.1",
       "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz",
@@ -2059,6 +2132,42 @@
         "node": "*"
       }
     },
+    "node_modules/ci-info": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz",
+      "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/sibiraj-s"
+        }
+      ],
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/clean-regexp": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz",
+      "integrity": "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==",
+      "dev": true,
+      "dependencies": {
+        "escape-string-regexp": "^1.0.5"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/clean-regexp/node_modules/escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
     "node_modules/clean-stack": {
       "version": "5.2.0",
       "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz",
@@ -2450,6 +2559,19 @@
       "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
       "dev": true
     },
+    "node_modules/core-js-compat": {
+      "version": "3.36.0",
+      "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.0.tgz",
+      "integrity": "sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw==",
+      "dev": true,
+      "dependencies": {
+        "browserslist": "^4.22.3"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/core-js"
+      }
+    },
     "node_modules/core-util-is": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -2627,6 +2749,12 @@
         "readable-stream": "^2.0.2"
       }
     },
+    "node_modules/electron-to-chromium": {
+      "version": "1.4.692",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.692.tgz",
+      "integrity": "sha512-d5rZRka9n2Y3MkWRN74IoAsxR0HK3yaAt7T50e3iT9VZmCCQDT3geXUO5ZRMhDToa1pkCeQXuNo+0g+NfDOVPA==",
+      "dev": true
+    },
     "node_modules/emoji-regex": {
       "version": "10.3.0",
       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
@@ -2784,6 +2912,198 @@
         "url": "https://opencollective.com/eslint"
       }
     },
+    "node_modules/eslint-config-prettier": {
+      "version": "9.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz",
+      "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==",
+      "dev": true,
+      "bin": {
+        "eslint-config-prettier": "bin/cli.js"
+      },
+      "peerDependencies": {
+        "eslint": ">=7.0.0"
+      }
+    },
+    "node_modules/eslint-plugin-unicorn": {
+      "version": "51.0.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-51.0.1.tgz",
+      "integrity": "sha512-MuR/+9VuB0fydoI0nIn2RDA5WISRn4AsJyNSaNKLVwie9/ONvQhxOBbkfSICBPnzKrB77Fh6CZZXjgTt/4Latw==",
+      "dev": true,
+      "dependencies": {
+        "@babel/helper-validator-identifier": "^7.22.20",
+        "@eslint-community/eslint-utils": "^4.4.0",
+        "@eslint/eslintrc": "^2.1.4",
+        "ci-info": "^4.0.0",
+        "clean-regexp": "^1.0.0",
+        "core-js-compat": "^3.34.0",
+        "esquery": "^1.5.0",
+        "indent-string": "^4.0.0",
+        "is-builtin-module": "^3.2.1",
+        "jsesc": "^3.0.2",
+        "pluralize": "^8.0.0",
+        "read-pkg-up": "^7.0.1",
+        "regexp-tree": "^0.1.27",
+        "regjsparser": "^0.10.0",
+        "semver": "^7.5.4",
+        "strip-indent": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=16"
+      },
+      "funding": {
+        "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1"
+      },
+      "peerDependencies": {
+        "eslint": ">=8.56.0"
+      }
+    },
+    "node_modules/eslint-plugin-unicorn/node_modules/find-up": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+      "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+      "dev": true,
+      "dependencies": {
+        "locate-path": "^5.0.0",
+        "path-exists": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/eslint-plugin-unicorn/node_modules/hosted-git-info": {
+      "version": "2.8.9",
+      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+      "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
+      "dev": true
+    },
+    "node_modules/eslint-plugin-unicorn/node_modules/indent-string": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+      "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/eslint-plugin-unicorn/node_modules/locate-path": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+      "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+      "dev": true,
+      "dependencies": {
+        "p-locate": "^4.1.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/eslint-plugin-unicorn/node_modules/normalize-package-data": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+      "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+      "dev": true,
+      "dependencies": {
+        "hosted-git-info": "^2.1.4",
+        "resolve": "^1.10.0",
+        "semver": "2 || 3 || 4 || 5",
+        "validate-npm-package-license": "^3.0.1"
+      }
+    },
+    "node_modules/eslint-plugin-unicorn/node_modules/normalize-package-data/node_modules/semver": {
+      "version": "5.7.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+      "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver"
+      }
+    },
+    "node_modules/eslint-plugin-unicorn/node_modules/p-limit": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+      "dev": true,
+      "dependencies": {
+        "p-try": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/eslint-plugin-unicorn/node_modules/p-locate": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+      "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+      "dev": true,
+      "dependencies": {
+        "p-limit": "^2.2.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/eslint-plugin-unicorn/node_modules/p-try": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+      "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/eslint-plugin-unicorn/node_modules/read-pkg": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
+      "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
+      "dev": true,
+      "dependencies": {
+        "@types/normalize-package-data": "^2.4.0",
+        "normalize-package-data": "^2.5.0",
+        "parse-json": "^5.0.0",
+        "type-fest": "^0.6.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/eslint-plugin-unicorn/node_modules/read-pkg-up": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
+      "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
+      "dev": true,
+      "dependencies": {
+        "find-up": "^4.1.0",
+        "read-pkg": "^5.2.0",
+        "type-fest": "^0.8.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/eslint-plugin-unicorn/node_modules/read-pkg/node_modules/type-fest": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
+      "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/eslint-plugin-unicorn/node_modules/type-fest": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+      "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/eslint-scope": {
       "version": "7.2.2",
       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
@@ -3550,6 +3870,21 @@
       "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
       "dev": true
     },
+    "node_modules/is-builtin-module": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
+      "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
+      "dev": true,
+      "dependencies": {
+        "builtin-modules": "^3.3.0"
+      },
+      "engines": {
+        "node": ">=6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/is-core-module": {
       "version": "2.13.1",
       "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
@@ -3763,6 +4098,18 @@
         "js-yaml": "bin/js-yaml.js"
       }
     },
+    "node_modules/jsesc": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
+      "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
+      "dev": true,
+      "bin": {
+        "jsesc": "bin/jsesc"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/json-buffer": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -4295,6 +4642,15 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/min-indent": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+      "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/minimatch": {
       "version": "3.1.2",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -4396,6 +4752,12 @@
         "node": ">=18"
       }
     },
+    "node_modules/node-releases": {
+      "version": "2.0.14",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
+      "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
+      "dev": true
+    },
     "node_modules/normalize-package-data": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz",
@@ -7644,6 +8006,12 @@
         "node": ">=8"
       }
     },
+    "node_modules/path-parse": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+      "dev": true
+    },
     "node_modules/path-type": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
@@ -7789,6 +8157,15 @@
         "pathe": "^1.1.0"
       }
     },
+    "node_modules/pluralize": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
+      "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
+      "dev": true,
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/postcss": {
       "version": "8.4.35",
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
@@ -8031,6 +8408,15 @@
         "util-deprecate": "~1.0.1"
       }
     },
+    "node_modules/regexp-tree": {
+      "version": "0.1.27",
+      "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz",
+      "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==",
+      "dev": true,
+      "bin": {
+        "regexp-tree": "bin/regexp-tree"
+      }
+    },
     "node_modules/registry-auth-token": {
       "version": "5.0.2",
       "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz",
@@ -8043,6 +8429,27 @@
         "node": ">=14"
       }
     },
+    "node_modules/regjsparser": {
+      "version": "0.10.0",
+      "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.10.0.tgz",
+      "integrity": "sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==",
+      "dev": true,
+      "dependencies": {
+        "jsesc": "~0.5.0"
+      },
+      "bin": {
+        "regjsparser": "bin/parser"
+      }
+    },
+    "node_modules/regjsparser/node_modules/jsesc": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+      "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==",
+      "dev": true,
+      "bin": {
+        "jsesc": "bin/jsesc"
+      }
+    },
     "node_modules/require-directory": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -8052,6 +8459,23 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/resolve": {
+      "version": "1.22.8",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+      "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+      "dev": true,
+      "dependencies": {
+        "is-core-module": "^2.13.0",
+        "path-parse": "^1.0.7",
+        "supports-preserve-symlinks-flag": "^1.0.0"
+      },
+      "bin": {
+        "resolve": "bin/resolve"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/resolve-from": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@@ -8660,6 +9084,18 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/strip-indent": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+      "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+      "dev": true,
+      "dependencies": {
+        "min-indent": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/strip-json-comments": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -8715,6 +9151,18 @@
         "node": ">=14.18"
       }
     },
+    "node_modules/supports-preserve-symlinks-flag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+      "dev": true,
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/temp-dir": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz",
@@ -9020,6 +9468,36 @@
         "node": ">= 10.0.0"
       }
     },
+    "node_modules/update-browserslist-db": {
+      "version": "1.0.13",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
+      "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/browserslist"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "escalade": "^3.1.1",
+        "picocolors": "^1.0.0"
+      },
+      "bin": {
+        "update-browserslist-db": "cli.js"
+      },
+      "peerDependencies": {
+        "browserslist": ">= 4.21.0"
+      }
+    },
     "node_modules/uri-js": {
       "version": "4.4.1",
       "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
diff --git a/package.json b/package.json
index fc1cb99..7a32ab2 100644
--- a/package.json
+++ b/package.json
@@ -46,8 +46,11 @@
   "devDependencies": {
     "@eslint/js": "^8.57.0",
     "@types/eslint__js": "^8.42.3",
+    "@types/eslint-config-prettier": "^6.11.3",
     "@vitest/coverage-v8": "^1.3.1",
     "eslint": "^8.57.0",
+    "eslint-config-prettier": "^9.1.0",
+    "eslint-plugin-unicorn": "^51.0.1",
     "husky": "^9.0.11",
     "lint-staged": "^15.2.2",
     "prettier": "^3.2.5",
diff --git a/prettier.config.js b/prettier.config.mjs
similarity index 66%
rename from prettier.config.js
rename to prettier.config.mjs
index 1c0267e..6f1a202 100644
--- a/prettier.config.js
+++ b/prettier.config.mjs
@@ -1,2 +1,2 @@
 /** @type {import('prettier').Config} */
-module.exports = {};
+export default {};
diff --git a/src/_binarySearchCutoffIndex.ts b/src/_binarySearchCutoffIndex.ts
index ebc21e4..5ebaa31 100644
--- a/src/_binarySearchCutoffIndex.ts
+++ b/src/_binarySearchCutoffIndex.ts
@@ -19,11 +19,11 @@ export function _binarySearchCutoffIndex<T>(
    */
 
   let lowIndex = 0;
+  // eslint-disable-next-line @typescript-eslint/prefer-destructuring
   let highIndex = array.length;
 
   while (lowIndex < highIndex) {
-    // We use bitwise operator here as a way to find the mid-point and round it
-    // down using the same operation.
+    // eslint-disable-next-line no-bitwise -- We use bitwise operator here as a way to find the mid-point and round it down using the same operation.
     const pivotIndex = (lowIndex + highIndex) >>> 1;
     const pivot = array[pivotIndex]!;
 
diff --git a/src/_heap.ts b/src/_heap.ts
index 2cc3a18..9fa5cda 100644
--- a/src/_heap.ts
+++ b/src/_heap.ts
@@ -16,6 +16,7 @@ import { hasAtLeast } from "./hasAtLeast";
  * @returns - void, the result is in the provided heap array.
  */
 export function heapify<T>(
+  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types -- Intentional!
   heap: Array<T>,
   compareFn: CompareFunction<T>,
 ): void {
@@ -37,6 +38,7 @@ export function heapify<T>(
  * heap if the item was inserted.
  */
 export function heapMaybeInsert<T>(
+  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types -- Intentional!
   heap: Array<T>,
   compareFn: CompareFunction<T>,
   item: T,
@@ -45,7 +47,7 @@ export function heapMaybeInsert<T>(
     return;
   }
 
-  const head = heap[0];
+  const [head] = heap;
 
   if (compareFn(item, head) >= 0) {
     // The item shouldn't be inserted into the heap, the heap is unchanged.
@@ -63,6 +65,7 @@ export function heapMaybeInsert<T>(
  * swapping other items in the process.
  */
 function heapSiftDown<T>(
+  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types -- Intentional!
   heap: Array<T>,
   index: number,
   compareFn: CompareFunction<T>,
diff --git a/src/_narrow.ts b/src/_narrow.ts
index 0a0b395..c99400d 100644
--- a/src/_narrow.ts
+++ b/src/_narrow.ts
@@ -33,7 +33,7 @@ type NarrowRaw<A> =
   | (A extends [] ? [] : never)
   | (A extends Narrowable ? A : never)
   | {
-      [K in keyof A]: A[K] extends (...args: Array<unknown>) => unknown
+      [K in keyof A]: A[K] extends (...args: ReadonlyArray<unknown>) => unknown
         ? A[K]
         : NarrowRaw<A[K]>;
     };
diff --git a/src/_paths.ts b/src/_paths.ts
index 58f3370..e206ea1 100644
--- a/src/_paths.ts
+++ b/src/_paths.ts
@@ -24,7 +24,7 @@ export type ValueAtPath<
       : never
     : never;
 
-export type SupportsValueAtPath<Obj, Path extends Array<PropertyKey>, Value> =
-  Value extends ValueAtPath<Obj, Path> ? Obj : never;
+export type SupportsValueAtPath<Obj, P extends Array<PropertyKey>, Value> =
+  Value extends ValueAtPath<Obj, P> ? Obj : never;
 
 type Primitive = boolean | number | string | symbol | null | undefined;
diff --git a/src/_purryOrderRules.ts b/src/_purryOrderRules.ts
index e7fdd2d..177a066 100644
--- a/src/_purryOrderRules.ts
+++ b/src/_purryOrderRules.ts
@@ -129,7 +129,7 @@ function orderRuleComparer<T>(
     typeof primaryRule === "function" ? primaryRule : primaryRule[0];
 
   const direction = typeof primaryRule === "function" ? "asc" : primaryRule[1];
-  const comparator = COMPARATORS[direction];
+  const { [direction]: comparator } = COMPARATORS;
 
   const nextComparer =
     secondaryRule === undefined
diff --git a/src/_quickSelect.ts b/src/_quickSelect.ts
index d78e9a7..55a9185 100644
--- a/src/_quickSelect.ts
+++ b/src/_quickSelect.ts
@@ -27,7 +27,7 @@ export const quickSelect = <T>(
       undefined
     : quickSelectImplementation(
         // We need to clone the array because quickSelect mutates it in-place.
-        [...data],
+        data.slice(),
         0 /* left */,
         data.length - 1 /* right */,
         index,
@@ -38,6 +38,7 @@ export const quickSelect = <T>(
  * The actual implementation, called recursively.
  */
 function quickSelectImplementation<T>(
+  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types -- Intentional!
   data: Array<T>,
   left: number,
   right: number,
@@ -65,6 +66,7 @@ function quickSelectImplementation<T>(
 }
 
 function partition<T>(
+  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types -- Intentional!
   data: Array<T>,
   left: number,
   right: number,
@@ -77,7 +79,7 @@ function partition<T>(
     if (compareFn(data[j]!, pivot) < 0) {
       // Move items smaller then the pivot to the start of the array.
       swapInPlace(data, i, j);
-      i++;
+      i += 1;
     }
   }
 
diff --git a/src/_reduceLazy.ts b/src/_reduceLazy.ts
index b0c1ade..69a687b 100644
--- a/src/_reduceLazy.ts
+++ b/src/_reduceLazy.ts
@@ -1,41 +1,26 @@
-export type LazyResult<T> = LazyEmpty | LazyMany<T> | LazyNext<T>;
-
-type LazyEmpty = {
-  done: boolean;
-  hasNext: false;
-  hasMany?: false | undefined;
-  next?: undefined;
-};
-
-type LazyNext<T> = {
-  done: boolean;
-  hasNext: true;
-  hasMany?: false | undefined;
-  next: T;
-};
-
-type LazyMany<T> = {
-  done: boolean;
-  hasNext: true;
-  hasMany: true;
-  next: Array<T>;
-};
+import type { LazyEvaluator } from "./pipe";
 
 export function _reduceLazy<T, K>(
   array: ReadonlyArray<T>,
-  lazy: (item: T, index?: number, array?: ReadonlyArray<T>) => LazyResult<K>,
-  indexed?: boolean,
+  lazy: LazyEvaluator<T, K>,
+  isIndexed = false,
 ): Array<K> {
-  const newArray: Array<K> = [];
+  const out: Array<K> = [];
+
   // We intentionally use a for loop here instead of reduce for performance reasons. See https://leanylabs.com/blog/js-forEach-map-reduce-vs-for-for_of/ for more info
   for (let index = 0; index < array.length; index++) {
     const item = array[index]!;
-    const result = indexed ? lazy(item, index, array) : lazy(item);
+    const result = isIndexed ? lazy(item, index, array) : lazy(item);
     if (result.hasMany === true) {
-      newArray.push(...result.next);
+      out.push(...result.next);
     } else if (result.hasNext) {
-      newArray.push(result.next);
+      out.push(result.next);
+    }
+
+    if (result.done) {
+      break;
     }
   }
-  return newArray;
+
+  return out;
 }
diff --git a/src/_swapInPlace.ts b/src/_swapInPlace.ts
index 0541266..58f1b6a 100644
--- a/src/_swapInPlace.ts
+++ b/src/_swapInPlace.ts
@@ -1,7 +1,12 @@
 /**
  * An efficient hack to swap the values at two indices in an array *in-place*.
  */
-export function swapInPlace(data: Array<unknown>, i: number, j: number): void {
+export function swapInPlace(
+  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types -- Intentional
+  data: Array<unknown>,
+  i: number,
+  j: number,
+): void {
   // We use destructuring to perform an in-place swap *without* needing a
   // temporary variable
   [data[i], data[j]] = [data[j], data[i]];
diff --git a/src/_toSingle.ts b/src/_toSingle.ts
index 3e72be9..ac21cc9 100644
--- a/src/_toSingle.ts
+++ b/src/_toSingle.ts
@@ -1,3 +1,6 @@
+type Single<Func> = Func & { readonly single: true };
+
 // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Typescript requires using `any` to infer any kind of function type, `unknown` is not enough.
-export const _toSingle = <Func extends (...args: any) => unknown>(fn: Func) =>
-  Object.assign(fn, { single: true as const });
+export const _toSingle = <Func extends (...args: any) => unknown>(
+  fn: Func,
+): Single<Func> => Object.assign(fn, { single: true as const });
diff --git a/src/_types.ts b/src/_types.ts
index 42d98a3..759a387 100644
--- a/src/_types.ts
+++ b/src/_types.ts
@@ -1,7 +1,11 @@
 import type { IsAny } from "./type-fest/is-any";
 
 export type Pred<T, K> = (input: T) => K;
-export type PredIndexed<T, K> = (input: T, index: number, array: Array<T>) => K;
+export type PredIndexed<T, K> = (
+  input: T,
+  index: number,
+  array: ReadonlyArray<T>,
+) => K;
 export type PredIndexedOptional<T, K> = (
   input: T,
   index?: number,
@@ -22,7 +26,7 @@ export type NonEmptyArray<T> = [T, ...Array<T>];
  *
  * @see This was inspired by the type-definition of Promise.all (https://github.com/microsoft/TypeScript/blob/1df5717b120cddd325deab8b0f2b2c3eecaf2b01/src/lib/es2015.promise.d.ts#L21)
  */
-export type IterableContainer<T = unknown> = ReadonlyArray<T> | [];
+export type IterableContainer<T = unknown> = ReadonlyArray<T> | readonly [];
 
 // Inspired and largely copied from `sindresorhus/ts-extras`:
 // @see https://github.com/sindresorhus/ts-extras/blob/44f57392c5f027268330771996c4fdf9260b22d6/source/object-keys.ts
diff --git a/src/_withPrecision.ts b/src/_withPrecision.ts
index 54dd7da..9fbdbc0 100644
--- a/src/_withPrecision.ts
+++ b/src/_withPrecision.ts
@@ -5,7 +5,7 @@
 const MAX_PRECISION = 15;
 
 export function _withPrecision(roundingFn: (value: number) => number) {
-  return (value: number, precision: number) => {
+  return (value: number, precision: number): number => {
     if (precision === 0) {
       return roundingFn(value);
     }
diff --git a/src/add.ts b/src/add.ts
index 55adc6b..1c72ce8 100644
--- a/src/add.ts
+++ b/src/add.ts
@@ -30,10 +30,10 @@ export function add(value: number, addend: number): number;
  */
 export function add(addend: number): (value: number) => number;
 
-export function add() {
+export function add(): unknown {
   return purry(_add, arguments);
 }
 
-function _add(value: number, addend: number) {
+function _add(value: number, addend: number): number {
   return value + addend;
 }
diff --git a/src/addProp.ts b/src/addProp.ts
index bd6ad78..f827a04 100644
--- a/src/addProp.ts
+++ b/src/addProp.ts
@@ -35,7 +35,7 @@ export function addProp<
   V,
 >(prop: K, value: V): (obj: T) => T & { [x in K]: V };
 
-export function addProp() {
+export function addProp(): unknown {
   return purry(_addProp, arguments);
 }
 
diff --git a/src/allPass.ts b/src/allPass.ts
index 6638953..d4102fa 100644
--- a/src/allPass.ts
+++ b/src/allPass.ts
@@ -38,10 +38,13 @@ export function allPass<T>(
   fns: ReadonlyArray<(data: T) => boolean>,
 ): (data: T) => boolean;
 
-export function allPass() {
+export function allPass(): unknown {
   return purry(_allPass, arguments);
 }
 
-function _allPass<T>(data: T, fns: Array<(data: T) => boolean>) {
+function _allPass<T>(
+  data: T,
+  fns: ReadonlyArray<(data: T) => boolean>,
+): boolean {
   return fns.every((fn) => fn(data));
 }
diff --git a/src/anyPass.ts b/src/anyPass.ts
index de186fe..63c3a17 100644
--- a/src/anyPass.ts
+++ b/src/anyPass.ts
@@ -38,10 +38,13 @@ export function anyPass<T>(
   fns: ReadonlyArray<(data: T) => boolean>,
 ): (data: T) => boolean;
 
-export function anyPass() {
+export function anyPass(): unknown {
   return purry(_anyPass, arguments);
 }
 
-function _anyPass<T>(data: T, fns: ReadonlyArray<(data: T) => boolean>) {
+function _anyPass<T>(
+  data: T,
+  fns: ReadonlyArray<(data: T) => boolean>,
+): boolean {
   return fns.some((fn) => fn(data));
 }
diff --git a/src/ceil.ts b/src/ceil.ts
index 3c9d22a..0b86f1f 100644
--- a/src/ceil.ts
+++ b/src/ceil.ts
@@ -37,6 +37,6 @@ export function ceil(value: number, precision: number): number;
  */
 export function ceil(precision: number): (value: number) => number;
 
-export function ceil() {
+export function ceil(): unknown {
   return purry(_withPrecision(Math.ceil), arguments);
 }
diff --git a/src/chunk.ts b/src/chunk.ts
index cd9f996..0bb48c2 100644
--- a/src/chunk.ts
+++ b/src/chunk.ts
@@ -41,15 +41,15 @@ export function chunk<T extends IterableContainer>(
   size: number,
 ): (array: T) => Chunked<T>;
 
-export function chunk() {
+export function chunk(): unknown {
   return purry(_chunk, arguments);
 }
 
-function _chunk<T>(array: ReadonlyArray<T>, size: number) {
-  const ret: Array<ReadonlyArray<T>> = Array.from({
+function _chunk<T>(array: ReadonlyArray<T>, size: number): Array<Array<T>> {
+  const ret: Array<Array<T>> = Array.from({
     length: Math.ceil(array.length / size),
   });
-  for (let index = 0; index < ret.length; index += 1) {
+  for (let index = 0; index < ret.length; index++) {
     ret[index] = array.slice(index * size, (index + 1) * size);
   }
   return ret;
diff --git a/src/clamp.ts b/src/clamp.ts
index ada8a59..41b05a8 100644
--- a/src/clamp.ts
+++ b/src/clamp.ts
@@ -1,5 +1,10 @@
 import { purry } from "./purry";
 
+type Limits = {
+  readonly min?: number;
+  readonly max?: number;
+};
+
 /**
  * Clamp the given value within the inclusive min and max bounds.
  * @param value the number
@@ -13,10 +18,7 @@ import { purry } from "./purry";
  * @dataFirst
  * @category Number
  */
-export function clamp(
-  value: number,
-  limits: { min?: number; max?: number },
-): number;
+export function clamp(value: number, limits: Limits): number;
 
 /**
  * Clamp the given value within the inclusive min and max bounds.
@@ -31,25 +33,16 @@ export function clamp(
  * @dataLast
  * @category Number
  */
-export function clamp(limits: {
-  min?: number;
-  max?: number;
-}): (value: number) => number;
+export function clamp(limits: Limits): (value: number) => number;
 
-export function clamp() {
+export function clamp(): unknown {
   return purry(_clamp, arguments);
 }
 
-function _clamp(value: number, limits: { min?: number; max?: number }) {
-  if (limits.min != null) {
-    if (limits.min > value) {
-      return limits.min;
-    }
-  }
-  if (limits.max != null) {
-    if (limits.max < value) {
-      return limits.max;
-    }
-  }
-  return value;
+function _clamp(value: number, { min, max }: Limits): number {
+  return min !== undefined && value < min
+    ? min
+    : max !== undefined && value > max
+      ? max
+      : value;
 }
diff --git a/src/clone.ts b/src/clone.ts
index 6b75c1e..8c7504f 100644
--- a/src/clone.ts
+++ b/src/clone.ts
@@ -1,10 +1,10 @@
-/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return -- FIXME! */
+/* eslint-disable eqeqeq, guard-for-in, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return, @typescript-eslint/prefer-destructuring, @typescript-eslint/prefer-readonly-parameter-types, unicorn/no-null -- FIXME! */
 
 // from https://github.com/ramda/ramda/blob/master/source/internal/_clone.js
 
 import { type } from "./type";
 
-function _cloneRegExp(pattern: RegExp) {
+function _cloneRegExp(pattern: RegExp): RegExp {
   return new RegExp(
     pattern.source,
     (pattern.global ? "g" : "") +
@@ -20,8 +20,8 @@ function _clone(
   refFrom: Array<any>,
   refTo: Array<any>,
   deep: boolean,
-) {
-  function copy(copiedValue: any) {
+): unknown {
+  function copy(copiedValue: any): unknown {
     const len = refFrom.length;
     let idx = 0;
     while (idx < len) {
diff --git a/src/compact.ts b/src/compact.ts
index 537c3ee..aa38874 100644
--- a/src/compact.ts
+++ b/src/compact.ts
@@ -1,9 +1,11 @@
 import { isTruthy } from "./isTruthy";
 
 /**
- * Filter out all falsey values. The values `false`, `null`, `0`, `""`, `undefined`, and `NaN` are falsey.
+ * Filter out all falsy values. The values `false`, `null`, `0`, `""`,
+ * `undefined`, and `NaN` are falsy.
  *
- * **DEPRECATED: equivalent to `R.filter(R.isTruthy)` and so will be removed in v2.**
+ * **DEPRECATED: equivalent to `R.filter(R.isTruthy)` and so will be removed in
+ * v2.**
  *
  * @param items the array to compact
  * @signature
@@ -16,6 +18,6 @@ import { isTruthy } from "./isTruthy";
 export function compact<T>(
   items: ReadonlyArray<T | "" | 0 | false | null | undefined>,
 ): Array<T> {
-  // TODO: Make lazy version
+  // eslint-disable-next-line unicorn/no-array-callback-reference -- This is incorrect, we use isTruthy as a type predicate here so it has to be called directly.
   return items.filter(isTruthy);
 }
diff --git a/src/concat.ts b/src/concat.ts
index 6c29afd..1d8f8c8 100644
--- a/src/concat.ts
+++ b/src/concat.ts
@@ -30,10 +30,13 @@ export function concat<T, K>(
   arr2: ReadonlyArray<K>,
 ): (arr1: ReadonlyArray<T>) => Array<K | T>;
 
-export function concat() {
+export function concat(): unknown {
   return purry(_concat, arguments);
 }
 
-function _concat(arr1: ReadonlyArray<unknown>, arr2: ReadonlyArray<unknown>) {
-  return arr1.concat(arr2);
+function _concat<T, K>(
+  arr1: ReadonlyArray<T>,
+  arr2: ReadonlyArray<K>,
+): Array<K | T> {
+  return (arr1 as Array<K | T>).concat(arr2);
 }
diff --git a/src/conditional.ts b/src/conditional.ts
index 4c73d4b..8d7de5f 100644
--- a/src/conditional.ts
+++ b/src/conditional.ts
@@ -234,4 +234,4 @@ export namespace conditional {
 }
 
 // Lodash and Ramda return `undefined` as the default case.
-const trivialDefaultCase = () => undefined;
+const trivialDefaultCase = (): undefined => undefined;
diff --git a/src/countBy.ts b/src/countBy.ts
index 425e85e..83bb41e 100644
--- a/src/countBy.ts
+++ b/src/countBy.ts
@@ -3,11 +3,16 @@ import type { Pred, PredIndexed, PredIndexedOptional } from "./_types";
 
 const _countBy =
   (indexed: boolean) =>
-  <T>(array: Array<T>, fn: PredIndexedOptional<T, boolean>) =>
-    array.reduce((ret, item, index) => {
+  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, boolean>) => {
+    let out = 0;
+    for (let index = 0; index < array.length; index++) {
+      // TODO: Once we bump our TypeScript target above ES5 we can use Array.prototype.entries to iterate over the indices and values at the same time.
+      const item = array[index]!;
       const value = indexed ? fn(item, index, array) : fn(item);
-      return ret + (value ? 1 : 0);
-    }, 0);
+      out += value ? 1 : 0;
+    }
+    return out;
+  };
 
 /**
  * Counts how many values of the collection pass the specified predicate.
@@ -49,7 +54,7 @@ export function countBy<T>(
  * @category Array
  * @deprecated equivalent to `R.filter(fn).length` and so will be removed in v2.
  */
-export function countBy() {
+export function countBy(): unknown {
   return purry(_countBy(false), arguments);
 }
 
@@ -67,7 +72,7 @@ export namespace countBy {
   export function indexed<T>(
     fn: PredIndexed<T, boolean>,
   ): (array: ReadonlyArray<T>) => number;
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_countBy(true), arguments);
   }
 }
diff --git a/src/createPipe.ts b/src/createPipe.ts
index 8cf5f1b..c7c83b9 100644
--- a/src/createPipe.ts
+++ b/src/createPipe.ts
@@ -61,7 +61,7 @@ export function createPipe<A, B, C, D, E, F, G, H>(
 export function createPipe(
   ...operations: ReadonlyArray<(input: unknown) => unknown>
 ) {
-  return (value: unknown) =>
+  return (value: unknown): unknown =>
     pipe(
       value,
       // @ts-expect-error [ts2556] - We can't avoid this error because pipe is typed for users and this is an internal function
diff --git a/src/debounce.ts b/src/debounce.ts
index b5fadfd..2bd1991 100644
--- a/src/debounce.ts
+++ b/src/debounce.ts
@@ -142,7 +142,7 @@ export function debounce<F extends (...args: any) => any>(
   // will return this cached value.
   let result: ReturnType<F> | undefined;
 
-  const handleInvoke = () => {
+  const handleInvoke = (): void => {
     if (latestCallArgs === undefined) {
       // This should never happen! It means we forgot to clear a timeout!
       return;
@@ -165,7 +165,7 @@ export function debounce<F extends (...args: any) => any>(
     result = func(...args);
   };
 
-  const handleCoolDownEnd = () => {
+  const handleCoolDownEnd = (): void => {
     if (coolDownTimeoutId === undefined) {
       // It's rare to get here, it should only happen when `flush` is called
       // when the cool-down window isn't active.
@@ -185,7 +185,7 @@ export function debounce<F extends (...args: any) => any>(
     }
   };
 
-  const handleDebouncedCall = (args: Parameters<F>) => {
+  const handleDebouncedCall = (args: Parameters<F>): void => {
     // We save the latest call args so that (if and) when we invoke the function
     // in the future, we have args to invoke it with.
     latestCallArgs = args;
diff --git a/src/difference.ts b/src/difference.ts
index 3eceab0..8871184 100644
--- a/src/difference.ts
+++ b/src/difference.ts
@@ -1,6 +1,6 @@
-import { purry } from "./purry";
-import type { LazyResult } from "./_reduceLazy";
 import { _reduceLazy } from "./_reduceLazy";
+import type { LazyEvaluator } from "./pipe";
+import { purry } from "./purry";
 
 /**
  * Excludes the values from `other` array.
@@ -39,30 +39,24 @@ export function difference<T, K>(
   other: ReadonlyArray<T>,
 ): (array: ReadonlyArray<K>) => Array<T>;
 
-export function difference() {
+export function difference(): unknown {
   return purry(_difference, arguments, difference.lazy);
 }
 
-function _difference<T>(array: Array<T>, other: Array<T>) {
+function _difference<T>(
+  array: ReadonlyArray<T>,
+  other: ReadonlyArray<T>,
+): Array<T> {
   const lazy = difference.lazy(other);
   return _reduceLazy(array, lazy);
 }
 
 export namespace difference {
-  export function lazy<T>(other: Array<T>) {
+  export function lazy<T>(other: ReadonlyArray<T>): LazyEvaluator<T> {
     const set = new Set(other);
-    return (value: T): LazyResult<T> => {
-      if (!set.has(value)) {
-        return {
-          done: false,
-          hasNext: true,
-          next: value,
-        };
-      }
-      return {
-        done: false,
-        hasNext: false,
-      };
-    };
+    return (value) =>
+      set.has(value)
+        ? { done: false, hasNext: false }
+        : { done: false, hasNext: true, next: value };
   }
 }
diff --git a/src/differenceWith.ts b/src/differenceWith.ts
index 73a128c..06f9008 100644
--- a/src/differenceWith.ts
+++ b/src/differenceWith.ts
@@ -1,6 +1,6 @@
-import { purry } from "./purry";
-import type { LazyResult } from "./_reduceLazy";
 import { _reduceLazy } from "./_reduceLazy";
+import type { LazyEvaluator } from "./pipe";
+import { purry } from "./purry";
 
 type IsEquals<TFirst, TSecond> = (a: TFirst, b: TSecond) => boolean;
 
@@ -54,36 +54,27 @@ export function differenceWith<TFirst, TSecond>(
   isEquals: IsEquals<TFirst, TSecond>,
 ): (array: ReadonlyArray<TFirst>) => Array<TFirst>;
 
-export function differenceWith() {
+export function differenceWith(): unknown {
   return purry(_differenceWith, arguments, differenceWith.lazy);
 }
 
 function _differenceWith<TFirst, TSecond>(
-  array: Array<TFirst>,
-  other: Array<TSecond>,
+  array: ReadonlyArray<TFirst>,
+  other: ReadonlyArray<TSecond>,
   isEquals: IsEquals<TFirst, TSecond>,
-) {
+): Array<TFirst> {
   const lazy = differenceWith.lazy(other, isEquals);
   return _reduceLazy(array, lazy);
 }
 
 export namespace differenceWith {
-  export function lazy<TFirst, TSecond>(
-    other: Array<TSecond>,
-    isEquals: IsEquals<TFirst, TSecond>,
-  ) {
-    return (value: TFirst): LazyResult<TFirst> => {
-      if (other.every((otherValue) => !isEquals(value, otherValue))) {
-        return {
-          done: false,
-          hasNext: true,
-          next: value,
-        };
-      }
-      return {
-        done: false,
-        hasNext: false,
-      };
-    };
-  }
+  export const lazy =
+    <TFirst, TSecond>(
+      other: ReadonlyArray<TSecond>,
+      isEquals: IsEquals<TFirst, TSecond>,
+    ): LazyEvaluator<TFirst> =>
+    (value) =>
+      other.every((otherValue) => !isEquals(value, otherValue))
+        ? { done: false, hasNext: true, next: value }
+        : { done: false, hasNext: false };
 }
diff --git a/src/divide.ts b/src/divide.ts
index 422bcd2..928a2c7 100644
--- a/src/divide.ts
+++ b/src/divide.ts
@@ -28,10 +28,10 @@ export function divide(value: number, divisor: number): number;
  */
 export function divide(divisor: number): (value: number) => number;
 
-export function divide() {
+export function divide(): unknown {
   return purry(_divide, arguments);
 }
 
-function _divide(value: number, divisor: number) {
+function _divide(value: number, divisor: number): number {
   return value / divisor;
 }
diff --git a/src/drop.ts b/src/drop.ts
index f498d5d..542b9a8 100644
--- a/src/drop.ts
+++ b/src/drop.ts
@@ -1,6 +1,6 @@
-import { purry } from "./purry";
-import type { LazyResult } from "./_reduceLazy";
 import { _reduceLazy } from "./_reduceLazy";
+import type { LazyEvaluator } from "./pipe";
+import { purry } from "./purry";
 
 /**
  * Removes first `n` elements from the `array`.
@@ -30,30 +30,23 @@ export function drop<T>(array: ReadonlyArray<T>, n: number): Array<T>;
  */
 export function drop<T>(n: number): (array: ReadonlyArray<T>) => Array<T>;
 
-export function drop() {
+export function drop(): unknown {
   return purry(_drop, arguments, drop.lazy);
 }
 
-function _drop<T>(array: Array<T>, n: number) {
+function _drop<T>(array: ReadonlyArray<T>, n: number): Array<T> {
   return _reduceLazy(array, drop.lazy(n));
 }
 
 export namespace drop {
-  export function lazy<T>(n: number) {
+  export function lazy<T>(n: number): LazyEvaluator<T> {
     let left = n;
-    return (value: T): LazyResult<T> => {
+    return (value) => {
       if (left > 0) {
-        left--;
-        return {
-          done: false,
-          hasNext: false,
-        };
+        left -= 1;
+        return { done: false, hasNext: false };
       }
-      return {
-        done: false,
-        hasNext: true,
-        next: value,
-      };
+      return { done: false, hasNext: true, next: value };
     };
   }
 }
diff --git a/src/dropFirstBy.ts b/src/dropFirstBy.ts
index c673b0f..517379c 100644
--- a/src/dropFirstBy.ts
+++ b/src/dropFirstBy.ts
@@ -60,7 +60,7 @@ function dropFirstByImplementation<T>(
   }
 
   if (n <= 0) {
-    return [...data];
+    return data.slice();
   }
 
   const heap = data.slice(0, n);
diff --git a/src/dropLast.ts b/src/dropLast.ts
index f41fea1..8493d62 100644
--- a/src/dropLast.ts
+++ b/src/dropLast.ts
@@ -26,12 +26,12 @@ export function dropLast<T>(array: ReadonlyArray<T>, n: number): Array<T>;
  */
 export function dropLast<T>(n: number): (array: ReadonlyArray<T>) => Array<T>;
 
-export function dropLast() {
+export function dropLast(): unknown {
   return purry(_dropLast, arguments);
 }
 
-function _dropLast<T>(array: Array<T>, n: number) {
-  const copy = [...array];
+function _dropLast<T>(array: ReadonlyArray<T>, n: number): Array<T> {
+  const copy = array.slice();
   if (n > 0) {
     copy.splice(-n);
   }
diff --git a/src/dropLastWhile.ts b/src/dropLastWhile.ts
index 419eda1..e0705f2 100644
--- a/src/dropLastWhile.ts
+++ b/src/dropLastWhile.ts
@@ -36,7 +36,7 @@ export function dropLastWhile<T>(
   predicate: (item: T) => boolean,
 ): (data: ReadonlyArray<T>) => Array<T>;
 
-export function dropLastWhile() {
+export function dropLastWhile(): unknown {
   return purry(_dropLastWhile, arguments);
 }
 
diff --git a/src/dropWhile.ts b/src/dropWhile.ts
index f45ec04..f009ffd 100644
--- a/src/dropWhile.ts
+++ b/src/dropWhile.ts
@@ -36,7 +36,7 @@ export function dropWhile<T>(
   predicate: (item: T) => boolean,
 ): (data: ReadonlyArray<T>) => Array<T>;
 
-export function dropWhile() {
+export function dropWhile(): unknown {
   return purry(_dropWhile, arguments);
 }
 
diff --git a/src/equals.ts b/src/equals.ts
index 731dcdc..ea52314 100644
--- a/src/equals.ts
+++ b/src/equals.ts
@@ -1,9 +1,5 @@
 import { purry } from "./purry";
 
-// from https://github.com/epoberezkin/fast-deep-equal/blob/master/index.js
-const isArray = Array.isArray;
-const keyList = Object.keys;
-
 /**
  * Returns true if its arguments are equivalent, false otherwise.
  * NOTE: Doesn't handle cyclical data structures.
@@ -36,80 +32,82 @@ export function equals(a: unknown, b: unknown): boolean;
  */
 export function equals(a: unknown): (b: unknown) => boolean;
 
-export function equals() {
+export function equals(): unknown {
   return purry(_equals, arguments);
 }
 
-function _equals(a: unknown, b: unknown) {
+function _equals(a: unknown, b: unknown): boolean {
   if (a === b) {
     return true;
   }
 
-  if (a && b && typeof a === "object" && typeof b === "object") {
-    const arrA = isArray(a);
-    const arrB = isArray(b);
-    let i;
-    let length;
-    let key;
+  if (typeof a === "number" && typeof b === "number") {
+    // TODO: This is a temporary fix for NaN, we should use Number.isNaN once we bump our target above ES5.
+    // eslint-disable-next-line no-self-compare -- We should use Number.isNaN here, but it's ES2015.
+    return a !== a && b !== b;
+  }
 
-    if (arrA && arrB) {
-      length = a.length;
-      if (length !== b.length) {
-        return false;
-      }
-      for (i = length; i-- !== 0; ) {
-        if (!equals(a[i], b[i])) {
-          return false;
-        }
-      }
-      return true;
-    }
+  if (typeof a !== "object" || typeof b !== "object") {
+    return false;
+  }
 
-    if (arrA !== arrB) {
-      return false;
-    }
+  if (a === null || b === null) {
+    return false;
+  }
 
-    const dateA = a instanceof Date;
-    const dateB = b instanceof Date;
-    if (dateA !== dateB) {
-      return false;
-    }
-    if (dateA && dateB) {
-      return a.getTime() === b.getTime();
-    }
+  const isArrayA = Array.isArray(a);
+  const isArrayB = Array.isArray(b);
 
-    const regexpA = a instanceof RegExp;
-    const regexpB = b instanceof RegExp;
-    if (regexpA !== regexpB) {
+  if (isArrayA && isArrayB) {
+    if (a.length !== b.length) {
       return false;
     }
-    if (regexpA && regexpB) {
-      return a.toString() === b.toString();
+    for (let i = 0; i < a.length; i++) {
+      if (!_equals(a[i], b[i])) {
+        return false;
+      }
     }
+    return true;
+  }
+
+  if (isArrayA !== isArrayB) {
+    return false;
+  }
 
-    const keys = keyList(a);
-    length = keys.length;
+  const isDateA = a instanceof Date;
+  const isDateB = b instanceof Date;
+  if (isDateA && isDateB) {
+    return a.getTime() === b.getTime();
+  }
+  if (isDateA !== isDateB) {
+    return false;
+  }
 
-    if (length !== keyList(b).length) {
-      return false;
-    }
+  const isRegExpA = a instanceof RegExp;
+  const isRegExpB = b instanceof RegExp;
+  if (isRegExpA && isRegExpB) {
+    return a.toString() === b.toString();
+  }
+  if (isRegExpA !== isRegExpB) {
+    return false;
+  }
 
-    for (i = length; i-- !== 0; ) {
-      if (!Object.prototype.hasOwnProperty.call(b, keys[i]!)) {
-        return false;
-      }
-    }
+  const keys = Object.keys(a);
 
-    for (i = length; i-- !== 0; ) {
-      key = keys[i]!;
-      // @ts-expect-error [ts7053] - There's no easy way to tell typescript these keys are safe.
-      if (!equals(a[key], b[key])) {
-        return false;
-      }
+  if (keys.length !== Object.keys(b).length) {
+    return false;
+  }
+
+  for (const key of keys) {
+    if (!Object.prototype.hasOwnProperty.call(b, key)) {
+      return false;
     }
 
-    return true;
+    // @ts-expect-error [ts7053] - There's no easy way to tell typescript these keys are safe.
+    if (!_equals(a[key], b[key])) {
+      return false;
+    }
   }
 
-  return a !== a && b !== b;
+  return true;
 }
diff --git a/src/evolve.ts b/src/evolve.ts
index 59f9636..2cf63ab 100644
--- a/src/evolve.ts
+++ b/src/evolve.ts
@@ -132,7 +132,7 @@ export function evolve<T extends object, E extends Evolver<T>>(
   evolver: E,
 ): (object: T) => Evolved<T, E>;
 
-export function evolve() {
+export function evolve(): unknown {
   return purry(_evolve, arguments);
 }
 
@@ -140,16 +140,17 @@ function _evolve(data: unknown, evolver: GenericEvolver): unknown {
   if (typeof data !== "object" || data === null) {
     return data;
   }
-  return toPairs.strict(evolver).reduce<Record<string, unknown>>(
-    (result, [key, value]) => {
-      if (key in result) {
-        result[key] =
-          typeof value === "function"
-            ? value(result[key])
-            : _evolve(result[key], value);
-      }
-      return result;
-    },
-    { ...data },
-  );
+
+  const out: Record<string, unknown> = { ...data };
+
+  for (const [key, value] of toPairs.strict(evolver)) {
+    if (key in out) {
+      out[key] =
+        typeof value === "function"
+          ? value(out[key])
+          : _evolve(out[key], value);
+    }
+  }
+
+  return out;
 }
diff --git a/src/filter.ts b/src/filter.ts
index 34274e3..0ed3a7f 100644
--- a/src/filter.ts
+++ b/src/filter.ts
@@ -1,8 +1,8 @@
-import { purry } from "./purry";
-import type { LazyResult } from "./_reduceLazy";
 import { _reduceLazy } from "./_reduceLazy";
-import type { Pred, PredIndexedOptional, PredIndexed } from "./_types";
 import { _toLazyIndexed } from "./_toLazyIndexed";
+import type { Pred, PredIndexed, PredIndexedOptional } from "./_types";
+import type { LazyEvaluator } from "./pipe";
+import { purry } from "./purry";
 
 /**
  * Filter the elements of an array that meet the condition specified in a callback function.
@@ -49,7 +49,7 @@ export function filter<T>(
   fn: Pred<T, boolean>,
 ): (array: ReadonlyArray<T>) => Array<T>;
 
-export function filter() {
+export function filter(): unknown {
   return purry(_filter(false), arguments, filter.lazy);
 }
 
@@ -64,26 +64,16 @@ const _filter =
 
 const _lazy =
   (indexed: boolean) =>
-  <T>(fn: PredIndexedOptional<T, boolean>) =>
-  (value: T, index?: number, array?: ReadonlyArray<T>): LazyResult<T> => {
-    const valid = indexed ? fn(value, index, array) : fn(value);
-    if (valid) {
-      return {
-        done: false,
-        hasNext: true,
-        next: value,
-      };
-    }
-    return {
-      done: false,
-      hasNext: false,
-    };
-  };
+  <T>(fn: PredIndexedOptional<T, boolean>): LazyEvaluator<T> =>
+  (value, index, array) =>
+    (indexed ? fn(value, index, array) : fn(value))
+      ? { done: false, hasNext: true, next: value }
+      : { done: false, hasNext: false };
 
 export namespace filter {
   export function indexed<T, S extends T>(
     array: ReadonlyArray<T>,
-    fn: (input: T, index: number, array: Array<T>) => input is S,
+    fn: (input: T, index: number, array: ReadonlyArray<T>) => input is S,
   ): Array<S>;
   export function indexed<T>(
     array: ReadonlyArray<T>,
@@ -93,12 +83,12 @@ export namespace filter {
    * @dataLast
    */
   export function indexed<T, S extends T>(
-    fn: (input: T, index: number, array: Array<T>) => input is S,
+    fn: (input: T, index: number, array: ReadonlyArray<T>) => input is S,
   ): (array: ReadonlyArray<T>) => Array<S>;
   export function indexed<T>(
     fn: PredIndexed<T, boolean>,
   ): (array: ReadonlyArray<T>) => Array<T>;
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_filter(true), arguments, filter.lazyIndexed);
   }
 
diff --git a/src/find.ts b/src/find.ts
index fa0c307..6353b89 100644
--- a/src/find.ts
+++ b/src/find.ts
@@ -1,7 +1,8 @@
-import { purry } from "./purry";
-import type { Pred, PredIndexedOptional, PredIndexed } from "./_types";
 import { _toLazyIndexed } from "./_toLazyIndexed";
 import { _toSingle } from "./_toSingle";
+import type { Pred, PredIndexed, PredIndexedOptional } from "./_types";
+import type { LazyEvaluator } from "./pipe";
+import { purry } from "./purry";
 
 /**
  * Returns the value of the first element in the array where predicate is true, and undefined otherwise.
@@ -47,31 +48,24 @@ export function find<T = never>(
   fn: Pred<T, boolean>,
 ): (array: ReadonlyArray<T>) => T | undefined;
 
-export function find() {
+export function find(): unknown {
   return purry(_find(false), arguments, find.lazy);
 }
 
 const _find =
   (indexed: boolean) =>
-  <T>(array: Array<T>, fn: PredIndexedOptional<T, boolean>) => {
-    if (indexed) {
-      return array.find(fn);
-    }
-
-    return array.find((x) => fn(x));
-  };
+  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, boolean>) =>
+    array.find((item, index, input) =>
+      indexed ? fn(item, index, input) : fn(item),
+    );
 
 const _lazy =
   (indexed: boolean) =>
-  <T>(fn: PredIndexedOptional<T, boolean>) =>
-  (value: T, index?: number, array?: Array<T>) => {
-    const valid = indexed ? fn(value, index, array) : fn(value);
-    return {
-      done: valid,
-      hasNext: valid,
-      next: value,
-    };
-  };
+  <T>(fn: PredIndexedOptional<T, boolean>): LazyEvaluator<T> =>
+  (value, index, array) =>
+    (indexed ? fn(value, index, array) : fn(value))
+      ? { done: true, hasNext: true, next: value }
+      : { done: false, hasNext: false };
 
 export namespace find {
   export function indexed<T>(
@@ -81,7 +75,7 @@ export namespace find {
   export function indexed<T>(
     fn: PredIndexed<T, boolean>,
   ): (array: ReadonlyArray<T>) => T | undefined;
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_find(true), arguments, find.lazyIndexed);
   }
 
diff --git a/src/findIndex.ts b/src/findIndex.ts
index 94dc0b7..e14220c 100644
--- a/src/findIndex.ts
+++ b/src/findIndex.ts
@@ -1,7 +1,8 @@
-import { purry } from "./purry";
-import type { Pred, PredIndexedOptional, PredIndexed } from "./_types";
 import { _toLazyIndexed } from "./_toLazyIndexed";
 import { _toSingle } from "./_toSingle";
+import type { Pred, PredIndexed, PredIndexedOptional } from "./_types";
+import type { LazyEvaluator } from "./pipe";
+import { purry } from "./purry";
 
 /**
  * Returns the index of the first element in the array where predicate is true, and -1 otherwise.
@@ -48,38 +49,28 @@ export function findIndex<T>(
   fn: Pred<T, boolean>,
 ): (array: ReadonlyArray<T>) => number;
 
-export function findIndex() {
+export function findIndex(): unknown {
   return purry(_findIndex(false), arguments, findIndex.lazy);
 }
 
 const _findIndex =
   (indexed: boolean) =>
-  <T>(array: Array<T>, fn: PredIndexedOptional<T, boolean>) => {
-    if (indexed) {
-      return array.findIndex(fn);
-    }
-
-    return array.findIndex((x) => fn(x));
-  };
+  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, boolean>) =>
+    array.findIndex((item, index, input) =>
+      indexed ? fn(item, index, input) : fn(item),
+    );
 
 const _lazy =
   (indexed: boolean) =>
-  <T>(fn: PredIndexedOptional<T, boolean>) => {
-    let i = 0;
-    return (value: T, index?: number, array?: Array<T>) => {
-      const valid = indexed ? fn(value, index, array) : fn(value);
-      if (valid) {
-        return {
-          done: true,
-          hasNext: true,
-          next: i,
-        };
+  <T>(fn: PredIndexedOptional<T, boolean>): LazyEvaluator<T, number> => {
+    // TODO: We use the `actualIndex` here because we can't trust the index coming from pipe. This is due to the fact that the `indexed` abstraction might turn off incrementing the index or not send it at all. Once we simplify the code base by removing the non-indexed versions, we can remove this.
+    let actualIndex = 0;
+    return (value, index, array) => {
+      if (indexed ? fn(value, index, array) : fn(value)) {
+        return { done: true, hasNext: true, next: actualIndex };
       }
-      i++;
-      return {
-        done: false,
-        hasNext: false,
-      };
+      actualIndex += 1;
+      return { done: false, hasNext: false };
     };
   };
 
@@ -91,7 +82,7 @@ export namespace findIndex {
   export function indexed<T>(
     fn: PredIndexed<T, boolean>,
   ): (array: ReadonlyArray<T>) => number;
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_findIndex(true), arguments, findIndex.lazyIndexed);
   }
 
diff --git a/src/findLast.ts b/src/findLast.ts
index 2262dba..488b04f 100644
--- a/src/findLast.ts
+++ b/src/findLast.ts
@@ -47,19 +47,19 @@ export function findLast<T = never>(
   fn: Pred<T, boolean>,
 ): (array: ReadonlyArray<T>) => T | undefined;
 
-export function findLast() {
+export function findLast(): unknown {
   return purry(_findLast(false), arguments);
 }
 
 const _findLast =
   (indexed: boolean) =>
-  <T>(array: Array<T>, fn: PredIndexedOptional<T, boolean>) => {
+  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, boolean>) => {
     for (let i = array.length - 1; i >= 0; i--) {
       if (indexed ? fn(array[i]!, i, array) : fn(array[i]!)) {
         return array[i];
       }
     }
-    return undefined;
+    return;
   };
 
 export namespace findLast {
@@ -71,7 +71,7 @@ export namespace findLast {
     fn: PredIndexed<T, boolean>,
   ): (array: ReadonlyArray<T>) => T | undefined;
 
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_findLast(true), arguments);
   }
 }
diff --git a/src/findLastIndex.ts b/src/findLastIndex.ts
index dcee55f..f3c19be 100644
--- a/src/findLastIndex.ts
+++ b/src/findLastIndex.ts
@@ -46,13 +46,13 @@ export function findLastIndex<T>(
   fn: Pred<T, boolean>,
 ): (array: ReadonlyArray<T>) => number;
 
-export function findLastIndex() {
+export function findLastIndex(): unknown {
   return purry(_findLastIndex(false), arguments);
 }
 
 const _findLastIndex =
   (indexed: boolean) =>
-  <T>(array: Array<T>, fn: PredIndexedOptional<T, boolean>) => {
+  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, boolean>) => {
     for (let i = array.length - 1; i >= 0; i--) {
       if (indexed ? fn(array[i]!, i, array) : fn(array[i]!)) {
         return i;
@@ -71,7 +71,7 @@ export namespace findLastIndex {
     fn: PredIndexed<T, boolean>,
   ): (array: ReadonlyArray<T>) => number;
 
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_findLastIndex(true), arguments);
   }
 }
diff --git a/src/first.ts b/src/first.ts
index f28b2a1..6397359 100644
--- a/src/first.ts
+++ b/src/first.ts
@@ -1,4 +1,5 @@
 import type { IterableContainer } from "./_types";
+import type { LazyEvaluator } from "./pipe";
 import { purry } from "./purry";
 
 type First<T extends IterableContainer> = T extends []
@@ -45,21 +46,17 @@ export function first<T extends IterableContainer>(data: T): First<T>;
  */
 export function first(): <T extends IterableContainer>(data: T) => First<T>;
 
-export function first() {
+export function first(): unknown {
   return purry(_first, arguments, first.lazy);
 }
 
-function _first<T>([first]: ReadonlyArray<T>) {
-  return first;
+function _first<T>([item]: ReadonlyArray<T>): T | undefined {
+  return item;
 }
 
 export namespace first {
-  export function lazy<T>() {
-    return (value: T) => ({
-      done: true,
-      hasNext: true,
-      next: value,
-    });
+  export function lazy<T>(): LazyEvaluator<T> {
+    return (value) => ({ done: true, hasNext: true, next: value });
   }
 
   export namespace lazy {
diff --git a/src/flatMap.ts b/src/flatMap.ts
index aac62d8..03b133b 100644
--- a/src/flatMap.ts
+++ b/src/flatMap.ts
@@ -1,4 +1,5 @@
 import { flatten } from "./flatten";
+import type { LazyEvaluator } from "./pipe";
 import { purry } from "./purry";
 
 /**
@@ -34,34 +35,25 @@ export function flatMap<T, K>(
   fn: (input: T) => K | ReadonlyArray<K>,
 ): (array: ReadonlyArray<T>) => Array<K>;
 
-export function flatMap() {
+export function flatMap(): unknown {
   return purry(_flatMap, arguments, flatMap.lazy);
 }
 
 function _flatMap<T, K>(
-  array: Array<T>,
+  array: ReadonlyArray<T>,
   fn: (input: T) => ReadonlyArray<K>,
 ): Array<K> {
   return flatten(array.map((item) => fn(item)));
 }
 
 export namespace flatMap {
-  export function lazy<T, K>(fn: (input: T) => K | ReadonlyArray<K>) {
-    return (value: T) => {
+  export const lazy =
+    <T, K>(fn: (input: T) => K | ReadonlyArray<K>): LazyEvaluator<T, K> =>
+    // @ts-expect-error [ts2322] - We need to make LazyMany better so it accommodate the typing here...
+    (value) => {
       const next = fn(value);
-      if (Array.isArray(next)) {
-        return {
-          done: false,
-          hasNext: true,
-          hasMany: true,
-          next,
-        };
-      }
-      return {
-        done: false,
-        hasNext: true,
-        next,
-      };
+      return Array.isArray(next)
+        ? { done: false, hasNext: true, hasMany: true, next }
+        : { done: false, hasNext: true, next };
     };
-  }
 }
diff --git a/src/flatMapToObj.ts b/src/flatMapToObj.ts
index 2f92843..b4e3585 100644
--- a/src/flatMapToObj.ts
+++ b/src/flatMapToObj.ts
@@ -53,7 +53,7 @@ export function flatMapToObj<T, K extends PropertyKey, V>(
   fn: (element: T) => Array<[K, V]>,
 ): (array: ReadonlyArray<T>) => Record<K, V>;
 
-export function flatMapToObj() {
+export function flatMapToObj(): unknown {
   return purry(_flatMapToObj(false), arguments);
 }
 
@@ -63,16 +63,21 @@ const _flatMapToObj =
     array: ReadonlyArray<T>,
     fn: PredIndexedOptional<
       T,
-      ReadonlyArray<[key: PropertyKey, value: unknown]>
+      ReadonlyArray<readonly [key: PropertyKey, value: unknown]>
     >,
-  ) =>
-    array.reduce<Record<PropertyKey, unknown>>((result, element, index) => {
+  ) => {
+    const out: Record<PropertyKey, unknown> = {};
+    for (let index = 0; index < array.length; index++) {
+      // TODO: Once we bump the TypeScript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value at the same time.
+      const element = array[index]!;
       const items = indexed ? fn(element, index, array) : fn(element);
-      items.forEach(([key, value]) => {
-        result[key] = value;
-      });
-      return result;
-    }, {});
+
+      for (const [key, value] of items) {
+        out[key] = value;
+      }
+    }
+    return out;
+  };
 
 export namespace flatMapToObj {
   export function indexed<T, K extends PropertyKey, V>(
@@ -82,7 +87,7 @@ export namespace flatMapToObj {
   export function indexed<T, K extends PropertyKey, V>(
     fn: (element: T, index: number, array: ReadonlyArray<T>) => Array<[K, V]>,
   ): (array: ReadonlyArray<T>) => Record<K, V>;
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_flatMapToObj(true), arguments);
   }
 }
diff --git a/src/flatten.ts b/src/flatten.ts
index c75f417..e79dee7 100644
--- a/src/flatten.ts
+++ b/src/flatten.ts
@@ -1,5 +1,5 @@
-import type { LazyResult } from "./_reduceLazy";
 import { _reduceLazy } from "./_reduceLazy";
+import type { LazyEvaluator } from "./pipe";
 import { purry } from "./purry";
 
 type Flatten<T> = T extends ReadonlyArray<infer K> ? K : T;
@@ -35,30 +35,21 @@ export function flatten<T>(items: ReadonlyArray<T>): Array<Flatten<T>>;
  */
 export function flatten<T>(): (items: ReadonlyArray<T>) => Array<Flatten<T>>;
 
-export function flatten() {
+export function flatten(): unknown {
   return purry(_flatten, arguments, flatten.lazy);
 }
 
-function _flatten<T>(items: Array<T>) {
+function _flatten<T>(items: ReadonlyArray<T>): Array<Flatten<T>> {
   return _reduceLazy(items, flatten.lazy());
 }
 
 export namespace flatten {
-  export function lazy<T>() {
-    return (next: T): LazyResult<unknown> => {
-      if (Array.isArray(next)) {
-        return {
-          done: false,
-          hasNext: true,
-          hasMany: true,
-          next,
-        };
-      }
-      return {
-        done: false,
-        hasNext: true,
-        next,
-      };
-    };
-  }
+  export const lazy =
+    <T>(): LazyEvaluator<T, Flatten<T>> =>
+    // eslint-disable-next-line unicorn/consistent-function-scoping -- I tried pulling the function out but I couldn't get the `<T>` to get inferred correctly.
+    (item) =>
+      // @ts-expect-error [ts2322] - We need to make LazyMany better so it accommodate the typing here...
+      Array.isArray(item)
+        ? { done: false, hasNext: true, hasMany: true, next: item }
+        : { done: false, hasNext: true, next: item };
 }
diff --git a/src/flattenDeep.ts b/src/flattenDeep.ts
index 940c8a5..591ecc2 100644
--- a/src/flattenDeep.ts
+++ b/src/flattenDeep.ts
@@ -1,7 +1,7 @@
-/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-return -- FIXME! */
+/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-return, @typescript-eslint/prefer-readonly-parameter-types -- FIXME! */
 
-import type { LazyResult } from "./_reduceLazy";
 import { _reduceLazy } from "./_reduceLazy";
+import type { LazyEvaluator } from "./pipe";
 import { purry } from "./purry";
 
 type FlattenDeep<T> = T extends ReadonlyArray<infer K> ? FlattenDeep2<K> : T;
@@ -39,11 +39,11 @@ export function flattenDeep<T>(): (
   items: ReadonlyArray<T>,
 ) => Array<FlattenDeep<T>>;
 
-export function flattenDeep() {
+export function flattenDeep(): unknown {
   return purry(_flattenDeep, arguments, flattenDeep.lazy);
 }
 
-function _flattenDeep<T>(items: Array<T>): Array<FlattenDeep<T>> {
+function _flattenDeep<T>(items: ReadonlyArray<T>): Array<FlattenDeep<T>> {
   return _reduceLazy(items, flattenDeep.lazy());
 }
 
@@ -52,33 +52,26 @@ function _flattenDeepValue<T>(value: Array<T> | T): Array<FlattenDeep<T>> | T {
     return value;
   }
   const ret: Array<any> = [];
-  value.forEach((item) => {
+
+  for (const item of value) {
     if (Array.isArray(item)) {
       ret.push(...flattenDeep(item));
     } else {
       ret.push(item);
     }
-  });
+  }
+
   return ret;
 }
 
 export namespace flattenDeep {
-  export function lazy() {
-    return (value: any): LazyResult<any> => {
+  export const lazy =
+    <T>(): LazyEvaluator<T, any> =>
+    // eslint-disable-next-line unicorn/consistent-function-scoping -- I tried pulling the function out but I couldn't get the `<T>` to get inferred correctly.
+    (value) => {
       const next = _flattenDeepValue(value);
-      if (Array.isArray(next)) {
-        return {
-          done: false,
-          hasNext: true,
-          hasMany: true,
-          next,
-        };
-      }
-      return {
-        done: false,
-        hasNext: true,
-        next,
-      };
+      return Array.isArray(next)
+        ? { done: false, hasNext: true, hasMany: true, next }
+        : { done: false, hasNext: true, next };
     };
-  }
 }
diff --git a/src/floor.ts b/src/floor.ts
index fdccf35..fe4a0f8 100644
--- a/src/floor.ts
+++ b/src/floor.ts
@@ -37,6 +37,6 @@ export function floor(value: number, precision: number): number;
  */
 export function floor(precision: number): (value: number) => number;
 
-export function floor() {
+export function floor(): unknown {
   return purry(_withPrecision(Math.floor), arguments);
 }
diff --git a/src/forEach.ts b/src/forEach.ts
index ce60044..99c94f1 100644
--- a/src/forEach.ts
+++ b/src/forEach.ts
@@ -1,8 +1,8 @@
-import { purry } from "./purry";
-import type { LazyResult } from "./_reduceLazy";
 import { _reduceLazy } from "./_reduceLazy";
 import { _toLazyIndexed } from "./_toLazyIndexed";
-import type { Pred, PredIndexedOptional, PredIndexed } from "./_types";
+import type { Pred, PredIndexed, PredIndexedOptional } from "./_types";
+import type { LazyEvaluator } from "./pipe";
+import { purry } from "./purry";
 
 /**
  * Iterate an array using a defined callback function. The original array is returned instead of `void`.
@@ -57,7 +57,7 @@ export function forEach<T>(
   fn: Pred<T, void>,
 ): (array: ReadonlyArray<T>) => Array<T>;
 
-export function forEach() {
+export function forEach(): unknown {
   return purry(_forEach(false), arguments, forEach.lazy);
 }
 
@@ -72,8 +72,8 @@ const _forEach =
 
 const _lazy =
   (indexed: boolean) =>
-  <T>(fn: PredIndexedOptional<T, void>) =>
-  (value: T, index?: number, array?: ReadonlyArray<T>): LazyResult<T> => {
+  <T>(fn: PredIndexedOptional<T, void>): LazyEvaluator<T> =>
+  (value, index, array) => {
     if (indexed) {
       fn(value, index, array);
     } else {
@@ -94,7 +94,7 @@ export namespace forEach {
   export function indexed<T>(
     fn: PredIndexed<T, void>,
   ): (array: ReadonlyArray<T>) => Array<T>;
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_forEach(true), arguments, forEach.lazyIndexed);
   }
   export const lazy = _lazy(false);
diff --git a/src/forEachObj.ts b/src/forEachObj.ts
index 2f4e30d..5e68bb6 100644
--- a/src/forEachObj.ts
+++ b/src/forEachObj.ts
@@ -51,7 +51,7 @@ export function forEachObj<T extends Record<PropertyKey, unknown>>(
   fn: UnindexedIteratee<T>,
 ): (object: T) => T;
 
-export function forEachObj() {
+export function forEachObj(): unknown {
   return purry(_forEachObj(false), arguments);
 }
 
@@ -67,7 +67,7 @@ const _forEachObj =
   ) => {
     for (const key in data) {
       if (Object.prototype.hasOwnProperty.call(data, key)) {
-        const val = data[key];
+        const { [key]: val } = data;
         if (indexed) {
           fn(val, key, data);
         } else {
@@ -86,7 +86,7 @@ export namespace forEachObj {
   export function indexed<T extends Record<PropertyKey, unknown>>(
     fn: IndexedIteratee<T, keyof T>,
   ): (object: T) => T;
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_forEachObj(true), arguments);
   }
 }
diff --git a/src/fromPairs.ts b/src/fromPairs.ts
index 6d94f3a..0807b04 100644
--- a/src/fromPairs.ts
+++ b/src/fromPairs.ts
@@ -68,7 +68,7 @@ export function fromPairs<V>(
 //   pairs: ReadonlyArray<Entry<K, V>>,
 // ) => Record<K extends string ? string : K extends number ? number : never, V>;
 
-export function fromPairs() {
+export function fromPairs(): unknown {
   // TODO: When we bump the typescript target beyond ES2019 we can use Object.fromEntries directly here instead of our user-space implementation.
   return purry(fromPairsImplementation, arguments);
 }
diff --git a/src/groupBy.ts b/src/groupBy.ts
index 6bfd1c4..ddc0473 100644
--- a/src/groupBy.ts
+++ b/src/groupBy.ts
@@ -38,21 +38,25 @@ export function groupBy<T>(
  * @indexed
  * @category Array
  */
-export function groupBy() {
+export function groupBy(): unknown {
   return purry(_groupBy(false), arguments);
 }
 
 const _groupBy =
   (indexed: boolean) =>
   <T, Key extends PropertyKey = PropertyKey>(
-    array: Array<T>,
+    array: ReadonlyArray<T>,
     fn: PredIndexedOptional<T, Key | undefined>,
   ) => {
     const ret: Record<string, Array<T>> = {};
-    array.forEach((item, index) => {
+
+    for (let index = 0; index < array.length; index++) {
+      // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
+      const item = array[index]!;
       const key = indexed ? fn(item, index, array) : fn(item);
       if (key !== undefined) {
         const actualKey = String(key);
+        // eslint-disable-next-line @typescript-eslint/prefer-destructuring
         let items = ret[actualKey];
         if (items === undefined) {
           items = [];
@@ -60,7 +64,8 @@ const _groupBy =
         }
         items.push(item);
       }
-    });
+    }
+
     return ret;
   };
 
@@ -120,7 +125,7 @@ export namespace groupBy {
   export function indexed<T>(
     fn: PredIndexed<T, PropertyKey | undefined>,
   ): (array: ReadonlyArray<T>) => Record<string, NonEmptyArray<T>>;
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_groupBy(true), arguments);
   }
 
diff --git a/src/indexBy.ts b/src/indexBy.ts
index a880c2b..a2700d7 100644
--- a/src/indexBy.ts
+++ b/src/indexBy.ts
@@ -51,19 +51,23 @@ export function indexBy<T>(
   fn: (item: T) => unknown,
 ): (array: ReadonlyArray<T>) => Record<string, T>;
 
-export function indexBy() {
+export function indexBy(): unknown {
   return purry(_indexBy(false), arguments);
 }
 
 const _indexBy =
   (indexed: boolean) =>
-  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, unknown>) =>
-    array.reduce<Record<string, T>>((ret, item, index) => {
+  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, unknown>) => {
+    const out: Record<string, T> = {};
+    for (let index = 0; index < array.length; index++) {
+      // TODO: Once we bump our Typescript target version we can use Array.prototype.entries to iterate over the elements and index at the same time.
+      const item = array[index]!;
       const value = indexed ? fn(item, index, array) : fn(item);
       const key = String(value);
-      ret[key] = item;
-      return ret;
-    }, {});
+      out[key] = item;
+    }
+    return out;
+  };
 
 function indexByStrict<K extends PropertyKey, T>(
   array: ReadonlyArray<T>,
@@ -74,19 +78,23 @@ function indexByStrict<K extends PropertyKey, T>(
   fn: (item: T) => K,
 ): (array: ReadonlyArray<T>) => Partial<Record<K, T>>;
 
-function indexByStrict() {
+function indexByStrict(): unknown {
   return purry(_indexByStrict, arguments);
 }
 
-const _indexByStrict = <K extends PropertyKey, T>(
+function _indexByStrict<K extends PropertyKey, T>(
   array: ReadonlyArray<T>,
   fn: (item: T) => K,
-) =>
-  array.reduce<Partial<Record<K, T>>>((ret, item) => {
+): Partial<Record<K, T>> {
+  const out: Partial<Record<K, T>> = {};
+
+  for (const item of array) {
     const key = fn(item);
-    ret[key] = item;
-    return ret;
-  }, {});
+    out[key] = item;
+  }
+
+  return out;
+}
 
 export namespace indexBy {
   export function indexed<T>(
@@ -96,7 +104,7 @@ export namespace indexBy {
   export function indexed<T>(
     fn: PredIndexed<T, unknown>,
   ): (array: ReadonlyArray<T>) => Record<string, T>;
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_indexBy(true), arguments);
   }
   export const strict = indexByStrict;
diff --git a/src/intersection.ts b/src/intersection.ts
index e1b672f..c63b8a3 100644
--- a/src/intersection.ts
+++ b/src/intersection.ts
@@ -1,6 +1,6 @@
-import { purry } from "./purry";
-import type { LazyResult } from "./_reduceLazy";
 import { _reduceLazy } from "./_reduceLazy";
+import type { LazyEvaluator } from "./pipe";
+import { purry } from "./purry";
 
 /**
  * Returns a list of elements that exist in both array.
@@ -35,30 +35,24 @@ export function intersection<T, K>(
   other: ReadonlyArray<T>,
 ): (source: ReadonlyArray<K>) => Array<T>;
 
-export function intersection() {
+export function intersection(): unknown {
   return purry(_intersection, arguments, intersection.lazy);
 }
 
-function _intersection<T>(array: Array<T>, other: Array<T>) {
+function _intersection<T>(
+  array: ReadonlyArray<T>,
+  other: ReadonlyArray<T>,
+): Array<T> {
   const lazy = intersection.lazy(other);
   return _reduceLazy(array, lazy);
 }
 
 export namespace intersection {
-  export function lazy<T>(other: Array<T>) {
-    return (value: T): LazyResult<T> => {
-      const set = new Set(other);
-      if (set.has(value)) {
-        return {
-          done: false,
-          hasNext: true,
-          next: value,
-        };
-      }
-      return {
-        done: false,
-        hasNext: false,
-      };
-    };
+  export function lazy<T>(other: ReadonlyArray<T>): LazyEvaluator<T> {
+    const set = new Set(other);
+    return (value) =>
+      set.has(value)
+        ? { done: false, hasNext: true, next: value }
+        : { done: false, hasNext: false };
   }
 }
diff --git a/src/intersectionWith.ts b/src/intersectionWith.ts
index d0d0c3a..7491bef 100644
--- a/src/intersectionWith.ts
+++ b/src/intersectionWith.ts
@@ -1,5 +1,5 @@
-import type { LazyResult } from "./_reduceLazy";
 import { _reduceLazy } from "./_reduceLazy";
+import type { LazyEvaluator } from "./pipe";
 import { purry } from "./purry";
 
 type Comparator<TFirst, TSecond> = (a: TFirst, b: TSecond) => boolean;
@@ -59,36 +59,27 @@ export function intersectionWith<TFirst, TSecond>(
   comparator: Comparator<TFirst, TSecond>,
 ): (array: ReadonlyArray<TFirst>) => Array<TFirst>;
 
-export function intersectionWith() {
+export function intersectionWith(): unknown {
   return purry(_intersectionWith, arguments, intersectionWith.lazy);
 }
 
 function _intersectionWith<TFirst, TSecond>(
-  array: Array<TFirst>,
-  other: Array<TSecond>,
+  array: ReadonlyArray<TFirst>,
+  other: ReadonlyArray<TSecond>,
   comparator: Comparator<TFirst, TSecond>,
-) {
+): Array<TFirst> {
   const lazy = intersectionWith.lazy(other, comparator);
   return _reduceLazy(array, lazy);
 }
 
 export namespace intersectionWith {
-  export function lazy<TFirst, TSecond>(
-    other: Array<TSecond>,
-    comparator: Comparator<TFirst, TSecond>,
-  ) {
-    return (value: TFirst): LazyResult<TFirst> => {
-      if (other.some((otherValue) => comparator(value, otherValue))) {
-        return {
-          done: false,
-          hasNext: true,
-          next: value,
-        };
-      }
-      return {
-        done: false,
-        hasNext: false,
-      };
-    };
-  }
+  export const lazy =
+    <TFirst, TSecond>(
+      other: ReadonlyArray<TSecond>,
+      comparator: Comparator<TFirst, TSecond>,
+    ): LazyEvaluator<TFirst> =>
+    (value) =>
+      other.some((otherValue) => comparator(value, otherValue))
+        ? { done: false, hasNext: true, next: value }
+        : { done: false, hasNext: false };
 }
diff --git a/src/invert.ts b/src/invert.ts
index 2a2537a..70b3a95 100644
--- a/src/invert.ts
+++ b/src/invert.ts
@@ -32,7 +32,7 @@ export function invert<T extends object>(object: T): Inverted<T>;
  */
 export function invert<T extends object>(): (object: T) => Inverted<T>;
 
-export function invert() {
+export function invert(): unknown {
   return purry(_invert, arguments);
 }
 
@@ -42,7 +42,10 @@ function _invert(
   const result: Record<PropertyKey, PropertyKey> = {};
 
   for (const key in object) {
-    result[object[key]!] = key;
+    // @see https://eslint.org/docs/latest/rules/guard-for-in
+    if (Object.prototype.hasOwnProperty.call(object, key)) {
+      result[object[key]!] = key;
+    }
   }
 
   return result;
diff --git a/src/isDefined.ts b/src/isDefined.ts
index 65ee73e..258df0c 100644
--- a/src/isDefined.ts
+++ b/src/isDefined.ts
@@ -16,7 +16,7 @@
  * @strict
  */
 export function isDefined<T>(data: T): data is NonNullable<T> {
-  return typeof data !== "undefined" && data !== null;
+  return data !== undefined && data !== null;
 }
 
 export namespace isDefined {
diff --git a/src/isEmpty.ts b/src/isEmpty.ts
index 1195d74..d0d2d84 100644
--- a/src/isEmpty.ts
+++ b/src/isEmpty.ts
@@ -1,3 +1,4 @@
+import type { IterableContainer } from "./_types";
 import { isArray } from "./isArray";
 import { isObject } from "./isObject";
 import { isString } from "./isString";
@@ -32,7 +33,7 @@ export function isEmpty<T extends string | undefined>(
 ): data is
   | ("" extends T ? "" : never)
   | (undefined extends T ? undefined : never);
-export function isEmpty(data: ReadonlyArray<unknown> | []): data is [];
+export function isEmpty(data: IterableContainer): data is [];
 export function isEmpty<T extends Readonly<Record<PropertyKey, unknown>>>(
   data: T,
 ): data is Record<keyof T, never>;
diff --git a/src/isNil.ts b/src/isNil.ts
index 31d52c7..9d567da 100644
--- a/src/isNil.ts
+++ b/src/isNil.ts
@@ -11,5 +11,5 @@
  * @category Guard
  */
 export function isNil<T>(data: T): data is Extract<T, null | undefined> {
-  return data == null;
+  return data === null || data === undefined;
 }
diff --git a/src/isNot.ts b/src/isNot.ts
index 0ed455f..7a39131 100644
--- a/src/isNot.ts
+++ b/src/isNot.ts
@@ -16,5 +16,5 @@ export function isNot<T, S extends T>(
 export function isNot<T>(predicate: (data: T) => boolean): (data: T) => boolean;
 
 export function isNot<T>(predicate: (data: T) => boolean) {
-  return (data: T) => !predicate(data);
+  return (data: T): boolean => !predicate(data);
 }
diff --git a/src/isPlainObject.ts b/src/isPlainObject.ts
index eac4aba..c8beb5a 100644
--- a/src/isPlainObject.ts
+++ b/src/isPlainObject.ts
@@ -26,7 +26,7 @@ import type { NarrowedTo } from "./_types";
  * @category Guard
  */
 export function isPlainObject<T>(
-  data: Record<PropertyKey, unknown> | T,
+  data: Readonly<Record<PropertyKey, unknown>> | T,
 ): data is NarrowedTo<T, Record<PropertyKey, unknown>> {
   if (typeof data !== "object" || data === null) {
     return false;
diff --git a/src/isTruthy.ts b/src/isTruthy.ts
index 0f35d98..b4ff679 100644
--- a/src/isTruthy.ts
+++ b/src/isTruthy.ts
@@ -13,6 +13,7 @@
  *    R.isTruthy('') //=> false
  * @category Guard
  */
+// eslint-disable-next-line unicorn/prefer-native-coercion-functions -- This is not entirely correct, our isTruthy also coerces the type.
 export function isTruthy<T>(
   data: T,
 ): data is Exclude<T, "" | 0 | false | null | undefined> {
diff --git a/src/keys.ts b/src/keys.ts
index 0c9fca2..f62ed92 100644
--- a/src/keys.ts
+++ b/src/keys.ts
@@ -25,7 +25,7 @@ import { purry } from "./purry";
  * @dataFirst
  */
 export function keys(
-  source: ArrayLike<unknown> | Record<PropertyKey, unknown>,
+  source: ArrayLike<unknown> | Readonly<Record<PropertyKey, unknown>>,
 ): Array<string>;
 
 /**
@@ -53,7 +53,7 @@ export function keys(
 //   source: Record<PropertyKey, unknown> | ArrayLike<unknown>,
 // ) => Array<string>;
 
-export function keys() {
+export function keys(): unknown {
   return purry(Object.keys, arguments);
 }
 
diff --git a/src/last.ts b/src/last.ts
index 70ea797..cd37f7e 100644
--- a/src/last.ts
+++ b/src/last.ts
@@ -13,7 +13,7 @@ import { purry } from "./purry";
  * @pipeable
  * @dataFirst
  */
-export function last<T>(array: NonEmptyArray<T>): T;
+export function last<T>(array: Readonly<NonEmptyArray<T>>): T;
 export function last<T>(array: ReadonlyArray<T>): T | undefined;
 
 /**
@@ -34,10 +34,10 @@ export function last<T>(array: ReadonlyArray<T>): T | undefined;
  */
 export function last<T>(): (array: ReadonlyArray<T>) => T | undefined;
 
-export function last() {
+export function last(): unknown {
   return purry(_last, arguments);
 }
 
-function _last<T>(array: Array<T>) {
+function _last<T>(array: ReadonlyArray<T>): T | undefined {
   return array[array.length - 1];
 }
diff --git a/src/length.ts b/src/length.ts
index 6a89206..6cee845 100644
--- a/src/length.ts
+++ b/src/length.ts
@@ -34,10 +34,10 @@ export function length<T>(): (items: Enumerable<T>) => number;
  *    R.pipe([1, 2, 3], R.length()) // => 3
  * @category Array
  */
-export function length() {
+export function length(): unknown {
   return purry(_length, arguments);
 }
 
-function _length<T>(items: Enumerable<T>) {
+function _length<T>(items: Enumerable<T>): number {
   return "length" in items ? items.length : Array.from(items).length;
 }
diff --git a/src/map.ts b/src/map.ts
index c1fe606..1bbdba3 100644
--- a/src/map.ts
+++ b/src/map.ts
@@ -1,5 +1,3 @@
-import { purry } from "./purry";
-import type { LazyResult } from "./_reduceLazy";
 import { _reduceLazy } from "./_reduceLazy";
 import { _toLazyIndexed } from "./_toLazyIndexed";
 import type {
@@ -8,6 +6,8 @@ import type {
   PredIndexed,
   PredIndexedOptional,
 } from "./_types";
+import type { LazyEvaluator } from "./pipe";
+import { purry } from "./purry";
 
 /**
  * Map each element of an array using a defined callback function. If the input
@@ -51,7 +51,7 @@ export function map<T, K>(
   fn: Pred<T, K>,
 ): (array: ReadonlyArray<T>) => Array<K>;
 
-export function map() {
+export function map(): unknown {
   return purry(_map(false), arguments, map.lazy);
 }
 
@@ -62,8 +62,8 @@ const _map =
 
 const _lazy =
   (indexed: boolean) =>
-  <T, K>(fn: PredIndexedOptional<T, K>) =>
-  (value: T, index?: number, array?: ReadonlyArray<T>): LazyResult<K> => ({
+  <T, K>(fn: PredIndexedOptional<T, K>): LazyEvaluator<T, K> =>
+  (value, index, array) => ({
     done: false,
     hasNext: true,
     next: indexed ? fn(value, index, array) : fn(value),
@@ -105,7 +105,7 @@ export namespace map {
   export function indexed<T, K>(
     fn: PredIndexed<T, K>,
   ): (array: ReadonlyArray<T>) => Array<K>;
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_map(true), arguments, map.lazyIndexed);
   }
 
diff --git a/src/mapKeys.ts b/src/mapKeys.ts
index 775ec7c..072485a 100644
--- a/src/mapKeys.ts
+++ b/src/mapKeys.ts
@@ -31,17 +31,18 @@ export function mapKeys<T, S extends PropertyKey>(
   fn: (key: keyof T, value: Required<T>[keyof T]) => S,
 ): (data: T) => Record<S, T[keyof T]>;
 
-export function mapKeys() {
+export function mapKeys(): unknown {
   return purry(_mapKeys, arguments);
 }
 
-function _mapKeys<T extends object>(
+function _mapKeys<T extends object, S extends PropertyKey>(
   data: T,
-  fn: (key: keyof T, value: Required<T>[keyof T]) => PropertyKey,
-) {
-  const out: Partial<Record<PropertyKey, Required<T>[keyof T]>> = {};
+  fn: (key: keyof T, value: Required<T>[keyof T]) => S,
+): Record<S, T[keyof T]> {
+  const out: Partial<Record<S, T[keyof T]>> = {};
   for (const [key, value] of toPairs.strict(data)) {
     out[fn(key, value)] = value;
   }
+  // @ts-expect-error [ts2322] - We build the object incrementally so the type can't represent the final object.
   return out;
 }
diff --git a/src/mapToObj.ts b/src/mapToObj.ts
index fa0f206..c77ad44 100644
--- a/src/mapToObj.ts
+++ b/src/mapToObj.ts
@@ -45,7 +45,7 @@ export function mapToObj<T, K extends PropertyKey, V>(
   fn: (element: T) => [K, V],
 ): (array: ReadonlyArray<T>) => Record<K, V>;
 
-export function mapToObj() {
+export function mapToObj(): unknown {
   return purry(_mapToObj(false), arguments);
 }
 
@@ -54,12 +54,19 @@ const _mapToObj =
   (
     array: ReadonlyArray<unknown>,
     fn: PredIndexedOptional<unknown, [PropertyKey, unknown]>,
-  ) =>
-    array.reduce<Record<PropertyKey, unknown>>((result, element, index) => {
+  ) => {
+    const out: Record<PropertyKey, unknown> = {};
+
+    for (let index = 0; index < array.length; index++) {
+      // TODO: Use Array.prototype.entries once we bump the Typescript target version to iterate over the elements and index at the same time.
+      // eslint-disable-next-line @typescript-eslint/prefer-destructuring
+      const element = array[index];
       const [key, value] = indexed ? fn(element, index, array) : fn(element);
-      result[key] = value;
-      return result;
-    }, {});
+      out[key] = value;
+    }
+
+    return out;
+  };
 
 export namespace mapToObj {
   export function indexed<T, K extends PropertyKey, V>(
@@ -69,7 +76,7 @@ export namespace mapToObj {
   export function indexed<T, K extends PropertyKey, V>(
     fn: (element: T, index: number, array: ReadonlyArray<T>) => [K, V],
   ): (array: ReadonlyArray<T>) => Record<K, V>;
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_mapToObj(true), arguments);
   }
 }
diff --git a/src/mapValues.ts b/src/mapValues.ts
index 4c12ea6..1782299 100644
--- a/src/mapValues.ts
+++ b/src/mapValues.ts
@@ -32,17 +32,21 @@ export function mapValues<T extends Record<PropertyKey, unknown>, S>(
   fn: (value: T[ObjectKeys<T>], key: ObjectKeys<T>) => S,
 ): (data: T) => Record<ObjectKeys<T>, S>;
 
-export function mapValues() {
+export function mapValues(): unknown {
   return purry(_mapValues, arguments);
 }
 
-function _mapValues<T extends object>(
+function _mapValues<T extends Record<PropertyKey, unknown>, S>(
   data: T,
-  fn: (value: Required<T>[keyof T], key: keyof T) => unknown,
-) {
-  const out: Partial<Record<keyof T, unknown>> = {};
+  fn: (value: T[ObjectKeys<T>], key: ObjectKeys<T>) => S,
+): Record<ObjectKeys<T>, S> {
+  const out: Partial<Record<ObjectKeys<T>, S>> = {};
   for (const [key, value] of toPairs.strict(data)) {
-    out[key] = fn(value, key);
+    // @ts-expect-error [ts2345] - FIXME!
+    const mappedValue = fn(value, key);
+    // @ts-expect-error [ts2536] - FIXME!
+    out[key] = mappedValue;
   }
+  // @ts-expect-error [ts2322] - We build the object incrementally so the type can't represent the final object.
   return out;
 }
diff --git a/src/maxBy.ts b/src/maxBy.ts
index 8fdb7d0..5ac96ac 100644
--- a/src/maxBy.ts
+++ b/src/maxBy.ts
@@ -3,16 +3,19 @@ import type { PredIndexed, PredIndexedOptional } from "./_types";
 
 const _maxBy =
   (indexed: boolean) =>
-  <T>(array: Array<T>, fn: PredIndexedOptional<T, number>) => {
-    let ret: T | undefined = undefined;
-    let retMax: number | undefined = undefined;
-    array.forEach((item, i) => {
-      const max = indexed ? fn(item, i, array) : fn(item);
+  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, number>) => {
+    let ret: T | undefined;
+    let retMax: number | undefined;
+
+    for (let index = 0; index < array.length; index++) {
+      // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
+      const item = array[index]!;
+      const max = indexed ? fn(item, index, array) : fn(item);
       if (retMax === undefined || max > retMax) {
         ret = item;
         retMax = max;
       }
-    });
+    }
 
     return ret;
   };
@@ -60,7 +63,7 @@ export function maxBy<T>(
   fn: (item: T) => number,
 ): T | undefined;
 
-export function maxBy() {
+export function maxBy(): unknown {
   return purry(_maxBy(false), arguments);
 }
 
@@ -72,7 +75,7 @@ export namespace maxBy {
   export function indexed<T>(
     fn: PredIndexed<T, number>,
   ): (array: ReadonlyArray<T>) => T | undefined;
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_maxBy(true), arguments);
   }
 }
diff --git a/src/meanBy.ts b/src/meanBy.ts
index aeb2887..dc1fa37 100644
--- a/src/meanBy.ts
+++ b/src/meanBy.ts
@@ -3,15 +3,18 @@ import type { PredIndexed, PredIndexedOptional } from "./_types";
 
 const _meanBy =
   (indexed: boolean) =>
-  <T>(array: Array<T>, fn: PredIndexedOptional<T, number>) => {
+  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, number>) => {
     if (array.length === 0) {
       return NaN;
     }
 
     let sum = 0;
-    array.forEach((item, i) => {
-      sum += indexed ? fn(item, i, array) : fn(item);
-    });
+
+    for (let index = 0; index < array.length; index++) {
+      // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
+      const item = array[index]!;
+      sum += indexed ? fn(item, index, array) : fn(item);
+    }
 
     return sum / array.length;
   };
@@ -58,7 +61,7 @@ export function meanBy<T>(
   fn: (item: T) => number,
 ): number;
 
-export function meanBy() {
+export function meanBy(): unknown {
   return purry(_meanBy(false), arguments);
 }
 
@@ -72,7 +75,7 @@ export namespace meanBy {
     fn: PredIndexed<T, number>,
   ): (array: ReadonlyArray<T>) => number;
 
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_meanBy(true), arguments);
   }
 }
diff --git a/src/merge.ts b/src/merge.ts
index 7ab746e..e6e525b 100644
--- a/src/merge.ts
+++ b/src/merge.ts
@@ -26,10 +26,10 @@ export function merge<A, B>(a: A, b: B): A & B;
  */
 export function merge<A, B>(b: B): (a: A) => A & B;
 
-export function merge() {
+export function merge(): unknown {
   return purry(_merge, arguments);
 }
 
-function _merge<A, B>(a: A, b: B) {
+function _merge<A, B>(a: A, b: B): A & B {
   return { ...a, ...b };
 }
diff --git a/src/mergeAll.ts b/src/mergeAll.ts
index 0f43056..8a485b3 100644
--- a/src/mergeAll.ts
+++ b/src/mergeAll.ts
@@ -14,10 +14,16 @@ export function mergeAll<A, B, C, D>(
   array: readonly [A, B, C, D],
 ): A & B & C & D;
 export function mergeAll<A, B, C, D, E>(
-  array: [A, B, C, D, E],
+  array: readonly [A, B, C, D, E],
 ): A & B & C & D & E;
 export function mergeAll(array: ReadonlyArray<object>): object;
 
-export function mergeAll(items: ReadonlyArray<object>) {
-  return items.reduce((acc, x) => ({ ...acc, ...x }), {});
+export function mergeAll(items: ReadonlyArray<object>): object {
+  let out = {};
+
+  for (const item of items) {
+    out = { ...out, ...item };
+  }
+
+  return out;
 }
diff --git a/src/mergeDeep.ts b/src/mergeDeep.ts
index 5fa5669..369799e 100644
--- a/src/mergeDeep.ts
+++ b/src/mergeDeep.ts
@@ -40,14 +40,14 @@ export function mergeDeep<
   Source extends Record<string, unknown>,
 >(source: Source): (target: Destination) => MergeDeep<Destination, Source>;
 
-export function mergeDeep() {
+export function mergeDeep(): unknown {
   return purry(mergeDeepImplementation, arguments);
 }
 
 function mergeDeepImplementation<
   Destination extends Record<string, unknown>,
   Source extends Record<string, unknown>,
->(destination: Destination, source: Source) {
+>(destination: Destination, source: Source): MergeDeep<Destination, Source> {
   // At this point the output is already merged, simply not deeply merged.
   const output = { ...destination, ...source } as Record<
     keyof Destination | keyof Source,
@@ -62,7 +62,7 @@ function mergeDeepImplementation<
       continue;
     }
 
-    const destinationValue = destination[key];
+    const { [key]: destinationValue } = destination;
     if (!isRecord(destinationValue)) {
       // The value in destination is not a mergable object so the value from
       // source (which was already copied in the shallow merge) would be used
@@ -70,7 +70,7 @@ function mergeDeepImplementation<
       continue;
     }
 
-    const sourceValue = source[key];
+    const { [key]: sourceValue } = source;
     if (!isRecord(sourceValue)) {
       // The value in source is not a mergable object either, so it will
       // override the object in destination.
@@ -82,6 +82,7 @@ function mergeDeepImplementation<
     output[key] = mergeDeepImplementation(destinationValue, sourceValue);
   }
 
+  // @ts-expect-error [ts2322] - We build the output object iteratively, I don't think it's possible to improve the types here so that typescript infers the right type.
   return output;
 }
 
diff --git a/src/minBy.ts b/src/minBy.ts
index d69e39b..ef330c0 100644
--- a/src/minBy.ts
+++ b/src/minBy.ts
@@ -3,16 +3,19 @@ import type { PredIndexed, PredIndexedOptional } from "./_types";
 
 const _minBy =
   (indexed: boolean) =>
-  <T>(array: Array<T>, fn: PredIndexedOptional<T, number>) => {
-    let ret: T | undefined = undefined;
-    let retMin: number | undefined = undefined;
-    array.forEach((item, i) => {
-      const min = indexed ? fn(item, i, array) : fn(item);
+  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, number>) => {
+    let ret: T | undefined;
+    let retMin: number | undefined;
+
+    for (let index = 0; index < array.length; index++) {
+      // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
+      const item = array[index]!;
+      const min = indexed ? fn(item, index, array) : fn(item);
       if (retMin === undefined || min < retMin) {
         ret = item;
         retMin = min;
       }
-    });
+    }
 
     return ret;
   };
@@ -60,7 +63,7 @@ export function minBy<T>(
   fn: (item: T) => number,
 ): T | undefined;
 
-export function minBy() {
+export function minBy(): unknown {
   return purry(_minBy(false), arguments);
 }
 
@@ -72,7 +75,7 @@ export namespace minBy {
   export function indexed<T>(
     fn: PredIndexed<T, number>,
   ): (array: ReadonlyArray<T>) => T | undefined;
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_minBy(true), arguments);
   }
 }
diff --git a/src/multiply.ts b/src/multiply.ts
index 3e12af2..49320bf 100644
--- a/src/multiply.ts
+++ b/src/multiply.ts
@@ -28,10 +28,10 @@ export function multiply(value: number, multiplicand: number): number;
  */
 export function multiply(multiplicand: number): (value: number) => number;
 
-export function multiply() {
+export function multiply(): unknown {
   return purry(_multiply, arguments);
 }
 
-function _multiply(value: number, multiplicand: number) {
+function _multiply(value: number, multiplicand: number): number {
   return value * multiplicand;
 }
diff --git a/src/noop.ts b/src/noop.ts
index fb614d5..e6e6738 100644
--- a/src/noop.ts
+++ b/src/noop.ts
@@ -6,4 +6,4 @@
  *    onSomething(R.noop)
  * @category Function
  */
-export const noop = () => undefined;
+export const noop = (): undefined => undefined;
diff --git a/src/objOf.ts b/src/objOf.ts
index ff8cfd2..5629363 100644
--- a/src/objOf.ts
+++ b/src/objOf.ts
@@ -25,10 +25,11 @@ export function objOf<T, K extends string>(
   key: K,
 ): (value: T) => { [x in K]: T };
 
-export function objOf() {
+export function objOf(): unknown {
   return purry(_objOf, arguments);
 }
 
-function _objOf<T, K extends string>(value: T, key: K) {
+function _objOf<T, K extends string>(value: T, key: K): { [x in K]: T } {
+  // @ts-expect-error [ts2322] - I'm not sure how to get the type right here...
   return { [key]: value };
 }
diff --git a/src/omit.ts b/src/omit.ts
index 178d3cd..c10aba4 100644
--- a/src/omit.ts
+++ b/src/omit.ts
@@ -33,7 +33,7 @@ export function omit<T extends object, K extends keyof T>(
   propNames: ReadonlyArray<K>,
 ): Omit<T, K>;
 
-export function omit() {
+export function omit(): unknown {
   return purry(_omit, arguments);
 }
 
diff --git a/src/omitBy.ts b/src/omitBy.ts
index 55dafef..643bc68 100644
--- a/src/omitBy.ts
+++ b/src/omitBy.ts
@@ -29,22 +29,25 @@ export function omitBy<T>(
   fn: <K extends keyof T>(value: T[K], key: K) => boolean,
 ): (object: T) => T extends Record<keyof T, T[keyof T]> ? T : Partial<T>;
 
-export function omitBy() {
+export function omitBy(): unknown {
   return purry(_omitBy, arguments);
 }
 
 function _omitBy<T>(
   object: T,
   fn: <K extends keyof T>(value: T[K], key: K) => boolean,
-) {
+): Partial<T> {
   if (object === undefined || object === null) {
     return object;
   }
 
-  return keys.strict(object).reduce<Partial<T>>((acc, key) => {
+  const out: Partial<T> = {};
+
+  for (const key of keys.strict(object)) {
     if (!fn(object[key], key)) {
-      acc[key] = object[key];
+      out[key] = object[key];
     }
-    return acc;
-  }, {});
+  }
+
+  return out;
 }
diff --git a/src/only.ts b/src/only.ts
index 0b6570e..aaefffe 100644
--- a/src/only.ts
+++ b/src/only.ts
@@ -43,13 +43,10 @@ export function only<T extends IterableContainer>(): (
   array: Readonly<T>,
 ) => Only<T>;
 
-export function only() {
+export function only(): unknown {
   return purry(_only, arguments);
 }
 
-function _only<T>(array: ReadonlyArray<T>) {
-  if (array.length === 1) {
-    return array[0];
-  }
-  return undefined;
+function _only<T>(array: ReadonlyArray<T>): T | undefined {
+  return array.length === 1 ? array[0] : undefined;
 }
diff --git a/src/partition.ts b/src/partition.ts
index 605eaca..247a39c 100644
--- a/src/partition.ts
+++ b/src/partition.ts
@@ -69,7 +69,7 @@ export function partition<T>(
   predicate: (item: T) => boolean,
 ): (array: ReadonlyArray<T>) => [Array<T>, Array<T>];
 
-export function partition() {
+export function partition(): unknown {
   return purry(_partition(false), arguments);
 }
 
@@ -77,10 +77,12 @@ const _partition =
   (indexed: boolean) =>
   <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, boolean>) => {
     const ret: [Array<T>, Array<T>] = [[], []];
-    array.forEach((item, index) => {
+    for (let index = 0; index < array.length; index++) {
+      // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
+      const item = array[index]!;
       const matches = indexed ? fn(item, index, array) : fn(item);
       ret[matches ? 0 : 1].push(item);
-    });
+    }
     return ret;
   };
 
@@ -92,7 +94,7 @@ export namespace partition {
   export function indexed<T>(
     predicate: PredIndexed<T, boolean>,
   ): (array: ReadonlyArray<T>) => [Array<T>, Array<T>];
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_partition(true), arguments);
   }
 }
diff --git a/src/pathOr.ts b/src/pathOr.ts
index ebf1813..de90b20 100644
--- a/src/pathOr.ts
+++ b/src/pathOr.ts
@@ -142,7 +142,7 @@ export function pathOr<
   defaultValue: PathValue3<T, A, B, C>,
 ): (object: T) => PathValue3<T, A, B, C>;
 
-export function pathOr() {
+export function pathOr(): unknown {
   return purry(_pathOr, arguments);
 }
 
@@ -153,7 +153,7 @@ function _pathOr(
 ): unknown {
   let current = data;
   for (const prop of path) {
-    if (current == null) {
+    if (current === null || current === undefined) {
       break;
     }
     current = (current as Record<PropertyKey, unknown>)[prop];
diff --git a/src/pick.ts b/src/pick.ts
index c021a29..4f90896 100644
--- a/src/pick.ts
+++ b/src/pick.ts
@@ -28,18 +28,20 @@ export function pick<T extends object, K extends keyof T>(
   names: ReadonlyArray<K>,
 ): Pick<T, K>;
 
-export function pick() {
+export function pick(): unknown {
   return purry(_pick, arguments);
 }
 
 function _pick<T extends object, K extends keyof T>(
   object: T,
   names: ReadonlyArray<K>,
-) {
-  return names.reduce<Record<PropertyKey, unknown>>((acc, name) => {
+): Pick<T, K> {
+  const out: Partial<Pick<T, K>> = {};
+  for (const name of names) {
     if (name in object) {
-      acc[name] = object[name];
+      out[name] = object[name];
     }
-    return acc;
-  }, {});
+  }
+  // @ts-expect-error [ts2322] - We build the type incrementally, there's no way to make typescript infer that we "finished" building the object and to treat it as such.
+  return out;
 }
diff --git a/src/pickBy.ts b/src/pickBy.ts
index 61ba0f2..f86a0c4 100644
--- a/src/pickBy.ts
+++ b/src/pickBy.ts
@@ -29,21 +29,25 @@ export function pickBy<T>(
   fn: <K extends keyof T>(value: T[K], key: K) => boolean,
 ): (object: T) => T extends Record<keyof T, T[keyof T]> ? T : Partial<T>;
 
-export function pickBy() {
+export function pickBy(): unknown {
   return purry(_pickBy, arguments);
 }
 
 function _pickBy<T>(
-  object: T,
+  data: T,
   fn: <K extends keyof T>(value: T[K], key: K) => boolean,
 ): Partial<T> {
-  if (object == null) {
+  if (data === null || data === undefined) {
     return {};
   }
-  return keys.strict(object).reduce<Partial<T>>((acc, key) => {
-    if (fn(object[key], key)) {
-      acc[key] = object[key];
+
+  const out: Partial<T> = {};
+
+  for (const key of keys.strict(data)) {
+    if (fn(data[key], key)) {
+      out[key] = data[key];
     }
-    return acc;
-  }, {});
+  }
+
+  return out;
 }
diff --git a/src/pipe.ts b/src/pipe.ts
index 9d6779e..0737f55 100644
--- a/src/pipe.ts
+++ b/src/pipe.ts
@@ -1,6 +1,42 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
 
-import type { LazyResult } from "./_reduceLazy";
+export type LazyEvaluator<T = unknown, R = T> = (
+  item: T,
+  index?: number,
+  data?: ReadonlyArray<T>,
+) => LazyResult<R>;
+
+type LazyResult<T> = LazyEmpty | LazyMany<T> | LazyNext<T>;
+
+type LazyEmpty = {
+  done: boolean;
+  hasNext: false;
+  hasMany?: false | undefined;
+  next?: undefined;
+};
+
+type LazyNext<T> = {
+  done: boolean;
+  hasNext: true;
+  hasMany?: false | undefined;
+  next: T;
+};
+
+type LazyMany<T> = {
+  done: boolean;
+  hasNext: true;
+  hasMany: true;
+  next: Array<T>;
+};
+
+type PreparedLazyOperation = LazyEvaluator & {
+  readonly isIndexed: boolean;
+  readonly isSingle: boolean;
+
+  // These are intentionally mutable, they maintain the lazy piped state.
+  index: number;
+  items: Array<unknown>;
+};
 
 /**
  * Perform left-to-right function composition.
@@ -201,22 +237,24 @@ export function pipe(
   let output = input;
 
   const lazyOperations = operations.map((op) =>
-    "lazy" in op ? toPipedLazy(op) : undefined,
+    "lazy" in op ? prepareLazyOperation(op) : undefined,
   );
 
   let operationIndex = 0;
   while (operationIndex < operations.length) {
+    // eslint-disable-next-line @typescript-eslint/prefer-destructuring
     const lazyOperation = lazyOperations[operationIndex];
     if (lazyOperation === undefined || !isIterable(output)) {
       const operation = operations[operationIndex]!;
       output = operation(output);
-      operationIndex++;
+      operationIndex += 1;
       continue;
     }
 
-    const lazySequence: Array<ReturnType<typeof toPipedLazy>> = [];
-    for (let j = operationIndex; j < operations.length; j++) {
-      const lazyOp = lazyOperations[j];
+    const lazySequence: Array<PreparedLazyOperation> = [];
+    for (let index = operationIndex; index < operations.length; index++) {
+      // eslint-disable-next-line @typescript-eslint/prefer-destructuring
+      const lazyOp = lazyOperations[index];
       if (lazyOp === undefined) {
         break;
       }
@@ -249,11 +287,7 @@ export function pipe(
     }
 
     const { isSingle } = lazySequence[lazySequence.length - 1]!;
-    if (isSingle) {
-      output = accumulator[0];
-    } else {
-      output = accumulator;
-    }
+    output = isSingle ? accumulator[0] : accumulator;
     operationIndex += lazySequence.length;
   }
   return output;
@@ -262,38 +296,49 @@ export function pipe(
 type LazyFn = (value: any, index?: number, items?: any) => LazyResult<any>;
 
 type LazyOp = ((input: any) => any) & {
-  lazy: ((...args: Array<any>) => LazyFn) & {
-    indexed: boolean;
-    single: boolean;
+  readonly lazy: ((...args: ReadonlyArray<any>) => LazyFn) & {
+    readonly indexed: boolean;
+    readonly single: boolean;
   };
-  lazyArgs?: ReadonlyArray<unknown>;
+  readonly lazyArgs?: ReadonlyArray<unknown>;
 };
 
 function _processItem(
   item: unknown,
+  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types -- Intentionally mutable, we use the accumulator directly to accumulate the results.
   accumulator: Array<unknown>,
-  lazySequence: ReadonlyArray<ReturnType<typeof toPipedLazy>>,
+  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types -- Intentionally mutable, the lazy sequence is stateful and contains the state needed to compute the next value lazily.
+  lazySequence: ReadonlyArray<PreparedLazyOperation>,
 ): boolean {
   if (lazySequence.length === 0) {
     accumulator.push(item);
     return false;
   }
+
+  let currentItem = item;
+
   let lazyResult: LazyResult<any> = { done: false, hasNext: false };
   let isDone = false;
-  for (let i = 0; i < lazySequence.length; i++) {
-    const lazyFn = lazySequence[i]!;
+  for (
+    let operationsIndex = 0;
+    operationsIndex < lazySequence.length;
+    operationsIndex++
+  ) {
+    // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
+    const lazyFn = lazySequence[operationsIndex]!;
     const { isIndexed, index, items } = lazyFn;
-    items.push(item);
-    lazyResult = isIndexed ? lazyFn(item, index, items) : lazyFn(item);
-    lazyFn.index++;
+    items.push(currentItem);
+    lazyResult = isIndexed
+      ? lazyFn(currentItem, index, items)
+      : lazyFn(currentItem);
+    lazyFn.index += 1;
     if (lazyResult.hasNext) {
-      if (lazyResult.hasMany) {
-        const nextValues: Array<any> = lazyResult.next;
-        for (const subItem of nextValues) {
+      if (lazyResult.hasMany ?? false) {
+        for (const subItem of lazyResult.next as ReadonlyArray<unknown>) {
           const subResult = _processItem(
             subItem,
             accumulator,
-            lazySequence.slice(i + 1),
+            lazySequence.slice(operationsIndex + 1),
           );
           if (subResult) {
             return true;
@@ -301,7 +346,7 @@ function _processItem(
         }
         return false;
       }
-      item = lazyResult.next;
+      currentItem = lazyResult.next;
     }
     if (!lazyResult.hasNext) {
       break;
@@ -313,7 +358,7 @@ function _processItem(
     }
   }
   if (lazyResult.hasNext) {
-    accumulator.push(item);
+    accumulator.push(currentItem);
   }
   if (isDone) {
     return true;
@@ -321,7 +366,7 @@ function _processItem(
   return false;
 }
 
-function toPipedLazy(op: LazyOp) {
+function prepareLazyOperation(op: LazyOp): PreparedLazyOperation {
   const { lazy, lazyArgs } = op;
   const fn = lazy(...(lazyArgs ?? []));
   return Object.assign(fn, {
diff --git a/src/prop.ts b/src/prop.ts
index 9164811..a33820f 100644
--- a/src/prop.ts
+++ b/src/prop.ts
@@ -9,5 +9,5 @@
  */
 export const prop =
   <T, K extends keyof T = keyof T>(propName: K) =>
-  ({ [propName]: value }: T) =>
+  ({ [propName]: value }: T): T[K] =>
     value;
diff --git a/src/purry.ts b/src/purry.ts
index 82d2ed6..4c443d4 100644
--- a/src/purry.ts
+++ b/src/purry.ts
@@ -1,17 +1,27 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
 
-type LazyFactory = (...args: any) => unknown;
+import type { LazyEvaluator } from "./pipe";
+
+type LazyEvaluatorFactory = (...args: any) => LazyEvaluator;
 
 type MaybeLazyFunction = {
   (...args: any): unknown;
-  lazy?: LazyFactory;
+  readonly lazy?: LazyEvaluatorFactory;
 };
 
 /**
- * Creates a function with `data-first` and `data-last` signatures.
+ * Creates a function with `dataFirst` and `dataLast` signatures.
+ *
+ * `purry` is a dynamic function and it's not type safe. It should be wrapped by
+ * a function that have proper typings. Refer to the example below for correct
+ * usage.
  *
- * `purry` is a dynamic function and it's not type safe. It should be wrapped by a function that have proper typings.
- * Refer to the example below for correct usage.
+ * !IMPORTANT: functions that simply call `purry` and return the result (like
+ * almost all functions in this library) should return `unknown` themselves if
+ * an explicit return type is required. This is because we currently don't
+ * provide a generic return type that is built from the input function, and
+ * crafting one manually isn't worthwhile as we rely on function declaration
+ * overloading to combine the types for dataFirst and dataLast invocations!
  *
  * @param fn the function to purry.
  * @param args the arguments
@@ -40,7 +50,7 @@ type MaybeLazyFunction = {
 export function purry(
   fn: MaybeLazyFunction,
   args: IArguments | ReadonlyArray<unknown>,
-  lazyFactory?: LazyFactory,
+  lazyFactory?: LazyEvaluatorFactory,
 ): unknown {
   // TODO: Once we bump our target beyond ES5 we can spread the args array directly and don't need this...
   const callArgs = Array.from(args) as ReadonlyArray<unknown>;
@@ -51,7 +61,7 @@ export function purry(
   }
 
   if (diff === 1) {
-    const ret = (data: unknown) => fn(data, ...callArgs);
+    const ret = (data: unknown): unknown => fn(data, ...callArgs);
     const lazy = lazyFactory ?? fn.lazy;
     return lazy === undefined
       ? ret
diff --git a/src/randomString.ts b/src/randomString.ts
index 6108ef0..44098ef 100644
--- a/src/randomString.ts
+++ b/src/randomString.ts
@@ -32,12 +32,13 @@ export function randomString(length: number): string;
 // TODO: Add this back when we deprecate headless calls in V2 of Remeda. Currently the dataLast overload breaks the typing for the headless version of the function, which is used widely in the wild.
 // export function randomString(): (length: number) => string;
 
-export function randomString() {
+export function randomString(): unknown {
   return purry(randomStringImplementation, arguments);
 }
 
-function randomStringImplementation(length: number) {
+function randomStringImplementation(length: number): string {
   return times(length, randomChar).join("");
 }
 
-const randomChar = () => ALPHABET[Math.floor(Math.random() * ALPHABET.length)]!;
+const randomChar = (): string =>
+  ALPHABET[Math.floor(Math.random() * ALPHABET.length)]!;
diff --git a/src/range.ts b/src/range.ts
index 9e18dc1..d053a58 100644
--- a/src/range.ts
+++ b/src/range.ts
@@ -23,11 +23,11 @@ export function range(start: number, end: number): Array<number>;
  */
 export function range(end: number): (start: number) => Array<number>;
 
-export function range() {
+export function range(): unknown {
   return purry(_range, arguments);
 }
 
-function _range(start: number, end: number) {
+function _range(start: number, end: number): Array<number> {
   const ret: Array<number> = [];
   for (let i = start; i < end; i++) {
     ret.push(i);
diff --git a/src/rankBy.ts b/src/rankBy.ts
index c02cec4..10ac810 100644
--- a/src/rankBy.ts
+++ b/src/rankBy.ts
@@ -63,7 +63,7 @@ function rankByImplementation<T>(
     if (compareFn(targetItem, item) > 0) {
       // The rank of the item is equivalent to the number of items that would
       // come before it if the array was sorted. We assume that the
-      rank++;
+      rank += 1;
     }
   }
   return rank;
diff --git a/src/reduce.ts b/src/reduce.ts
index 8175d5c..24752a5 100644
--- a/src/reduce.ts
+++ b/src/reduce.ts
@@ -39,17 +39,18 @@ export function reduce<T, K>(
   initialValue: K,
 ): (items: ReadonlyArray<T>) => K;
 
-export function reduce() {
+export function reduce(): unknown {
   return purry(_reduce(false), arguments);
 }
 
 const _reduce =
   (indexed: boolean) =>
   <T, K>(
-    items: Array<T>,
-    fn: (acc: K, item: T, index?: number, items?: Array<T>) => K,
+    items: ReadonlyArray<T>,
+    fn: (acc: K, item: T, index?: number, items?: ReadonlyArray<T>) => K,
     initialValue: K,
   ): K =>
+    // eslint-disable-next-line unicorn/no-array-reduce -- Our function wraps the built-in reduce.
     items.reduce(
       (acc, item, index) =>
         indexed ? fn(acc, item, index, items) : fn(acc, item),
@@ -59,14 +60,14 @@ const _reduce =
 export namespace reduce {
   export function indexed<T, K>(
     array: ReadonlyArray<T>,
-    fn: (acc: K, item: T, index: number, items: Array<T>) => K,
+    fn: (acc: K, item: T, index: number, items: ReadonlyArray<T>) => K,
     initialValue: K,
   ): K;
   export function indexed<T, K>(
-    fn: (acc: K, item: T, index: number, items: Array<T>) => K,
+    fn: (acc: K, item: T, index: number, items: ReadonlyArray<T>) => K,
     initialValue: K,
   ): (array: ReadonlyArray<T>) => K;
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_reduce(true), arguments);
   }
 }
diff --git a/src/reject.ts b/src/reject.ts
index 899c081..4c33128 100644
--- a/src/reject.ts
+++ b/src/reject.ts
@@ -1,8 +1,8 @@
-import { purry } from "./purry";
-import type { LazyResult } from "./_reduceLazy";
 import { _reduceLazy } from "./_reduceLazy";
-import type { Pred, PredIndexedOptional, PredIndexed } from "./_types";
 import { _toLazyIndexed } from "./_toLazyIndexed";
+import type { Pred, PredIndexed, PredIndexedOptional } from "./_types";
+import type { LazyEvaluator } from "./pipe";
+import { purry } from "./purry";
 
 /**
  * Reject the elements of an array that meet the condition specified in a callback function.
@@ -43,7 +43,7 @@ export function reject<T>(
   fn: Pred<T, boolean>,
 ): (items: ReadonlyArray<T>) => Array<T>;
 
-export function reject() {
+export function reject(): unknown {
   return purry(_reject(false), arguments, reject.lazy);
 }
 
@@ -58,21 +58,11 @@ const _reject =
 
 const _lazy =
   (indexed: boolean) =>
-  <T>(fn: PredIndexedOptional<T, boolean>) =>
-  (value: T, index?: number, array?: ReadonlyArray<T>): LazyResult<T> => {
-    const valid = indexed ? fn(value, index, array) : fn(value);
-    if (!valid) {
-      return {
-        done: false,
-        hasNext: true,
-        next: value,
-      };
-    }
-    return {
-      done: false,
-      hasNext: false,
-    };
-  };
+  <T>(fn: PredIndexedOptional<T, boolean>): LazyEvaluator<T> =>
+  (item, index, data) =>
+    (indexed ? fn(item, index, data) : fn(item))
+      ? { done: false, hasNext: false }
+      : { done: false, hasNext: true, next: item };
 
 export namespace reject {
   export function indexed<T, K>(
@@ -82,7 +72,7 @@ export namespace reject {
   export function indexed<T, K>(
     fn: PredIndexed<T, boolean>,
   ): (array: ReadonlyArray<T>) => Array<K>;
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_reject(true), arguments, reject.lazyIndexed);
   }
 
diff --git a/src/reverse.ts b/src/reverse.ts
index d9a37c1..6751608 100644
--- a/src/reverse.ts
+++ b/src/reverse.ts
@@ -39,7 +39,7 @@ export function reverse<T extends ReadonlyArray<unknown>>(): (
   array: T,
 ) => Reverse<T>;
 
-export function reverse() {
+export function reverse(): unknown {
   return purry(_reverse, arguments);
 }
 
diff --git a/src/round.ts b/src/round.ts
index 6a79c23..f29dfed 100644
--- a/src/round.ts
+++ b/src/round.ts
@@ -37,6 +37,6 @@ export function round(value: number, precision: number): number;
  */
 export function round(precision: number): (value: number) => number;
 
-export function round() {
+export function round(): unknown {
   return purry(_withPrecision(Math.round), arguments);
 }
diff --git a/src/sample.ts b/src/sample.ts
index b49fc2d..d8f3ab0 100644
--- a/src/sample.ts
+++ b/src/sample.ts
@@ -103,7 +103,10 @@ export function sample(...args: ReadonlyArray<unknown>): unknown {
   return purry(sampleImplementation, args);
 }
 
-function sampleImplementation<T>(data: Array<T>, sampleSize: number): Array<T> {
+function sampleImplementation<T>(
+  data: ReadonlyArray<T>,
+  sampleSize: number,
+): Array<T> {
   if (sampleSize < 0) {
     throw new RangeError(`sampleSize must cannot be negative: ${sampleSize}`);
   }
@@ -114,7 +117,7 @@ function sampleImplementation<T>(data: Array<T>, sampleSize: number): Array<T> {
 
   if (sampleSize >= data.length) {
     // Trivial
-    return data;
+    return data.slice();
   }
 
   if (sampleSize === 0) {
diff --git a/src/set.ts b/src/set.ts
index 72069a3..3eff38e 100644
--- a/src/set.ts
+++ b/src/set.ts
@@ -27,7 +27,7 @@ export function set<T, K extends keyof T>(obj: T, prop: K, value: T[K]): T;
  */
 export function set<T, K extends keyof T>(prop: K, value: T[K]): (obj: T) => T;
 
-export function set() {
+export function set(): unknown {
   return purry(_set, arguments);
 }
 
diff --git a/src/setPath.ts b/src/setPath.ts
index 4e1b342..647c58a 100644
--- a/src/setPath.ts
+++ b/src/setPath.ts
@@ -37,7 +37,7 @@ export function setPath<TPath extends Array<PropertyKey>, Value>(
   value: Value,
 ): <Obj>(object: SupportsValueAtPath<Obj, TPath, Value>) => Obj;
 
-export function setPath() {
+export function setPath(): unknown {
   return purry(_setPath, arguments);
 }
 
diff --git a/src/shuffle.ts b/src/shuffle.ts
index f37bff5..fe37f1d 100644
--- a/src/shuffle.ts
+++ b/src/shuffle.ts
@@ -23,13 +23,13 @@ export function shuffle<T>(items: ReadonlyArray<T>): Array<T>;
  */
 export function shuffle<T>(): (items: ReadonlyArray<T>) => Array<T>;
 
-export function shuffle() {
+export function shuffle(): unknown {
   return purry(_shuffle, arguments);
 }
 
 function _shuffle<T>(items: ReadonlyArray<T>): Array<T> {
   const result = items.slice();
-  for (let index = 0; index < items.length; index += 1) {
+  for (let index = 0; index < items.length; index++) {
     const rand = index + Math.floor(Math.random() * (items.length - index));
     const value = result[rand]!;
     result[rand] = result[index]!;
diff --git a/src/sort.ts b/src/sort.ts
index f4299e3..f83b725 100644
--- a/src/sort.ts
+++ b/src/sort.ts
@@ -47,12 +47,15 @@ export function sort<T>(
   cmp: (a: T, b: T) => number,
 ): (items: ReadonlyArray<T>) => Array<T>;
 
-export function sort() {
+export function sort(): unknown {
   return purry(_sort, arguments);
 }
 
-function _sort<T>(items: Array<T>, cmp: (a: T, b: T) => number) {
-  const ret = [...items];
+function _sort<T>(
+  items: ReadonlyArray<T>,
+  cmp: (a: T, b: T) => number,
+): Array<T> {
+  const ret = items.slice();
   ret.sort(cmp);
   return ret;
 }
diff --git a/src/sortBy.ts b/src/sortBy.ts
index 6529a6a..ee2e248 100644
--- a/src/sortBy.ts
+++ b/src/sortBy.ts
@@ -107,7 +107,7 @@ const _sortBy = <T>(
   compareFn: CompareFunction<T>,
 ): Array<T> =>
   // Sort is done in-place so we need to copy the array.
-  [...data].sort(compareFn);
+  data.slice().sort(compareFn);
 
 type Strict = {
   <T extends IterableContainer>(
diff --git a/src/splice.ts b/src/splice.ts
index 1ed2384..e9f6b77 100644
--- a/src/splice.ts
+++ b/src/splice.ts
@@ -41,7 +41,7 @@ export function splice<T>(
   replacement: ReadonlyArray<T>,
 ): (items: ReadonlyArray<T>) => Array<T>;
 
-export function splice() {
+export function splice(): unknown {
   return purry(_splice, arguments);
 }
 
@@ -51,7 +51,7 @@ function _splice<T>(
   deleteCount: number,
   replacement: ReadonlyArray<T>,
 ): Array<T> {
-  const result = [...items];
+  const result = items.slice();
   result.splice(start, deleteCount, ...replacement);
   return result;
 }
diff --git a/src/splitAt.ts b/src/splitAt.ts
index f051072..c87ba89 100644
--- a/src/splitAt.ts
+++ b/src/splitAt.ts
@@ -33,12 +33,15 @@ export function splitAt<T>(
   index: number,
 ): (array: ReadonlyArray<T>) => [Array<T>, Array<T>];
 
-export function splitAt() {
+export function splitAt(): unknown {
   return purry(_splitAt, arguments);
 }
 
-function _splitAt<T>(array: Array<T>, index: number) {
-  const copy = [...array];
+function _splitAt<T>(
+  array: ReadonlyArray<T>,
+  index: number,
+): [Array<T>, Array<T>] {
+  const copy = array.slice();
   const tail = copy.splice(index);
   return [copy, tail];
 }
diff --git a/src/splitWhen.ts b/src/splitWhen.ts
index a32c8d1..d86b060 100644
--- a/src/splitWhen.ts
+++ b/src/splitWhen.ts
@@ -31,11 +31,14 @@ export function splitWhen<T>(
   fn: (item: T) => boolean,
 ): (array: ReadonlyArray<T>) => [Array<T>, Array<T>];
 
-export function splitWhen() {
+export function splitWhen(): unknown {
   return purry(_splitWhen, arguments);
 }
 
-function _splitWhen<T>(array: Array<T>, fn: (item: T) => boolean) {
+function _splitWhen<T>(
+  array: ReadonlyArray<T>,
+  fn: (item: T) => boolean,
+): [Array<T>, Array<T>] {
   for (let i = 0; i < array.length; i++) {
     // TODO: Use `Array.prototype.entries` once we bump our TS target so we
     // can get both the index and the item at the same time, and don't need
@@ -44,5 +47,6 @@ function _splitWhen<T>(array: Array<T>, fn: (item: T) => boolean) {
       return splitAt(array, i);
     }
   }
-  return [array, []];
+
+  return [array.slice(), []];
 }
diff --git a/src/stringToPath.ts b/src/stringToPath.ts
index 3803017..969caaf 100644
--- a/src/stringToPath.ts
+++ b/src/stringToPath.ts
@@ -18,8 +18,9 @@ function _stringToPath(path: string): Array<string> {
   }
 
   const match =
-    /^\[(.+?)\](.*)$/.exec(path) ?? /^\.?([^.[\]]+)(.*)$/.exec(path);
-  if (match) {
+    // eslint-disable-next-line prefer-named-capture-group
+    /^\[(.+?)\](.*)$/u.exec(path) ?? /^\.?([^.[\]]+)(.*)$/u.exec(path);
+  if (match !== null) {
     const [, key, rest] = match;
     // @ts-expect-error [ts2322] - Can we improve typing here to assure that `key` and `rest` are defined when the regex matches?
     return [key, ..._stringToPath(rest)];
diff --git a/src/subtract.ts b/src/subtract.ts
index 2adfa4d..3c0ebd4 100644
--- a/src/subtract.ts
+++ b/src/subtract.ts
@@ -30,10 +30,10 @@ export function subtract(value: number, subtrahend: number): number;
  */
 export function subtract(subtrahend: number): (value: number) => number;
 
-export function subtract() {
+export function subtract(): unknown {
   return purry(_subtract, arguments);
 }
 
-function _subtract(value: number, subtrahend: number) {
+function _subtract(value: number, subtrahend: number): number {
   return value - subtrahend;
 }
diff --git a/src/sumBy.ts b/src/sumBy.ts
index 4ad7a46..65f656f 100644
--- a/src/sumBy.ts
+++ b/src/sumBy.ts
@@ -3,12 +3,14 @@ import type { PredIndexed, PredIndexedOptional } from "./_types";
 
 const _sumBy =
   (indexed: boolean) =>
-  <T>(array: Array<T>, fn: PredIndexedOptional<T, number>) => {
+  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, number>) => {
     let sum = 0;
-    array.forEach((item, i) => {
-      const summand = indexed ? fn(item, i, array) : fn(item);
+    for (let index = 0; index < array.length; index++) {
+      // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
+      const item = array[index]!;
+      const summand = indexed ? fn(item, index, array) : fn(item);
       sum += summand;
-    });
+    }
     return sum;
   };
 
@@ -54,7 +56,7 @@ export function sumBy<T>(
   fn: (item: T) => number,
 ): number;
 
-export function sumBy() {
+export function sumBy(): unknown {
   return purry(_sumBy(false), arguments);
 }
 
@@ -68,7 +70,7 @@ export namespace sumBy {
     fn: PredIndexed<T, number>,
   ): (array: ReadonlyArray<T>) => number;
 
-  export function indexed() {
+  export function indexed(): unknown {
     return purry(_sumBy(true), arguments);
   }
 }
diff --git a/src/swapIndices.ts b/src/swapIndices.ts
index 75bdf95..cb23e05 100644
--- a/src/swapIndices.ts
+++ b/src/swapIndices.ts
@@ -75,10 +75,10 @@ type SwapArray<
   K1 extends number,
   K2 extends number,
 > =
-  // TODO [typescript@>4.6]: Because of limitations on the typescript version
-  // used in Remeda we can't build a proper Absolute number type so we can't
-  // implement proper typing for negative indices and have to opt for a less-
-  // strict type instead.
+  // TODO: Because of limitations on the typescript version used in Remeda we
+  // can't build a proper Absolute number type so we can't implement proper
+  // typing for negative indices and have to opt for a less- strict type
+  // instead.
   // Check out the history for the PR that introduced this TODO to see how it
   // could be implemented.
   IsNonNegative<K1> extends false
@@ -154,7 +154,7 @@ export function swapIndices<K1 extends number, K2 extends number>(
   index2: K2,
 ): <T extends IterableContainer | string>(data: T) => SwappedIndices<T, K1, K2>;
 
-export function swapIndices() {
+export function swapIndices(): unknown {
   return purry(_swapIndices, arguments);
 }
 
diff --git a/src/take.ts b/src/take.ts
index dd7ac25..f487a9f 100644
--- a/src/take.ts
+++ b/src/take.ts
@@ -1,6 +1,6 @@
-import { purry } from "./purry";
-import type { LazyResult } from "./_reduceLazy";
 import { _reduceLazy } from "./_reduceLazy";
+import type { LazyEvaluator } from "./pipe";
+import { purry } from "./purry";
 
 /**
  * Returns the first `n` elements of `array`.
@@ -29,36 +29,24 @@ export function take<T>(array: ReadonlyArray<T>, n: number): Array<T>;
  */
 export function take<T>(n: number): (array: ReadonlyArray<T>) => Array<T>;
 
-export function take() {
+export function take(): unknown {
   return purry(_take, arguments, take.lazy);
 }
 
-function _take<T>(array: Array<T>, n: number) {
+function _take<T>(array: ReadonlyArray<T>, n: number): Array<T> {
   return _reduceLazy(array, take.lazy(n));
 }
 
 export namespace take {
-  export function lazy<T>(n: number) {
-    return (value: T): LazyResult<T> => {
-      if (n === 0) {
-        return {
-          done: true,
-          hasNext: false,
-        };
-      }
-      n--;
-      if (n === 0) {
-        return {
-          done: true,
-          hasNext: true,
-          next: value,
-        };
-      }
-      return {
-        done: false,
-        hasNext: true,
-        next: value,
-      };
+  export function lazy<T>(n: number): LazyEvaluator<T> {
+    if (n <= 0) {
+      return () => ({ done: true, hasNext: false });
+    }
+
+    let remaining = n;
+    return (value) => {
+      remaining -= 1;
+      return { done: remaining <= 0, hasNext: true, next: value };
     };
   }
 }
diff --git a/src/takeFirstBy.ts b/src/takeFirstBy.ts
index 8ae6f21..0e35f63 100644
--- a/src/takeFirstBy.ts
+++ b/src/takeFirstBy.ts
@@ -60,7 +60,7 @@ function takeFirstByImplementation<T>(
   }
 
   if (n >= data.length) {
-    return [...data];
+    return data.slice();
   }
 
   const heap = data.slice(0, n);
diff --git a/src/takeLastWhile.ts b/src/takeLastWhile.ts
index 13402e2..e13858b 100644
--- a/src/takeLastWhile.ts
+++ b/src/takeLastWhile.ts
@@ -34,7 +34,7 @@ export function takeLastWhile<T>(
   predicate: (item: T) => boolean,
 ): (data: ReadonlyArray<T>) => Array<T>;
 
-export function takeLastWhile() {
+export function takeLastWhile(): unknown {
   return purry(_takeLastWhile, arguments);
 }
 
diff --git a/src/takeWhile.ts b/src/takeWhile.ts
index 6aa7487..0f77bea 100644
--- a/src/takeWhile.ts
+++ b/src/takeWhile.ts
@@ -30,11 +30,14 @@ export function takeWhile<T>(
   fn: (item: T) => boolean,
 ): (array: ReadonlyArray<T>) => Array<T>;
 
-export function takeWhile() {
+export function takeWhile(): unknown {
   return purry(_takeWhile, arguments);
 }
 
-function _takeWhile<T>(array: Array<T>, fn: (item: T) => boolean) {
+function _takeWhile<T>(
+  array: ReadonlyArray<T>,
+  fn: (item: T) => boolean,
+): Array<T> {
   const ret: Array<T> = [];
   for (const item of array) {
     if (!fn(item)) {
diff --git a/src/tap.ts b/src/tap.ts
index 92dfc55..5a29832 100644
--- a/src/tap.ts
+++ b/src/tap.ts
@@ -40,7 +40,7 @@ export function tap<T>(value: T, fn: (value: T) => void): T;
  */
 export function tap<T, F extends (value: T) => unknown>(fn: F): (value: T) => T;
 
-export function tap() {
+export function tap(): unknown {
   return purry(_tap, arguments);
 }
 
diff --git a/src/times.ts b/src/times.ts
index 3972a2d..4009ce1 100644
--- a/src/times.ts
+++ b/src/times.ts
@@ -30,7 +30,7 @@ export function times<T>(count: number, fn: (n: number) => T): Array<T>;
  */
 export function times<T>(fn: (n: number) => T): (count: number) => Array<T>;
 
-export function times() {
+export function times(): unknown {
   return purry(_times, arguments);
 }
 
diff --git a/src/toPairs.ts b/src/toPairs.ts
index 1239009..2718a95 100644
--- a/src/toPairs.ts
+++ b/src/toPairs.ts
@@ -21,7 +21,9 @@ import { purry } from "./purry";
  * @category Object
  * @dataFirst
  */
-export function toPairs<T>(object: Record<string, T>): Array<[string, T]>;
+export function toPairs<T>(
+  object: Readonly<Record<string, T>>,
+): Array<[string, T]>;
 
 /**
  * Returns an array of key/values of the enumerable properties of an object.
@@ -45,7 +47,7 @@ export function toPairs<T>(object: Record<string, T>): Array<[string, T]>;
 // TODO: Add this back when we deprecate headless calls in V2 of Remeda. Currently the dataLast overload breaks the typing for the headless version of the function, which is used widely in the wild.
 // export function toPairs(): <T>(object: Record<string, T>) => Array<[string, T]>;
 
-export function toPairs() {
+export function toPairs(): unknown {
   return purry(Object.entries, arguments);
 }
 
diff --git a/src/type.ts b/src/type.ts
index 5495abd..56f2848 100644
--- a/src/type.ts
+++ b/src/type.ts
@@ -16,7 +16,7 @@
  *    R.type(undefined); //=> "Undefined"
  * @category Type
  */
-export function type(val: unknown) {
+export function type(val: unknown): string {
   return val === null
     ? "Null"
     : val === undefined
diff --git a/src/uniq.ts b/src/uniq.ts
index 5cfdcdd..4770fd9 100644
--- a/src/uniq.ts
+++ b/src/uniq.ts
@@ -1,6 +1,6 @@
-import { purry } from "./purry";
-import type { LazyResult } from "./_reduceLazy";
 import { _reduceLazy } from "./_reduceLazy";
+import type { LazyEvaluator } from "./pipe";
+import { purry } from "./purry";
 
 /**
  * Returns a new array containing only one copy of each element in the original list.
@@ -34,30 +34,23 @@ export function uniq<T>(array: ReadonlyArray<T>): Array<T>;
  */
 export function uniq<T>(): (array: ReadonlyArray<T>) => Array<T>;
 
-export function uniq() {
+export function uniq(): unknown {
   return purry(_uniq, arguments, uniq.lazy);
 }
 
-function _uniq<T>(array: Array<T>) {
+function _uniq<T>(array: ReadonlyArray<T>): Array<T> {
   return _reduceLazy(array, uniq.lazy());
 }
 
 export namespace uniq {
-  export function lazy<T>() {
+  export function lazy<T>(): LazyEvaluator<T> {
     const set = new Set<T>();
-    return (value: T): LazyResult<T> => {
+    return (value) => {
       if (set.has(value)) {
-        return {
-          done: false,
-          hasNext: false,
-        };
+        return { done: false, hasNext: false };
       }
       set.add(value);
-      return {
-        done: false,
-        hasNext: true,
-        next: value,
-      };
+      return { done: false, hasNext: true, next: value };
     };
   }
 }
diff --git a/src/uniqBy.ts b/src/uniqBy.ts
index 369b393..b13a675 100644
--- a/src/uniqBy.ts
+++ b/src/uniqBy.ts
@@ -1,6 +1,6 @@
-import { purry } from "./purry";
-import type { LazyResult } from "./_reduceLazy";
 import { _reduceLazy } from "./_reduceLazy";
+import type { LazyEvaluator } from "./pipe";
+import { purry } from "./purry";
 
 export function uniqBy<T, K>(
   array: ReadonlyArray<T>,
@@ -31,30 +31,26 @@ export function uniqBy<T, K>(
   transformer: (item: T) => K,
 ): (array: ReadonlyArray<T>) => Array<T>;
 
-export function uniqBy() {
+export function uniqBy(): unknown {
   return purry(_uniqBy, arguments, lazyUniqBy);
 }
 
-function _uniqBy<T, K>(array: Array<T>, transformer: (item: T) => K) {
+function _uniqBy<T, K>(
+  array: ReadonlyArray<T>,
+  transformer: (item: T) => K,
+): Array<T> {
   return _reduceLazy(array, lazyUniqBy(transformer));
 }
 
-function lazyUniqBy<T, K>(transformer: (item: T) => K) {
+function lazyUniqBy<T, K>(transformer: (item: T) => K): LazyEvaluator<T> {
   const set = new Set<K>();
-  return (value: T): LazyResult<T> => {
+  return (value) => {
     const appliedItem = transformer(value);
     if (set.has(appliedItem)) {
-      return {
-        done: false,
-        hasNext: false,
-      };
+      return { done: false, hasNext: false };
     }
 
     set.add(appliedItem);
-    return {
-      done: false,
-      hasNext: true,
-      next: value,
-    };
+    return { done: false, hasNext: true, next: value };
   };
 }
diff --git a/src/uniqWith.ts b/src/uniqWith.ts
index 5aa8094..7fe80f8 100644
--- a/src/uniqWith.ts
+++ b/src/uniqWith.ts
@@ -1,7 +1,7 @@
-import { purry } from "./purry";
-import type { LazyResult } from "./_reduceLazy";
 import { _reduceLazy } from "./_reduceLazy";
 import { _toLazyIndexed } from "./_toLazyIndexed";
+import type { LazyEvaluator } from "./pipe";
+import { purry } from "./purry";
 
 type IsEquals<T> = (a: T, b: T) => boolean;
 
@@ -46,37 +46,25 @@ export function uniqWith<T>(
   isEquals: IsEquals<T>,
 ): (array: ReadonlyArray<T>) => Array<T>;
 
-export function uniqWith() {
+export function uniqWith(): unknown {
   return purry(_uniqWith, arguments, uniqWith.lazy);
 }
 
-function _uniqWith<T>(array: ReadonlyArray<T>, isEquals: IsEquals<T>) {
+function _uniqWith<T>(
+  array: ReadonlyArray<T>,
+  isEquals: IsEquals<T>,
+): Array<T> {
   const lazy = uniqWith.lazy(isEquals);
   return _reduceLazy(array, lazy, true);
 }
 
-function _lazy<T>(isEquals: IsEquals<T>) {
-  return (
-    value: T,
-    index?: number,
-    array?: ReadonlyArray<T>,
-  ): LazyResult<T> => {
-    if (
-      array &&
-      array.findIndex((otherValue) => isEquals(value, otherValue)) === index
-    ) {
-      return {
-        done: false,
-        hasNext: true,
-        next: value,
-      };
-    }
-    return {
-      done: false,
-      hasNext: false,
-    };
-  };
-}
+const _lazy =
+  <T>(isEquals: IsEquals<T>): LazyEvaluator<T> =>
+  (value, index, array) =>
+    array !== undefined &&
+    array.findIndex((otherValue) => isEquals(value, otherValue)) === index
+      ? { done: false, hasNext: true, next: value }
+      : { done: false, hasNext: false };
 
 export namespace uniqWith {
   export const lazy = _toLazyIndexed(_lazy);
diff --git a/src/values.ts b/src/values.ts
index 90d0f34..01eb645 100644
--- a/src/values.ts
+++ b/src/values.ts
@@ -45,6 +45,6 @@ export function values<T extends object>(data: T): Values<T>;
 // TODO: Add this back when we deprecate headless calls in V2 of Remeda. Currently the dataLast overload breaks the typing for the headless version of the function, which is used widely in the wild.
 // export function values(): <T extends object>(data: T) => Values<T>;
 
-export function values() {
+export function values(): unknown {
   return purry(Object.values, arguments);
 }
diff --git a/src/zip.ts b/src/zip.ts
index 93c70c0..87732ef 100644
--- a/src/zip.ts
+++ b/src/zip.ts
@@ -43,14 +43,17 @@ export function zip<S>(
   second: ReadonlyArray<S>,
 ): <F>(first: ReadonlyArray<F>) => Array<[F, S]>;
 
-export function zip() {
+export function zip(): unknown {
   return purry(_zip, arguments);
 }
 
-function _zip(first: Array<unknown>, second: Array<unknown>) {
+function _zip(
+  first: ReadonlyArray<unknown>,
+  second: ReadonlyArray<unknown>,
+): Array<[unknown, unknown]> {
   const resultLength =
     first.length > second.length ? second.length : first.length;
-  const result = [];
+  const result: Array<[unknown, unknown]> = [];
   for (let i = 0; i < resultLength; i++) {
     result.push([first[i], second[i]]);
   }
diff --git a/src/zipObj.ts b/src/zipObj.ts
index 8f423de..afffa40 100644
--- a/src/zipObj.ts
+++ b/src/zipObj.ts
@@ -12,7 +12,7 @@ import { purry } from "./purry";
  * @dataFirst
  * @category Array
  */
-export function zipObj<F extends number | string | symbol, S>(
+export function zipObj<F extends PropertyKey, S>(
   first: ReadonlyArray<F>,
   second: ReadonlyArray<S>,
 ): Record<F, S>;
@@ -30,21 +30,19 @@ export function zipObj<F extends number | string | symbol, S>(
  */
 export function zipObj<S>(
   second: ReadonlyArray<S>,
-): <F extends number | string | symbol>(
-  first: ReadonlyArray<F>,
-) => Record<F, S>;
+): <F extends PropertyKey>(first: ReadonlyArray<F>) => Record<F, S>;
 
-export function zipObj() {
+export function zipObj(): unknown {
   return purry(_zipObj, arguments);
 }
 
 function _zipObj(
-  first: Array<number | string | symbol>,
-  second: Array<unknown>,
-) {
+  first: ReadonlyArray<PropertyKey>,
+  second: ReadonlyArray<unknown>,
+): Record<PropertyKey, unknown> {
   const resultLength =
     first.length > second.length ? second.length : first.length;
-  const result: Record<number | string | symbol, unknown> = {};
+  const result: Record<PropertyKey, unknown> = {};
   for (let i = 0; i < resultLength; i++) {
     result[first[i]!] = second[i];
   }
diff --git a/src/zipWith.ts b/src/zipWith.ts
index f1c5617..7166048 100644
--- a/src/zipWith.ts
+++ b/src/zipWith.ts
@@ -74,7 +74,7 @@ function _zipWith<F, S, R>(
   first: ReadonlyArray<F>,
   second: ReadonlyArray<S>,
   fn: ZippingFunction<F, S, R>,
-) {
+): Array<R> {
   const resultLength =
     first.length > second.length ? second.length : first.length;
   const result = [];
diff --git a/tsconfig.json b/tsconfig.json
index 7115edf..728220d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,7 +1,7 @@
 {
   "$schema": "http://json.schemastore.org/tsconfig",
 
-  "include": ["src", "test", "*.config.js", "*.config.ts"],
+  "include": ["src", "test", "*.config.mjs", "*.config.ts"],
 
   "compilerOptions": {
     // TYPE CHECKING


Test patch:
diff --git a/src/_binarySearchCutoffIndex.test.ts b/src/_binarySearchCutoffIndex.test.ts
index a32410e..c957dff 100644
--- a/src/_binarySearchCutoffIndex.test.ts
+++ b/src/_binarySearchCutoffIndex.test.ts
@@ -167,10 +167,10 @@ function indicesSeen(
   items: ReadonlyArray<unknown>,
   predicate: (item: unknown, index: number) => boolean,
 ): ReadonlyArray<number> {
-  const indicesSeen: Array<number> = [];
+  const indices: Array<number> = [];
   _binarySearchCutoffIndex(items, (pivot, index) => {
-    indicesSeen.push(index);
+    indices.push(index);
     return predicate(pivot, index);
   });
-  return indicesSeen;
+  return indices;
 }
diff --git a/src/ceil.test.ts b/src/ceil.test.ts
index b0def6e..2a8d35b 100644
--- a/src/ceil.test.ts
+++ b/src/ceil.test.ts
@@ -3,24 +3,27 @@ import { ceil } from "./ceil";
 describe("data-first", () => {
   test("should work with positive precision", () => {
     expect(ceil(8123.4317, 3)).toEqual(8123.432);
-    expect(ceil(483.22243, 1)).toEqual(483.3);
+    expect(ceil(483.222_43, 1)).toEqual(483.3);
     expect(ceil(123.4317, 5)).toEqual(123.4317);
   });
 
   test("should work with negative precision", () => {
     expect(ceil(8123.4317, -2)).toEqual(8200);
-    expect(ceil(8123.4317, -4)).toEqual(10000);
+    expect(ceil(8123.4317, -4)).toEqual(10_000);
   });
 
   test("should work with precision = 0", () => {
     expect(ceil(8123.4317, 0)).toEqual(8124);
   });
 
-  test.each([NaN, Infinity])("should throw for %d precision", (val) => {
-    expect(() => ceil(1, val)).toThrowError(
-      `precision must be an integer: ${val}`,
-    );
-  });
+  test.each([Number.NaN, Number.POSITIVE_INFINITY])(
+    "should throw for %d precision",
+    (val) => {
+      expect(() => ceil(1, val)).toThrowError(
+        `precision must be an integer: ${val}`,
+      );
+    },
+  );
 
   test("should throw for non integer precision", () => {
     expect(() => ceil(1, 21.37)).toThrowError(
@@ -37,7 +40,7 @@ describe("data-first", () => {
     );
   });
 
-  test.each([NaN, Infinity, -Infinity])(
+  test.each([Number.NaN, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY])(
     "should return %d when passed as value regardless of precision",
     (val) => {
       for (const precision of [-1, 0, 1]) {
@@ -50,24 +53,27 @@ describe("data-first", () => {
 describe("data-last", () => {
   test("should work with positive precision", () => {
     expect(ceil(3)(8123.4317)).toEqual(8123.432);
-    expect(ceil(1)(483.22243)).toEqual(483.3);
+    expect(ceil(1)(483.222_43)).toEqual(483.3);
     expect(ceil(5)(123.4317)).toEqual(123.4317);
   });
 
   test("should work with negative precision", () => {
     expect(ceil(-2)(8123.4317)).toEqual(8200);
-    expect(ceil(-4)(8123.4317)).toEqual(10000);
+    expect(ceil(-4)(8123.4317)).toEqual(10_000);
   });
 
   test("should work with precision = 0", () => {
     expect(ceil(0)(8123.4317)).toEqual(8124);
   });
 
-  test.each([NaN, Infinity])("should throw for %d precision", (val) => {
-    expect(() => ceil(val)(1)).toThrowError(
-      `precision must be an integer: ${val}`,
-    );
-  });
+  test.each([Number.NaN, Number.POSITIVE_INFINITY])(
+    "should throw for %d precision",
+    (val) => {
+      expect(() => ceil(val)(1)).toThrowError(
+        `precision must be an integer: ${val}`,
+      );
+    },
+  );
 
   test("should throw for non integer precision", () => {
     expect(() => ceil(21.37)(1)).toThrowError(
@@ -84,7 +90,7 @@ describe("data-last", () => {
     );
   });
 
-  test.each([NaN, Infinity, -Infinity])(
+  test.each([Number.NaN, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY])(
     "should return %d when passed as value regardless of precision",
     (val) => {
       for (const precision of [-1, 0, 1]) {
diff --git a/src/clone.test.ts b/src/clone.test.ts
index 55685a6..fea6762 100644
--- a/src/clone.test.ts
+++ b/src/clone.test.ts
@@ -2,19 +2,21 @@
 
 import { clone } from "./clone";
 
-const eq = (a: any, b: any) => {
+const eq = (a: any, b: any): void => {
   expect(a).toEqual(b);
 };
 
+const fn = (x: number): number => x + x;
+
 describe("deep clone integers, strings and booleans", () => {
   it("clones integers", () => {
     eq(clone(-4), -4);
-    eq(clone(9007199254740991), 9007199254740991);
+    eq(clone(9_007_199_254_740_991), 9_007_199_254_740_991);
   });
 
   it("clones floats", () => {
     eq(clone(-4.5), -4.5);
-    eq(clone(0.0), 0.0);
+    eq(clone(0), 0);
   });
 
   it("clones strings", () => {
@@ -86,7 +88,6 @@ describe("deep clone arrays", () => {
 
 describe("deep clone functions", () => {
   it("keep reference to function", () => {
-    const fn = (x: number) => x + x;
     const list = [{ a: fn }] as const;
 
     const cloned = clone(list);
@@ -108,8 +109,9 @@ describe("built-in types", () => {
     eq(cloned.getDay(), 5); // friday
   });
 
-  it("clones RegExp object", () => {
-    [/x/, /x/g, /x/i, /x/m, /x/gi, /x/gm, /x/im, /x/gim].forEach((pattern) => {
+  it.each([/x/u, /x/gu, /x/iu, /x/mu, /x/giu, /x/gmu, /x/imu, /x/gimu])(
+    "clones RegExp object",
+    (pattern) => {
       const cloned = clone(pattern);
       assert.notStrictEqual(cloned, pattern);
       eq(cloned.constructor, RegExp);
@@ -117,8 +119,8 @@ describe("built-in types", () => {
       eq(cloned.global, pattern.global);
       eq(cloned.ignoreCase, pattern.ignoreCase);
       eq(cloned.multiline, pattern.multiline);
-    });
-  });
+    },
+  );
 });
 
 describe("deep clone deep nested mixed objects", () => {
diff --git a/src/debounce.test.ts b/src/debounce.test.ts
index ba01d5a..21a80b2 100644
--- a/src/debounce.test.ts
+++ b/src/debounce.test.ts
@@ -421,7 +421,7 @@ describe("typing", () => {
   test("argument typing to be good (with rest param)", async () => {
     const debouncer = debounce(
       (a: string, ...flags: ReadonlyArray<boolean>) =>
-        `${a}${flags.map((flag) => (flag ? "y" : "n")).join()}`,
+        `${a}${flags.map((flag) => (flag ? "y" : "n")).join(",")}`,
       { timing: "leading" },
     );
     // @ts-expect-error [ts2554]: Expected 3 arguments, but got 1.
@@ -457,5 +457,7 @@ describe("typing", () => {
 });
 
 async function sleep(ms: number): Promise<void> {
-  await new Promise((resolve) => setTimeout(resolve, ms));
+  await new Promise((resolve) => {
+    setTimeout(resolve, ms);
+  });
 }
diff --git a/src/drop.test.ts b/src/drop.test.ts
index dac2e8e..9d2c073 100644
--- a/src/drop.test.ts
+++ b/src/drop.test.ts
@@ -15,7 +15,7 @@ describe("data first", () => {
     expect(drop(array, 0)).toEqual(array);
     expect(drop(array, -0)).toEqual(array);
     expect(drop(array, -1)).toEqual(array);
-    expect(drop(array, NaN)).toEqual(array);
+    expect(drop(array, Number.NaN)).toEqual(array);
   });
 
   test("should return a new array even if there was no drop", () => {
diff --git a/src/dropLast.test.ts b/src/dropLast.test.ts
index c35cb10..36ce39a 100644
--- a/src/dropLast.test.ts
+++ b/src/dropLast.test.ts
@@ -10,7 +10,7 @@ test("should not drop last", () => {
   expect(dropLast(arr, 0)).toEqual(arr);
   expect(dropLast(arr, -0)).toEqual(arr);
   expect(dropLast(arr, -1)).toEqual(arr);
-  expect(dropLast(arr, NaN)).toEqual(arr);
+  expect(dropLast(arr, Number.NaN)).toEqual(arr);
 });
 
 test("should return a new array even if there was no drop", () => {
diff --git a/src/equals.test.ts b/src/equals.test.ts
index 0ef01f6..a4f1ebd 100644
--- a/src/equals.test.ts
+++ b/src/equals.test.ts
@@ -1,356 +1,211 @@
 import { equals } from "./equals";
 
-const tests = [
-  {
-    description: "scalars",
-    tests: [
-      {
-        description: "equal numbers",
-        value1: 1,
-        value2: 1,
-        equal: true,
-      },
-      {
-        description: "not equal numbers",
-        value1: 1,
-        value2: 2,
-        equal: false,
-      },
-      {
-        description: "number and array are not equal",
-        value1: 1,
-        value2: [],
-        equal: false,
-      },
-      {
-        description: "0 and null are not equal",
-        value1: 0,
-        value2: null,
-        equal: false,
-      },
-      {
-        description: "equal strings",
-        value1: "a",
-        value2: "a",
-        equal: true,
-      },
-      {
-        description: "not equal strings",
-        value1: "a",
-        value2: "b",
-        equal: false,
-      },
-      {
-        description: "empty string and null are not equal",
-        value1: "",
-        value2: null,
-        equal: false,
-      },
-      {
-        description: "null is equal to null",
-        value1: null,
-        value2: null,
-        equal: true,
-      },
-      {
-        description: "equal booleans (true)",
-        value1: true,
-        value2: true,
-        equal: true,
-      },
-      {
-        description: "equal booleans (false)",
-        value1: false,
-        value2: false,
-        equal: true,
-      },
-      {
-        description: "not equal booleans",
-        value1: true,
-        value2: false,
-        equal: false,
-      },
-      {
-        description: "1 and true are not equal",
-        value1: 1,
-        value2: true,
-        equal: false,
-      },
-      {
-        description: "0 and false are not equal",
-        value1: 0,
-        value2: false,
-        equal: false,
-      },
-      {
-        description: "NaN and NaN are equal",
-        value1: NaN,
-        value2: NaN,
-        equal: true,
-      },
-      {
-        description: "0 and -0 are equal",
-        value1: 0,
-        value2: -0,
-        equal: true,
-      },
-      {
-        description: "Infinity and Infinity are equal",
-        value1: Infinity,
-        value2: Infinity,
-        equal: true,
-      },
-      {
-        description: "Infinity and -Infinity are not equal",
-        value1: Infinity,
-        value2: -Infinity,
-        equal: false,
-      },
-    ],
-  },
+function func1(): void {
+  // (intentionally empty)
+}
+function func2(): void {
+  // (intentionally empty)
+}
 
-  {
-    description: "objects",
-    tests: [
-      {
-        description: "empty objects are equal",
-        value1: {},
-        value2: {},
-        equal: true,
-      },
-      {
-        description: 'equal objects (same properties "order")',
-        value1: { a: 1, b: "2" },
-        value2: { a: 1, b: "2" },
-        equal: true,
-      },
-      {
-        description: 'equal objects (different properties "order")',
-        value1: { a: 1, b: "2" },
-        value2: { b: "2", a: 1 },
-        equal: true,
-      },
-      {
-        description: "not equal objects (extra property)",
-        value1: { a: 1, b: "2" },
-        value2: { a: 1, b: "2", c: [] },
-        equal: false,
-      },
-      {
-        description: "not equal objects (different properties)",
-        value1: { a: 1, b: "2", c: 3 },
-        value2: { a: 1, b: "2", d: 3 },
-        equal: false,
-      },
-      {
-        description: "not equal objects (different properties)",
-        value1: { a: 1, b: "2", c: 3 },
-        value2: { a: 1, b: "2", d: 3 },
-        equal: false,
-      },
-      {
-        description: "equal objects (same sub-properties)",
-        value1: { a: [{ b: "c" }] },
-        value2: { a: [{ b: "c" }] },
-        equal: true,
-      },
-      {
-        description: "not equal objects (different sub-property value)",
-        value1: { a: [{ b: "c" }] },
-        value2: { a: [{ b: "d" }] },
-        equal: false,
-      },
-      {
-        description: "not equal objects (different sub-property)",
-        value1: { a: [{ b: "c" }] },
-        value2: { a: [{ c: "c" }] },
-        equal: false,
-      },
-      {
-        description: "empty array and empty object are not equal",
-        value1: {},
-        value2: [],
-        equal: false,
-      },
-      {
-        description: "object with extra undefined properties are not equal #1",
-        value1: {},
-        value2: { foo: undefined },
-        equal: false,
-      },
-      {
-        description: "object with extra undefined properties are not equal #2",
-        value1: { foo: undefined },
-        value2: {},
-        equal: false,
-      },
-      {
-        description: "object with extra undefined properties are not equal #3",
-        value1: { foo: undefined },
-        value2: { bar: undefined },
-        equal: false,
-      },
-      {
-        description: "nulls are equal",
-        value1: null,
-        value2: null,
-        equal: true,
-      },
-      {
-        description: "null and undefined are not equal",
-        value1: null,
-        value2: undefined,
-        equal: false,
-      },
-      {
-        description: "null and empty object are not equal",
-        value1: null,
-        value2: {},
-        equal: false,
-      },
-      {
-        description: "undefined and empty object are not equal",
-        value1: undefined,
-        value2: {},
-        equal: false,
-      },
-    ],
-  },
+describe("scalars", () => {
+  test("equal numbers", () => {
+    expect(equals(1, 1)).toBe(true);
+  });
+  test("not equal numbers", () => {
+    expect(equals(1, 2)).toBe(false);
+  });
+  test("number and array are not equal", () => {
+    expect(equals(1, [])).toBe(false);
+  });
+  test("0 and null are not equal", () => {
+    expect(equals(0, null)).toBe(false);
+  });
+  test("equal strings", () => {
+    expect(equals("a", "a")).toBe(true);
+  });
+  test("not equal strings", () => {
+    expect(equals("a", "b")).toBe(false);
+  });
+  test("empty string and null are not equal", () => {
+    expect(equals("", null)).toBe(false);
+  });
+  test("null is equal to null", () => {
+    expect(equals(null, null)).toBe(true);
+  });
+  test("equal booleans (true)", () => {
+    expect(equals(true, true)).toBe(true);
+  });
+  test("equal booleans (false)", () => {
+    expect(equals(false, false)).toBe(true);
+  });
+  test("not equal booleans", () => {
+    expect(equals(true, false)).toBe(false);
+  });
+  test("1 and true are not equal", () => {
+    expect(equals(1, true)).toBe(false);
+  });
+  test("0 and false are not equal", () => {
+    expect(equals(0, false)).toBe(false);
+  });
+  test("NaN and NaN are equal", () => {
+    expect(equals(Number.NaN, Number.NaN)).toBe(true);
+  });
+  test("0 and -0 are equal", () => {
+    expect(equals(0, -0)).toBe(true);
+  });
+  test("Infinity and Infinity are equal", () => {
+    expect(equals(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY)).toBe(
+      true,
+    );
+  });
+  test("Infinity and -Infinity are not equal", () => {
+    expect(equals(Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY)).toBe(
+      false,
+    );
+  });
+});
 
-  {
-    description: "arrays",
-    tests: [
-      {
-        description: "two empty arrays are equal",
-        value1: [],
-        value2: [],
-        equal: true,
-      },
-      {
-        description: "equal arrays",
-        value1: [1, 2, 3],
-        value2: [1, 2, 3],
-        equal: true,
-      },
-      {
-        description: "not equal arrays (different item)",
-        value1: [1, 2, 3],
-        value2: [1, 2, 4],
-        equal: false,
-      },
-      {
-        description: "not equal arrays (different length)",
-        value1: [1, 2, 3],
-        value2: [1, 2],
-        equal: false,
-      },
-      {
-        description: "equal arrays of objects",
-        value1: [{ a: "a" }, { b: "b" }],
-        value2: [{ a: "a" }, { b: "b" }],
-        equal: true,
-      },
-      {
-        description: "not equal arrays of objects",
-        value1: [{ a: "a" }, { b: "b" }],
-        value2: [{ a: "a" }, { b: "c" }],
-        equal: false,
-      },
-      {
-        description: "pseudo array and equivalent array are not equal",
-        value1: { "0": 0, "1": 1, length: 2 },
-        value2: [0, 1],
-        equal: false,
-      },
-    ],
-  },
-  {
-    description: "Date objects",
-    tests: [
-      {
-        description: "equal date objects",
-        value1: new Date("2017-06-16T21:36:48.362Z"),
-        value2: new Date("2017-06-16T21:36:48.362Z"),
-        equal: true,
-      },
-      {
-        description: "not equal date objects",
-        value1: new Date("2017-06-16T21:36:48.362Z"),
-        value2: new Date("2017-01-01T00:00:00.000Z"),
-        equal: false,
-      },
-      {
-        description: "date and string are not equal",
-        value1: new Date("2017-06-16T21:36:48.362Z"),
-        value2: "2017-06-16T21:36:48.362Z",
-        equal: false,
-      },
-      {
-        description: "date and object are not equal",
-        value1: new Date("2017-06-16T21:36:48.362Z"),
-        value2: {},
-        equal: false,
-      },
-    ],
-  },
-  {
-    description: "RegExp objects",
-    tests: [
-      {
-        description: "equal RegExp objects",
-        value1: /foo/,
-        value2: /foo/,
-        equal: true,
-      },
-      {
-        description: "not equal RegExp objects (different pattern)",
-        value1: /foo/,
-        value2: /bar/,
-        equal: false,
-      },
-      {
-        description: "not equal RegExp objects (different flags)",
-        value1: /foo/,
-        value2: /foo/i,
-        equal: false,
-      },
-      {
-        description: "RegExp and string are not equal",
-        value1: /foo/,
-        value2: "foo",
-        equal: false,
-      },
-      {
-        description: "RegExp and object are not equal",
-        value1: /foo/,
-        value2: {},
-        equal: false,
-      },
-    ],
-  },
-  {
-    description: "functions",
-    tests: [
-      {
-        description: "same function is equal",
-        value1: func1,
-        value2: func1,
-        equal: true,
-      },
-      {
-        description: "different functions are not equal",
-        value1: func1,
-        value2: func2,
-        equal: false,
-      },
-    ],
-  },
-  {
-    description: "sample objects",
-    tests: [
-      {
-        description: "big object",
-        value1: {
+describe("objects", () => {
+  test("empty objects are equal", () => {
+    expect(equals({}, {})).toBe(true);
+  });
+  test('equal objects (same properties "order")', () => {
+    expect(equals({ a: 1, b: "2" }, { a: 1, b: "2" })).toBe(true);
+  });
+  test('equal objects (different properties "order")', () => {
+    expect(equals({ a: 1, b: "2" }, { b: "2", a: 1 })).toBe(true);
+  });
+  test("not equal objects (extra property)", () => {
+    expect(equals({ a: 1, b: "2" }, { a: 1, b: "2", c: [] })).toBe(false);
+  });
+  test("not equal objects (different properties) #1", () => {
+    expect(equals({ a: 1, b: "2", c: 3 }, { a: 1, b: "2", d: 3 })).toBe(false);
+  });
+  test("not equal objects (different properties) #2", () => {
+    expect(equals({ a: 1, b: "2", c: 3 }, { a: 1, b: "2", d: 3 })).toBe(false);
+  });
+  test("equal objects (same sub-properties)", () => {
+    expect(equals({ a: [{ b: "c" }] }, { a: [{ b: "c" }] })).toBe(true);
+  });
+  test("not equal objects (different sub-property value)", () => {
+    expect(equals({ a: [{ b: "c" }] }, { a: [{ b: "d" }] })).toBe(false);
+  });
+  test("not equal objects (different sub-property)", () => {
+    expect(equals({ a: [{ b: "c" }] }, { a: [{ c: "c" }] })).toBe(false);
+  });
+  test("empty array and empty object are not equal", () => {
+    expect(equals({}, [])).toBe(false);
+  });
+  test("object with extra undefined properties are not equal #1", () => {
+    expect(equals({}, { foo: undefined })).toBe(false);
+  });
+  test("object with extra undefined properties are not equal #2", () => {
+    expect(equals({ foo: undefined }, {})).toBe(false);
+  });
+  test("object with extra undefined properties are not equal #3", () => {
+    expect(equals({ foo: undefined }, { bar: undefined })).toBe(false);
+  });
+  test("nulls are equal", () => {
+    expect(equals(null, null)).toBe(true);
+  });
+  test("null and undefined are not equal", () => {
+    expect(equals(null, undefined)).toBe(false);
+  });
+  test("null and empty object are not equal", () => {
+    expect(equals(null, {})).toBe(false);
+  });
+  test("undefined and empty object are not equal", () => {
+    expect(equals(undefined, {})).toBe(false);
+  });
+});
+
+describe("arrays", () => {
+  test("two empty arrays are equal", () => {
+    expect(equals([], [])).toBe(true);
+  });
+  test("equal arrays", () => {
+    expect(equals([1, 2, 3], [1, 2, 3])).toBe(true);
+  });
+  test("not equal arrays (different item)", () => {
+    expect(equals([1, 2, 3], [1, 2, 4])).toBe(false);
+  });
+  test("not equal arrays (different length)", () => {
+    expect(equals([1, 2, 3], [1, 2])).toBe(false);
+  });
+  test("equal arrays of objects", () => {
+    expect(equals([{ a: "a" }, { b: "b" }], [{ a: "a" }, { b: "b" }])).toBe(
+      true,
+    );
+  });
+  test("not equal arrays of objects", () => {
+    expect(equals([{ a: "a" }, { b: "b" }], [{ a: "a" }, { b: "c" }])).toBe(
+      false,
+    );
+  });
+  test("pseudo array and equivalent array are not equal", () => {
+    expect(equals({ "0": 0, "1": 1, length: 2 }, [0, 1])).toBe(false);
+  });
+});
+
+describe("Date objects", () => {
+  test("equal date objects", () => {
+    expect(
+      equals(
+        new Date("2017-06-16T21:36:48.362Z"),
+        new Date("2017-06-16T21:36:48.362Z"),
+      ),
+    ).toBe(true);
+  });
+  test("not equal date objects", () => {
+    expect(
+      equals(
+        new Date("2017-06-16T21:36:48.362Z"),
+        new Date("2017-01-01T00:00:00.000Z"),
+      ),
+    ).toBe(false);
+  });
+  test("date and string are not equal", () => {
+    expect(
+      equals(new Date("2017-06-16T21:36:48.362Z"), "2017-06-16T21:36:48.362Z"),
+    ).toBe(false);
+  });
+  test("date and object are not equal", () => {
+    expect(equals(new Date("2017-06-16T21:36:48.362Z"), {})).toBe(false);
+  });
+});
+
+describe("RegExp objects", () => {
+  test("equal RegExp objects", () => {
+    expect(equals(/foo/u, /foo/u)).toBe(true);
+  });
+  test("not equal RegExp objects (different pattern)", () => {
+    expect(equals(/foo/u, /bar/u)).toBe(false);
+  });
+  test("not equal RegExp objects (different flags)", () => {
+    expect(equals(/foo/u, /foo/iu)).toBe(false);
+  });
+  test("RegExp and string are not equal", () => {
+    expect(equals(/foo/u, "foo")).toBe(false);
+  });
+  test("RegExp and object are not equal", () => {
+    expect(equals(/foo/u, {})).toBe(false);
+  });
+});
+
+describe("functions", () => {
+  test("same function is equal", () => {
+    expect(equals(func1, func1)).toBe(true);
+  });
+  test("different functions are not equal", () => {
+    expect(equals(func1, func2)).toBe(false);
+  });
+});
+
+describe("sample objects", () => {
+  test("big object", () => {
+    expect(
+      equals(
+        {
           prop1: "value1",
           prop2: "value2",
           prop3: "value3",
@@ -364,7 +219,7 @@ const tests = [
           prop5: 1000,
           prop6: new Date(2016, 2, 10),
         },
-        value2: {
+        {
           prop5: 1000,
           prop3: "value3",
           prop1: "value1",
@@ -378,27 +233,7 @@ const tests = [
             subProp1: "sub value1",
           },
         },
-        equal: true,
-      },
-    ],
-  },
-];
-
-function func1() {
-  //
-}
-function func2() {
-  //
-}
-
-describe("equal", () => {
-  tests.forEach((suite) => {
-    describe(suite.description, () => {
-      suite.tests.forEach((test) => {
-        it(test.description, () => {
-          expect(equals(test.value1, test.value2)).toEqual(test.equal);
-        });
-      });
-    });
+      ),
+    ).toBe(true);
   });
 });
diff --git a/src/evolve.test.ts b/src/evolve.test.ts
index 0f1534a..e0a037c 100644
--- a/src/evolve.test.ts
+++ b/src/evolve.test.ts
@@ -158,7 +158,7 @@ describe("data last", () => {
         ],
       },
       evolve({
-        array: (array: Array<string>) => array.length,
+        array: (array: ReadonlyArray<string>) => array.length,
         nestedObj: { a: set<{ b: string }, "b">("b", "Set") },
         objAry: map(omit<{ a: number; b: number }, "b">(["b"])),
       }),
@@ -190,7 +190,9 @@ describe("typing", () => {
 
       it("can reflect type of data to function of evolver object", () => {
         const result = evolve(data, {
-          count: (x: number) => x, // type of parameter is required because `count` property is not defined in data
+          // type of parameter is required because `count` property is not
+          // defined in data
+          count: (x: number) => x,
           quartile: (x) => x,
           time: (x) => x,
         });
@@ -200,7 +202,9 @@ describe("typing", () => {
 
       it("can reflect type of data to function of nested evolver object", () => {
         const result = evolve(data, {
-          count: (x: number) => x, // type of parameter is required because `count` property is not defined in data
+          // type of parameter is required because `count` property is not
+          // defined in data
+          count: (x: number) => x,
           quartile: (x) => x,
           time: { elapsed: (x) => x, remaining: (x) => x },
         });
@@ -221,33 +225,18 @@ describe("typing", () => {
           ],
         },
         {
-          // @ts-expect-error -- Type 'string' is not assignable to type 'number'.ts(2322)
+          // @ts-expect-error [ts2322] - Type 'string' is not assignable to type 'number'.
           number: add(1),
-          // @ts-expect-error -- Type 'string' is not assignable to type 'number'.ts(2322)
+          // @ts-expect-error [ts2322] - Type 'string' is not assignable to type 'number'.
           array: (array: ReadonlyArray<number>) => array.length,
-          // @ts-expect-error -- Type 'string' is not assignable to type 'number'.ts(2322)
+          // @ts-expect-error [ts2322] - Type 'string' is not assignable to type 'number'.
           nestedObj: { a: set<{ b: number }, "b">("b", 0) },
-          // @ts-expect-error -- Type 'string' is not assignable to type 'number'.ts(2322)
+          // @ts-expect-error [ts2322] - Type 'string' is not assignable to type 'number'.
           objAry: map(omit<{ a: number; b: number }, "b">(["b"])),
         },
       );
     });
 
-    it("does not accept the input value that is not Array and Object", () => {
-      const evolver = { a: add(1) };
-      // Type check only
-      () => {
-        // @ts-expect-error -- Argument of type '{ a: (value: number) => number; }' is not assignable to parameter of type 'never'.ts(2345)
-        evolve(0, evolver);
-        // @ts-expect-error -- Argument of type '{ a: (value: number) => number; }' is not assignable to parameter of type 'never'.ts(2345)
-        expect(evolve(undefined, evolver)).toThrowError();
-        // @ts-expect-error -- Argument of type '{ a: (value: number) => number; }' is not assignable to parameter of type 'never'.ts(2345)
-        expect(evolve(null, evolver)).toThrowError();
-        // @ts-expect-error -- Argument of type '{ a: (value: number) => number; }' is not assignable to parameter of type 'never'.ts(2345)
-        expect(evolve("", evolver)).toThrowError();
-      };
-    });
-
     it("does not accept function that require multiple arguments", () => {
       evolve(
         {
@@ -255,9 +244,9 @@ describe("typing", () => {
           requiring3Args: 1,
         },
         {
-          // @ts-expect-error -- Target signature provides too few arguments. Expected 2 or more, but got 1.ts(2322)
+          // @ts-expect-error [ts2322] - Target signature provides too few arguments. Expected 2 or more, but got 1.
           requiring2Args: (a: number, b: number) => a + b,
-          // @ts-expect-error -- Target signature provides too few arguments. Expected 3 or more, but got 1.ts(2322)
+          // @ts-expect-error [ts2322] - Target signature provides too few arguments. Expected 3 or more, but got 1.
           requiring3Args: (a: number, b: number | undefined, c: number) =>
             a + (b ?? 0) + c,
         },
@@ -292,7 +281,7 @@ describe("typing", () => {
           quartile: [1, 2],
         },
         {
-          // @ts-expect-error -- Type '((value: number) => number)[]' provides no match for the signature '(data: number[]): unknown'.ts(2322)
+          // @ts-expect-error [ts2322] - Type '((value: number) => number)[]' provides no match for the signature '(data: number[]): unknown'.
           quartile: [add(1), add(-1)],
         },
       );
@@ -311,7 +300,7 @@ describe("typing", () => {
             number: "1",
             array: ["1", "2", "3"],
           },
-          // @ts-expect-error -- [ts2345]: Type 'string' is not assignable to type 'number | undefined'.
+          // @ts-expect-error [ts2345] - Type 'string' is not assignable to type 'number | undefined'.
           evolve(evolver),
         );
       });
@@ -325,35 +314,20 @@ describe("typing", () => {
             number: 1,
             array: ["1", "2", "3"],
           },
-          // @ts-expect-error -- [ts2345]: Type 'string[]' is not assignable to type 'readonly number[]'.
+          // @ts-expect-error [ts2345] - Type 'string[]' is not assignable to type 'readonly number[]'.
           evolve(evolver),
         );
       });
     });
 
-    it("does not accept the input value that is not Array and Object", () => {
-      const evolver = { a: add(1) };
-      // Type check only
-      () => {
-        // @ts-expect-error -- Type '{ a: any; }' provides no match for the signature '(input: number): unknown'.ts(2345)
-        pipe(0, evolve(evolver));
-        // @ts-expect-error -- Type '{ a: any; }' provides no match for the signature '(input: undefined): unknown'.ts(2345)
-        pipe(undefined, evolve(evolver));
-        // @ts-expect-error -- Type '{ a: any; }' provides no match for the signature '(input: null): unknown'.ts(2345)
-        pipe(null, evolve(evolver));
-        // @ts-expect-error -- Type '{ a: any; }' provides no match for the signature '(input: string): unknown'.ts(2345)
-        pipe("", evolve(evolver));
-      };
-    });
-
     it("does not accept function that require multiple arguments", () => {
       pipe(
         {
           requiring2Args: 1,
         },
-        // @ts-expect-error -- Type '{ requiring2Args: any; }' provides no match for the signature '(input: { requiring2Args: number; }): unknown'.ts(2345)
+        // @ts-expect-error [ts2345] - Type '{ requiring2Args: any; }' provides no match for the signature '(input: { requiring2Args: number; }): unknown'.
         evolve({
-          // @ts-expect-error -- Target signature provides too few arguments. Expected 2 or more, but got 1.ts(2322)
+          // @ts-expect-error [ts2322] - Target signature provides too few arguments. Expected 2 or more, but got 1.
           requiring2Args: (a: number, b: number) => a + b,
         }),
       );
@@ -386,9 +360,9 @@ describe("typing", () => {
         {
           quartile: [1, 2],
         },
-        // @ts-expect-error -- Type '{ quartile: ((value: number) => number)[]; }' provides no match for the signature '(input: { quartile: number[]; }): unknown'.ts(2345)
+        // @ts-expect-error [ts2345] - Type '{ quartile: ((value: number) => number)[]; }' provides no match for the signature '(input: { quartile: number[]; }): unknown'.
         evolve({
-          // @ts-expect-error -- Type '((value: number) => number)[]' provides no match for the signature '(data: number[]): unknown'.ts(2322)
+          // @ts-expect-error [ts2322] - Type '((value: number) => number)[]' provides no match for the signature '(data: number[]): unknown'.
           quartile: [add(1), add(-1)],
         }),
       );
diff --git a/src/first.test.ts b/src/first.test.ts
index 1abf414..a742a4c 100644
--- a/src/first.test.ts
+++ b/src/first.test.ts
@@ -5,7 +5,7 @@ import { pipe } from "./pipe";
 
 function defaultTo<T>(d: T) {
   return function (v: T | null | undefined) {
-    return v == null ? d : v;
+    return v ?? d;
   };
 }
 
diff --git a/src/floor.test.ts b/src/floor.test.ts
index 6ba5db4..d5374c8 100644
--- a/src/floor.test.ts
+++ b/src/floor.test.ts
@@ -3,7 +3,7 @@ import { floor } from "./floor";
 describe("data-first", () => {
   test("should work with positive precision", () => {
     expect(floor(8123.4317, 3)).toEqual(8123.431);
-    expect(floor(483.22243, 1)).toEqual(483.2);
+    expect(floor(483.222_43, 1)).toEqual(483.2);
     expect(floor(123.4317, 5)).toEqual(123.4317);
   });
 
@@ -16,11 +16,14 @@ describe("data-first", () => {
     expect(floor(8123.4317, 0)).toEqual(8123);
   });
 
-  test.each([NaN, Infinity])("should throw for %d precision", (val) => {
-    expect(() => floor(1, val)).toThrowError(
-      `precision must be an integer: ${val}`,
-    );
-  });
+  test.each([Number.NaN, Number.POSITIVE_INFINITY])(
+    "should throw for %d precision",
+    (val) => {
+      expect(() => floor(1, val)).toThrowError(
+        `precision must be an integer: ${val}`,
+      );
+    },
+  );
 
   test("should throw for non integer precision", () => {
     expect(() => floor(1, 21.37)).toThrowError(
@@ -37,7 +40,7 @@ describe("data-first", () => {
     );
   });
 
-  test.each([NaN, Infinity, -Infinity])(
+  test.each([Number.NaN, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY])(
     "should return %d when passed as value regardless of precision",
     (val) => {
       for (const precision of [-1, 0, 1]) {
@@ -50,7 +53,7 @@ describe("data-first", () => {
 describe("data-last", () => {
   test("should work with positive precision", () => {
     expect(floor(3)(8123.4317)).toEqual(8123.431);
-    expect(floor(1)(483.22243)).toEqual(483.2);
+    expect(floor(1)(483.222_43)).toEqual(483.2);
     expect(floor(5)(123.4317)).toEqual(123.4317);
   });
 
@@ -63,11 +66,14 @@ describe("data-last", () => {
     expect(floor(0)(8123.4317)).toEqual(8123);
   });
 
-  test.each([NaN, Infinity])("should throw for %d precision", (val) => {
-    expect(() => floor(val)(1)).toThrowError(
-      `precision must be an integer: ${val}`,
-    );
-  });
+  test.each([Number.NaN, Number.POSITIVE_INFINITY])(
+    "should throw for %d precision",
+    (val) => {
+      expect(() => floor(val)(1)).toThrowError(
+        `precision must be an integer: ${val}`,
+      );
+    },
+  );
 
   test("should throw for non integer precision", () => {
     expect(() => floor(21.37)(1)).toThrowError(
@@ -84,7 +90,7 @@ describe("data-last", () => {
     );
   });
 
-  test.each([NaN, Infinity, -Infinity])(
+  test.each([Number.NaN, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY])(
     "should return %d when passed as value regardless of precision",
     (val) => {
       for (const precision of [-1, 0, 1]) {
diff --git a/src/groupBy.test.ts b/src/groupBy.test.ts
index d7b7ed6..8611011 100644
--- a/src/groupBy.test.ts
+++ b/src/groupBy.test.ts
@@ -87,6 +87,7 @@ describe("Filtering on undefined grouper result", () => {
     );
     expect(Object.values(result)).toHaveLength(1);
     expect(result).toHaveProperty("even");
+    // eslint-disable-next-line dot-notation
     expect(result["even"]).toEqual([0, 2, 4, 6, 8]);
   });
 
@@ -97,6 +98,7 @@ describe("Filtering on undefined grouper result", () => {
     );
     expect(Object.values(result)).toHaveLength(1);
     expect(result).toHaveProperty("even");
+    // eslint-disable-next-line dot-notation
     expect(result["even"]).toEqual(["a", "c", "e", "g", "i"]);
   });
 
diff --git a/src/intersectionWith.test.ts b/src/intersectionWith.test.ts
index 17e23d9..3d5f92d 100644
--- a/src/intersectionWith.test.ts
+++ b/src/intersectionWith.test.ts
@@ -38,17 +38,21 @@ describe("intersectionWith", () => {
       ).toEqual(expected);
     });
     it("checks if items are equal based on remeda's imported util function as a comparator", () => {
-      const source = [
-        { x: 1, y: 2 },
-        { x: 2, y: 1 },
-      ];
-      const other = [
-        { x: 1, y: 1 },
-        { x: 1, y: 2 },
-      ];
-      const expected = [{ x: 1, y: 2 }];
-
-      expect(pipe(source, intersectionWith(other, equals))).toEqual(expected);
+      expect(
+        pipe(
+          [
+            { x: 1, y: 2 },
+            { x: 2, y: 1 },
+          ],
+          intersectionWith(
+            [
+              { x: 1, y: 1 },
+              { x: 1, y: 2 },
+            ],
+            equals,
+          ),
+        ),
+      ).toEqual([{ x: 1, y: 2 }]);
     });
 
     it("evaluates lazily", () => {
diff --git a/src/isError.test.ts b/src/isError.test.ts
index c7901f3..e101156 100644
--- a/src/isError.test.ts
+++ b/src/isError.test.ts
@@ -5,6 +5,13 @@ import {
 } from "../test/types_data_provider";
 import { isError } from "./isError";
 
+class MyError extends Error {
+  constructor() {
+    super();
+    this.name = "MyError";
+  }
+}
+
 describe("isError", () => {
   it("should work as type guard", () => {
     const data = TYPES_DATA_PROVIDER.error as AllTypesDataProviderTypes;
@@ -13,8 +20,6 @@ describe("isError", () => {
       expectTypeOf(data).toEqualTypeOf<Error>();
     }
 
-    class MyError extends Error {}
-
     let maybeError: MyError | undefined;
     if (isError(maybeError)) {
       expectTypeOf(maybeError).toEqualTypeOf<MyError>();
diff --git a/src/isNil.test.ts b/src/isNil.test.ts
index 167ffca..4e7f240 100644
--- a/src/isNil.test.ts
+++ b/src/isNil.test.ts
@@ -16,7 +16,8 @@ describe("isNil", () => {
 
   it("should work as type guard in filter", () => {
     const data = ALL_TYPES_DATA_PROVIDER.filter(isNil);
-    expect(data.every((c) => c == null)).toEqual(true);
+    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Intentional for the test.
+    expect(data.every((c) => c === null || c === undefined)).toEqual(true);
     expectTypeOf(data).toEqualTypeOf<Array<null | undefined>>();
   });
 });
diff --git a/src/isNumber.test.ts b/src/isNumber.test.ts
index b4aab76..30adef1 100644
--- a/src/isNumber.test.ts
+++ b/src/isNumber.test.ts
@@ -5,6 +5,8 @@ import {
 } from "../test/types_data_provider";
 import { isNumber } from "./isNumber";
 
+const dataFunction = (): string | 1 | 2 | 3 => 1;
+
 describe("isNumber", () => {
   it("should work as type guard", () => {
     const data = TYPES_DATA_PROVIDER.number as AllTypesDataProviderTypes;
@@ -29,8 +31,7 @@ describe("isNumber", () => {
   });
 
   it("should work with literal types", () => {
-    const data = (): string | 1 | 2 | 3 => 1;
-    const x = data();
+    const x = dataFunction();
     if (isNumber(x)) {
       expect(typeof x).toEqual("number");
       expectTypeOf(x).toEqualTypeOf<1 | 2 | 3>(x);
diff --git a/src/isString.test.ts b/src/isString.test.ts
index 8302df1..333715e 100644
--- a/src/isString.test.ts
+++ b/src/isString.test.ts
@@ -5,6 +5,8 @@ import {
 } from "../test/types_data_provider";
 import { isString } from "./isString";
 
+const dataFunction = (): number | "a" | "b" | "c" => "a";
+
 describe("isString", () => {
   it("should work as type guard", () => {
     const data = TYPES_DATA_PROVIDER.string as AllTypesDataProviderTypes;
@@ -23,8 +25,7 @@ describe("isString", () => {
   });
 
   it("should work with literal types", () => {
-    const data = (): number | "a" | "b" | "c" => "a";
-    const x = data();
+    const x = dataFunction();
     if (isString(x)) {
       expect(typeof x).toEqual("string");
       expectTypeOf(x).toEqualTypeOf<"a" | "b" | "c">();
diff --git a/src/last.test.ts b/src/last.test.ts
index c5bf4c0..06a2779 100644
--- a/src/last.test.ts
+++ b/src/last.test.ts
@@ -37,7 +37,7 @@ describe("last", () => {
     });
 
     test("should infer type in pipes", () => {
-      const data = pipe("this is a text", (data) => data.split(""), last());
+      const data = pipe("this is a text", (text) => text.split(""), last());
       assertType<string | undefined>(data);
     });
   });
diff --git a/src/mergeDeep.test.ts b/src/mergeDeep.test.ts
index ecd6067..e247274 100644
--- a/src/mergeDeep.test.ts
+++ b/src/mergeDeep.test.ts
@@ -1,6 +1,6 @@
 import { mergeDeep } from "./mergeDeep";
 
-const NOOP = () => {
+const NOOP = (): void => {
   /* do nothing */
 };
 
diff --git a/src/partition.test.ts b/src/partition.test.ts
index 4725fa0..cb7ec90 100644
--- a/src/partition.test.ts
+++ b/src/partition.test.ts
@@ -1,3 +1,4 @@
+import { isNumber } from "./isNumber";
 import { partition } from "./partition";
 import { pipe } from "./pipe";
 
@@ -21,9 +22,6 @@ describe("data first", () => {
     expect(partition(array, (x) => x.a === 1)).toEqual(expected);
   });
   test("partition with type guard", () => {
-    const isNumber = function (value: unknown): value is number {
-      return typeof value === "number";
-    };
     const actual = partition([1, "a", 2, "b"], isNumber);
     expect(actual).toEqual([
       [1, 2],
diff --git a/src/pick.test.ts b/src/pick.test.ts
index 8120f0d..22f81d1 100644
--- a/src/pick.test.ts
+++ b/src/pick.test.ts
@@ -9,7 +9,7 @@ describe("data first", () => {
   });
   test("support inherited properties", () => {
     class BaseClass {
-      testProp() {
+      testProp(): string {
         return "abc";
       }
     }
diff --git a/src/purry.test.ts b/src/purry.test.ts
index b45738a..980bb65 100644
--- a/src/purry.test.ts
+++ b/src/purry.test.ts
@@ -1,27 +1,22 @@
 import { purry } from "./purry";
 
-function sub(a: number, b: number) {
+function sub(a: number, b: number): number {
   return a - b;
 }
 
+function fn(...args: ReadonlyArray<unknown>): unknown {
+  return purry(sub, args);
+}
+
 test("all arguments", () => {
-  function fn(...args: ReadonlyArray<unknown>) {
-    return purry(sub, args);
-  }
   expect(fn(10, 5)).toEqual(5);
 });
 
 test("1 missing", () => {
-  function fn(...args: ReadonlyArray<unknown>) {
-    return purry(sub, args);
-  }
   const purried = fn(5) as (...args: ReadonlyArray<unknown>) => unknown;
   expect(purried(10)).toEqual(5);
 });
 
 test("wrong number of arguments", () => {
-  function fn(...args: ReadonlyArray<unknown>) {
-    return purry(sub, args);
-  }
   expect(() => fn(5, 10, 40)).toThrowError("Wrong number of arguments");
 });
diff --git a/src/rankBy.test.ts b/src/rankBy.test.ts
index d74fbc1..04a1403 100644
--- a/src/rankBy.test.ts
+++ b/src/rankBy.test.ts
@@ -8,8 +8,8 @@ describe("runtime (dataFirst)", () => {
 
   it("maintains the rank for items already in the array", () => {
     const data = [5, 1, 3] as const;
-    const sorted = [...data].sort();
-    for (let i = 0; i < sorted.length; i += 1) {
+    const sorted = [...data].sort((a, b) => a - b);
+    for (let i = 0; i < sorted.length; i++) {
       // TODO: Use `Array.prototype.entries` once we bump our TS target so we
       // can get both the index and the item at the same time, and don't need
       // the non-null assertion.
diff --git a/src/reduce.test.ts b/src/reduce.test.ts
index 0329533..39b3c2a 100644
--- a/src/reduce.test.ts
+++ b/src/reduce.test.ts
@@ -15,7 +15,7 @@ describe("data first", () => {
         (acc, x, index, items) => {
           expect(index).toBe(i);
           expect(items).toBe(array);
-          i++;
+          i += 1;
           return acc + x;
         },
         100,
diff --git a/src/round.test.ts b/src/round.test.ts
index d600ea0..123a159 100644
--- a/src/round.test.ts
+++ b/src/round.test.ts
@@ -3,24 +3,27 @@ import { round } from "./round";
 describe("data-first", () => {
   test("should work with positive precision", () => {
     expect(round(8123.4317, 3)).toEqual(8123.432);
-    expect(round(483.22243, 1)).toEqual(483.2);
+    expect(round(483.222_43, 1)).toEqual(483.2);
     expect(round(123.4317, 5)).toEqual(123.4317);
   });
 
   test("should work with negative precision", () => {
     expect(round(8123.4317, -2)).toEqual(8100);
-    expect(round(8123.4317, -4)).toEqual(10000);
+    expect(round(8123.4317, -4)).toEqual(10_000);
   });
 
   test("should work with precision = 0", () => {
     expect(round(8123.4317, 0)).toEqual(8123);
   });
 
-  test.each([NaN, Infinity])("should throw for %d precision", (val) => {
-    expect(() => round(1, val)).toThrowError(
-      `precision must be an integer: ${val}`,
-    );
-  });
+  test.each([Number.NaN, Number.POSITIVE_INFINITY])(
+    "should throw for %d precision",
+    (val) => {
+      expect(() => round(1, val)).toThrowError(
+        `precision must be an integer: ${val}`,
+      );
+    },
+  );
 
   test("should throw for non integer precision", () => {
     expect(() => round(1, 21.37)).toThrowError(
@@ -37,7 +40,7 @@ describe("data-first", () => {
     );
   });
 
-  test.each([NaN, Infinity, -Infinity])(
+  test.each([Number.NaN, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY])(
     "should return %d when passed as value regardless of precision",
     (val) => {
       for (const precision of [-1, 0, 1]) {
@@ -50,24 +53,27 @@ describe("data-first", () => {
 describe("data-last", () => {
   test("should work with positive precision", () => {
     expect(round(3)(8123.4317)).toEqual(8123.432);
-    expect(round(1)(483.22243)).toEqual(483.2);
+    expect(round(1)(483.222_43)).toEqual(483.2);
     expect(round(5)(123.4317)).toEqual(123.4317);
   });
 
   test("should work with negative precision", () => {
     expect(round(-2)(8123.4317)).toEqual(8100);
-    expect(round(-4)(8123.4317)).toEqual(10000);
+    expect(round(-4)(8123.4317)).toEqual(10_000);
   });
 
   test("should work with precision = 0", () => {
     expect(round(0)(8123.4317)).toEqual(8123);
   });
 
-  test.each([NaN, Infinity])("should throw for %d precision", (val) => {
-    expect(() => round(val)(1)).toThrowError(
-      `precision must be an integer: ${val}`,
-    );
-  });
+  test.each([Number.NaN, Number.POSITIVE_INFINITY])(
+    "should throw for %d precision",
+    (val) => {
+      expect(() => round(val)(1)).toThrowError(
+        `precision must be an integer: ${val}`,
+      );
+    },
+  );
 
   test("should throw for non integer precision", () => {
     expect(() => round(21.37)(1)).toThrowError(
@@ -84,7 +90,7 @@ describe("data-last", () => {
     );
   });
 
-  test.each([NaN, Infinity, -Infinity])(
+  test.each([Number.NaN, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY])(
     "should return %d when passed as value regardless of precision",
     (val) => {
       for (const precision of [-1, 0, 1]) {
diff --git a/src/sample.test.ts b/src/sample.test.ts
index 2934459..721adce 100644
--- a/src/sample.test.ts
+++ b/src/sample.test.ts
@@ -45,13 +45,15 @@ describe("at runtime", () => {
     it.each(allIndices(array))("doesn't reorder items", (sampleSize) => {
       const result = sample(array, sampleSize);
 
-      // Scan the result array and make sure each item is after the previous one
-      // in the input array.
-      result.reduce((lastInputIndex, item) => {
+      let lastInputIndex = -1; // outside of the array
+      for (const item of result) {
+        // Scan the result array and make sure each item is after the previous one
+        // in the input array.
+
         const currentInputIndex = array.indexOf(item);
         expect(currentInputIndex).toBeGreaterThan(lastInputIndex);
-        return currentInputIndex;
-      }, -1 /* outside of the array */);
+        lastInputIndex = currentInputIndex;
+      }
     });
   });
 
@@ -59,25 +61,29 @@ describe("at runtime", () => {
     it("for full (=== n) sample size", () => {
       const array = [1, 2, 3];
       const result = sample(array, 3);
-      expect(result).toBe(array);
+      expect(result).toStrictEqual(array);
+      expect(result).not.toBe(array);
     });
 
     it("for large (> n) sample sizes", () => {
       const array = [1, 2, 3];
       const result = sample(array, 10);
-      expect(result).toBe(array);
+      expect(result).toStrictEqual(array);
+      expect(result).not.toBe(array);
     });
 
     it("on empty arrays", () => {
       const array: Array<number> = [];
       const result = sample(array, 1);
-      expect(result).toBe(array);
+      expect(result).toStrictEqual(array);
+      expect(result).not.toBe(array);
     });
 
     it("on empty arrays and sample size 0", () => {
       const array: Array<number> = [];
       const result = sample(array, 0);
-      expect(result).toBe(array);
+      expect(result).toStrictEqual(array);
+      expect(result).not.toBe(array);
     });
   });
 
@@ -914,7 +920,7 @@ describe("typing", () => {
 const generateRandomArray = (): NonEmptyArray<number> =>
   // We use a set to remove duplicates, as it allows us to simplify our tests
   // @ts-expect-error [ts2322]: we know this array isn't empty!
-  Array.from(new Set(Array.from({ length: 100 }).map(Math.random)));
+  Array.from(new Set(Array.from({ length: 100 }).map(() => Math.random())));
 
-const allIndices = (array: Array<unknown>): Array<number> =>
+const allIndices = (array: ReadonlyArray<unknown>): Array<number> =>
   array.map((_, index) => index);
diff --git a/src/sortedIndexBy.test.ts b/src/sortedIndexBy.test.ts
index 44285c7..c3c6295 100644
--- a/src/sortedIndexBy.test.ts
+++ b/src/sortedIndexBy.test.ts
@@ -260,10 +260,10 @@ function indicesSeen<T>(
   item: T,
   valueFunction: (item: T) => NonNullable<unknown>,
 ): ReadonlyArray<number | undefined> {
-  const indicesSeen: Array<number | undefined> = [];
+  const indices: Array<number | undefined> = [];
   sortedIndexBy.indexed(items, item, (pivot, index) => {
-    indicesSeen.push(index);
+    indices.push(index);
     return valueFunction(pivot);
   });
-  return indicesSeen;
+  return indices;
 }
diff --git a/src/sortedLastIndexBy.test.ts b/src/sortedLastIndexBy.test.ts
index fc66199..a2a77c7 100644
--- a/src/sortedLastIndexBy.test.ts
+++ b/src/sortedLastIndexBy.test.ts
@@ -260,10 +260,10 @@ function indicesSeen<T>(
   item: T,
   valueFunction: (item: T) => NonNullable<unknown>,
 ): ReadonlyArray<number | undefined> {
-  const indicesSeen: Array<number | undefined> = [];
+  const indices: Array<number | undefined> = [];
   sortedLastIndexBy.indexed(items, item, (pivot, index) => {
-    indicesSeen.push(index);
+    indices.push(index);
     return valueFunction(pivot);
   });
-  return indicesSeen;
+  return indices;
 }
diff --git a/src/splice.test.ts b/src/splice.test.ts
index a930a91..eba1fa8 100644
--- a/src/splice.test.ts
+++ b/src/splice.test.ts
@@ -99,7 +99,7 @@ describe("at runtime", () => {
       { items: [   ], start:  1, deleteCount:  0, replacement: [3], expected: [3] },
       { items: [   ], start:  1, deleteCount:  1, replacement: [ ], expected: [] },
       { items: [   ], start:  1, deleteCount:  1, replacement: [3], expected: [3] },
-    ];
+    ] as const;
 
     test.each(testCases)(
       "splice($items, $start, $deleteCount, $replacement) -> $expected",
diff --git a/src/tap.test.ts b/src/tap.test.ts
index 0a3eb9a..c501b9c 100644
--- a/src/tap.test.ts
+++ b/src/tap.test.ts
@@ -5,6 +5,11 @@ import { tap } from "./tap";
 
 const DATA = [1] as const;
 
+// (same as console.log)
+function foo(x: unknown): unknown {
+  return x;
+}
+
 describe("data first", () => {
   it("should call function with input value", () => {
     const fn = vi.fn();
@@ -50,11 +55,6 @@ describe("data last", () => {
   });
 
   it("should infer types after tapping function reference with parameter type any", () => {
-    // (same as console.log)
-    function foo(x: unknown) {
-      return x;
-    }
-
     expect(
       pipe(
         [-1, 2],
diff --git a/src/times.test.ts b/src/times.test.ts
index 6e0a85d..d7dbbd2 100644
--- a/src/times.test.ts
+++ b/src/times.test.ts
@@ -1,22 +1,24 @@
 import { times } from "./times";
 
+const noop = (): undefined => undefined;
+const one = () => 1 as const;
+const mul1 = (idx: number): number => idx;
+const mul2 = (idx: number): number => idx * 2;
+
 describe("times", () => {
   describe("data_first", () => {
     it("throws error on invalid idx", () => {
-      const noop = () => undefined;
       expect(() => times(-1, noop)).toThrow();
       expect(() => times(-1000, noop)).toThrow();
       expect(() => times(Number.MIN_SAFE_INTEGER, noop)).toThrow();
     });
 
     it("returns empty array", () => {
-      const noop = () => undefined;
       const res = times(0, noop);
       expect(res).toEqual([]);
     });
 
     it("returns arr with fn result", () => {
-      const one = () => 1;
       expect(times(1, one)).toEqual([1]);
       expect(times(5, one)).toEqual([1, 1, 1, 1, 1]);
     });
@@ -32,17 +34,13 @@ describe("times", () => {
     });
 
     it("returns fn results as arr", () => {
-      const idx = (idx: number) => idx;
-      expect(times(5, idx)).toEqual([0, 1, 2, 3, 4]);
-
-      const mul2 = (idx: number) => idx * 2;
+      expect(times(5, mul1)).toEqual([0, 1, 2, 3, 4]);
       expect(times(5, mul2)).toEqual([0, 2, 4, 6, 8]);
     });
   });
 
   describe("data_last", () => {
     it("throws error on invalid idx", () => {
-      const noop = () => undefined;
       const noopTimes = times(noop);
       expect(() => noopTimes(-1)).toThrow();
       expect(() => noopTimes(-1000)).toThrow();
@@ -50,13 +48,11 @@ describe("times", () => {
     });
 
     it("returns empty array", () => {
-      const noop = () => undefined;
       const res = times(noop)(0);
       expect(res).toEqual([]);
     });
 
     it("returns arr with fn result", () => {
-      const one = () => 1;
       const oneTime = times(one);
       expect(oneTime(1)).toEqual([1]);
       expect(oneTime(5)).toEqual([1, 1, 1, 1, 1]);
@@ -73,10 +69,7 @@ describe("times", () => {
     });
 
     it("returns fn results as arr", () => {
-      const idx = (idx: number) => idx;
-      expect(times(idx)(5)).toEqual([0, 1, 2, 3, 4]);
-
-      const mul2 = (idx: number) => idx * 2;
+      expect(times(mul1)(5)).toEqual([0, 1, 2, 3, 4]);
       expect(times(mul2)(5)).toEqual([0, 2, 4, 6, 8]);
     });
   });
diff --git a/src/type.test.ts b/src/type.test.ts
index ec3f8cc..769dc09 100644
--- a/src/type.test.ts
+++ b/src/type.test.ts
@@ -9,7 +9,7 @@ it('"Object" if given an object literal', () => {
 });
 
 it('"RegExp" if given a RegExp literal', () => {
-  expect(type(/[A-z]/)).toEqual("RegExp");
+  expect(type(/[A-z]/u)).toEqual("RegExp");
 });
 
 it('"Number" if given a numeric value', () => {
@@ -17,7 +17,7 @@ it('"Number" if given a numeric value', () => {
 });
 
 it('"Number" if given the NaN value', () => {
-  expect(type(NaN)).toEqual("Number");
+  expect(type(Number.NaN)).toEqual("Number");
 });
 
 it('"String" if given a String literal', () => {
@@ -25,6 +25,7 @@ it('"String" if given a String literal', () => {
 });
 
 it('"String" if given a String object', () => {
+  // eslint-disable-next-line no-new-wrappers, unicorn/new-for-builtins -- Intentional
   expect(type(new String("I am a String object"))).toEqual("String");
 });
 
diff --git a/src/uniqBy.test.ts b/src/uniqBy.test.ts
index f843a9d..4ee4f90 100644
--- a/src/uniqBy.test.ts
+++ b/src/uniqBy.test.ts
@@ -41,7 +41,7 @@ describe("uniqBy", () => {
   });
 
   it("returns people with uniq first letter of name", () => {
-    expect(uniqBy(people, (p) => p.name.substring(0, 1))).toEqual([
+    expect(uniqBy(people, (p) => p.name.slice(0, 1))).toEqual([
       { name: "John", age: 42 },
       { name: "Sarah", age: 33 },
       { name: "Kim", age: 22 },
diff --git a/src/values.test.ts b/src/values.test.ts
index 96700c0..fa9e1ae 100644
--- a/src/values.test.ts
+++ b/src/values.test.ts
@@ -20,9 +20,11 @@ describe("typing", () => {
   });
 
   it("should correctly type functions", () => {
-    expectTypeOf(values(function namedFunction() {})).toEqualTypeOf<
-      Array<never>
-    >();
+    expectTypeOf(
+      values(function namedFunction() {
+        /* (intentionally empty) */
+      }),
+    ).toEqualTypeOf<Array<never>>();
   });
 
   it("should correctly type arrays", () => {
diff --git a/src/zip.test.ts b/src/zip.test.ts
index 61ab08e..364051d 100644
--- a/src/zip.test.ts
+++ b/src/zip.test.ts
@@ -145,7 +145,7 @@ describe("strict dataFirst typings", () => {
     const result = zip.strict(firstVariadic, secondVariadic);
     expectTypeOf(result).toEqualTypeOf<
       [[number, string], ...Array<[string, number]>]
-    >;
+    >();
   });
 });
 
@@ -210,6 +210,6 @@ describe("strict dataLast typings", () => {
     const result = pipe(firstVariadic, zip.strict(secondVariadic));
     expectTypeOf(result).toEqualTypeOf<
       [[number, string], ...Array<[string, number]>]
-    >;
+    >();
   });
 });
diff --git a/src/zipWith.test.ts b/src/zipWith.test.ts
index a2bc925..b9d439b 100644
--- a/src/zipWith.test.ts
+++ b/src/zipWith.test.ts
@@ -1,6 +1,6 @@
 import { zipWith } from "./zipWith";
 
-const pred = (a: string, b: string) => a + b;
+const pred = (a: string, b: string): string => a + b;
 
 const first = ["1", "2", "3"];
 const second = ["a", "b", "c"];
diff --git a/test/types_data_provider.ts b/test/types_data_provider.ts
index 9593160..524c112 100644
--- a/test/types_data_provider.ts
+++ b/test/types_data_provider.ts
@@ -1,5 +1,6 @@
 export class TestClass {
-  get foo() {
+  // eslint-disable-next-line @typescript-eslint/class-literal-property-style -- Maybe we can just accept the auto-fixer here...?
+  get foo(): string {
     return "a";
   }
 }