]> Piment Noir Git Repositories - benchmarks-js.git/commitdiff
feat(bench): add common JS pattern benchmarks
authorJérôme Benoit <jerome.benoit@piment-noir.org>
Tue, 10 Feb 2026 22:13:06 +0000 (23:13 +0100)
committerJérôme Benoit <jerome.benoit@piment-noir.org>
Tue, 10 Feb 2026 22:13:06 +0000 (23:13 +0100)
Add 9 new benchmark files for frequently used JS patterns:
- array-iteration: for loop vs for...of vs forEach vs reduce
- string-concatenation: + vs template literal vs concat vs join
- type-checking: typeof vs instanceof vs Array.isArray vs toString
- array-filter: filter() vs for loop vs reduce
- array-find: find() vs findIndex() vs some() vs for loop
- set-vs-array-lookup: Set.has() vs includes() vs indexOf()
- object-iteration: for...in vs Object.keys/values/entries
- number-parsing: parseInt vs Number vs unary + vs bitwise ops
- property-check: in vs hasOwnProperty vs Object.hasOwn vs Reflect.has

array-filter.mjs [new file with mode: 0644]
array-find.mjs [new file with mode: 0644]
array-iteration.mjs [new file with mode: 0644]
number-parsing.mjs [new file with mode: 0644]
object-iteration.mjs [new file with mode: 0644]
property-check.mjs [new file with mode: 0644]
set-vs-array-lookup.mjs [new file with mode: 0644]
string-concatenation.mjs [new file with mode: 0644]
type-checking.mjs [new file with mode: 0644]

diff --git a/array-filter.mjs b/array-filter.mjs
new file mode 100644 (file)
index 0000000..1d2ac31
--- /dev/null
@@ -0,0 +1,47 @@
+import { Bench } from 'tinybench'
+
+import { generateRandomNumberArray } from './benchmark-utils.mjs'
+
+const size = 10000
+const testArray = generateRandomNumberArray(size, 100)
+const threshold = 50
+
+const bench = new Bench({
+  name: `Array filter with ${size} elements`,
+  time: 100,
+})
+
+bench
+  .add('filter()', () => {
+    return testArray.filter(value => value > threshold)
+  })
+  .add('for loop + push', () => {
+    const result = []
+    for (let i = 0; i < testArray.length; i++) {
+      if (testArray[i] > threshold) {
+        result.push(testArray[i])
+      }
+    }
+    return result
+  })
+  .add('for...of + push', () => {
+    const result = []
+    for (const value of testArray) {
+      if (value > threshold) {
+        result.push(value)
+      }
+    }
+    return result
+  })
+  .add('reduce', () => {
+    return testArray.reduce((result, value) => {
+      if (value > threshold) {
+        result.push(value)
+      }
+      return result
+    }, [])
+  })
+
+await bench.run()
+
+console.table(bench.table())
diff --git a/array-find.mjs b/array-find.mjs
new file mode 100644 (file)
index 0000000..027442f
--- /dev/null
@@ -0,0 +1,53 @@
+import { Bench } from 'tinybench'
+
+import { generateRandomNumberArray } from './benchmark-utils.mjs'
+
+const size = 10000
+const testArray = generateRandomNumberArray(size, 100)
+// Pick a target that exists somewhere in the middle
+const target = testArray[Math.floor(size / 2)]
+
+const bench = new Bench({
+  name: `Array find in ${size} elements`,
+  time: 100,
+})
+
+bench
+  .add('find()', () => {
+    return testArray.find(value => value === target)
+  })
+  .add('findIndex()', () => {
+    const index = testArray.findIndex(value => value === target)
+    return index !== -1 ? testArray[index] : undefined
+  })
+  .add('some()', () => {
+    let result
+    testArray.some(value => {
+      if (value === target) {
+        result = value
+        return true
+      }
+      return false
+    })
+    return result
+  })
+  .add('for loop', () => {
+    for (let i = 0; i < testArray.length; i++) {
+      if (testArray[i] === target) {
+        return testArray[i]
+      }
+    }
+    return undefined
+  })
+  .add('for...of', () => {
+    for (const value of testArray) {
+      if (value === target) {
+        return value
+      }
+    }
+    return undefined
+  })
+
+await bench.run()
+
+console.table(bench.table())
diff --git a/array-iteration.mjs b/array-iteration.mjs
new file mode 100644 (file)
index 0000000..d891346
--- /dev/null
@@ -0,0 +1,58 @@
+import { Bench } from 'tinybench'
+
+import { generateRandomNumberArray } from './benchmark-utils.mjs'
+
+const size = 10000
+const testArray = generateRandomNumberArray(size)
+
+const bench = new Bench({
+  name: `Array iteration with ${size} elements`,
+  time: 100,
+})
+
+bench
+  .add('for loop', () => {
+    let sum = 0
+    for (let i = 0; i < testArray.length; i++) {
+      sum += testArray[i]
+    }
+    return sum
+  })
+  .add('for loop (cached length)', () => {
+    let sum = 0
+    const len = testArray.length
+    for (let i = 0; i < len; i++) {
+      sum += testArray[i]
+    }
+    return sum
+  })
+  .add('for...of', () => {
+    let sum = 0
+    for (const value of testArray) {
+      sum += value
+    }
+    return sum
+  })
+  .add('forEach', () => {
+    let sum = 0
+    testArray.forEach(value => {
+      sum += value
+    })
+    return sum
+  })
+  .add('reduce', () => {
+    return testArray.reduce((sum, value) => sum + value, 0)
+  })
+  .add('while loop', () => {
+    let sum = 0
+    let i = 0
+    while (i < testArray.length) {
+      sum += testArray[i]
+      i++
+    }
+    return sum
+  })
+
+await bench.run()
+
+console.table(bench.table())
diff --git a/number-parsing.mjs b/number-parsing.mjs
new file mode 100644 (file)
index 0000000..0bdd83f
--- /dev/null
@@ -0,0 +1,53 @@
+import { Bench } from 'tinybench'
+
+const intString = '12345'
+const floatString = '123.456'
+
+const benchInt = new Bench({
+  name: 'Integer parsing',
+  time: 100,
+})
+
+benchInt
+  .add('parseInt()', () => {
+    return parseInt(intString, 10)
+  })
+  .add('Number()', () => {
+    return Number(intString)
+  })
+  .add('+ (unary plus)', () => {
+    return +intString
+  })
+  .add('~~ (double bitwise NOT)', () => {
+    return ~~intString
+  })
+  .add('| 0 (bitwise OR)', () => {
+    return intString | 0
+  })
+  .add('>> 0 (bitwise shift)', () => {
+    return intString >> 0
+  })
+
+await benchInt.run()
+
+console.table(benchInt.table())
+
+const benchFloat = new Bench({
+  name: 'Float parsing',
+  time: 100,
+})
+
+benchFloat
+  .add('parseFloat()', () => {
+    return parseFloat(floatString)
+  })
+  .add('Number()', () => {
+    return Number(floatString)
+  })
+  .add('+ (unary plus)', () => {
+    return +floatString
+  })
+
+await benchFloat.run()
+
+console.table(benchFloat.table())
diff --git a/object-iteration.mjs b/object-iteration.mjs
new file mode 100644 (file)
index 0000000..bacbbc1
--- /dev/null
@@ -0,0 +1,62 @@
+import { Bench } from 'tinybench'
+
+import { generateRandomObject } from './benchmark-utils.mjs'
+
+const object = generateRandomObject(500)
+const keyCount = Object.keys(object).length
+
+const bench = new Bench({
+  name: `Object iteration with ${keyCount} keys`,
+  time: 100,
+})
+
+bench
+  .add('for...in', () => {
+    let sum = 0
+    for (const key in object) {
+      sum += object[key]
+    }
+    return sum
+  })
+  .add('for...in (hasOwnProperty)', () => {
+    let sum = 0
+    for (const key in object) {
+      if (Object.prototype.hasOwnProperty.call(object, key)) {
+        sum += object[key]
+      }
+    }
+    return sum
+  })
+  .add('Object.keys() + for', () => {
+    let sum = 0
+    const keys = Object.keys(object)
+    for (let i = 0; i < keys.length; i++) {
+      sum += object[keys[i]]
+    }
+    return sum
+  })
+  .add('Object.keys() + forEach', () => {
+    let sum = 0
+    Object.keys(object).forEach(key => {
+      sum += object[key]
+    })
+    return sum
+  })
+  .add('Object.values() + for...of', () => {
+    let sum = 0
+    for (const value of Object.values(object)) {
+      sum += value
+    }
+    return sum
+  })
+  .add('Object.entries() + for...of', () => {
+    let sum = 0
+    for (const [, value] of Object.entries(object)) {
+      sum += value
+    }
+    return sum
+  })
+
+await bench.run()
+
+console.table(bench.table())
diff --git a/property-check.mjs b/property-check.mjs
new file mode 100644 (file)
index 0000000..ecdf94a
--- /dev/null
@@ -0,0 +1,70 @@
+import { Bench } from 'tinybench'
+
+import { generateRandomObject } from './benchmark-utils.mjs'
+
+const object = generateRandomObject(500)
+const keyCount = Object.keys(object).length
+// Pick an existing key
+const existingKey = Object.keys(object)[Math.floor(keyCount / 2)]
+// Pick a missing key
+const missingKey = 'nonexistent_property'
+
+const benchExisting = new Bench({
+  name: `Property check (existing key in ${keyCount} keys)`,
+  time: 100,
+})
+
+benchExisting
+  .add('in operator', () => {
+    return existingKey in object
+  })
+  .add('hasOwnProperty()', () => {
+    // eslint-disable-next-line no-prototype-builtins
+    return object.hasOwnProperty(existingKey)
+  })
+  .add('Object.hasOwn()', () => {
+    return Object.hasOwn(object, existingKey)
+  })
+  .add('Object.prototype.hasOwnProperty.call()', () => {
+    return Object.prototype.hasOwnProperty.call(object, existingKey)
+  })
+  .add('!== undefined', () => {
+    return object[existingKey] !== undefined
+  })
+  .add('Reflect.has()', () => {
+    return Reflect.has(object, existingKey)
+  })
+
+await benchExisting.run()
+
+console.table(benchExisting.table())
+
+const benchMissing = new Bench({
+  name: `Property check (missing key in ${keyCount} keys)`,
+  time: 100,
+})
+
+benchMissing
+  .add('in operator', () => {
+    return missingKey in object
+  })
+  .add('hasOwnProperty()', () => {
+    // eslint-disable-next-line no-prototype-builtins
+    return object.hasOwnProperty(missingKey)
+  })
+  .add('Object.hasOwn()', () => {
+    return Object.hasOwn(object, missingKey)
+  })
+  .add('Object.prototype.hasOwnProperty.call()', () => {
+    return Object.prototype.hasOwnProperty.call(object, missingKey)
+  })
+  .add('!== undefined', () => {
+    return object[missingKey] !== undefined
+  })
+  .add('Reflect.has()', () => {
+    return Reflect.has(object, missingKey)
+  })
+
+await benchMissing.run()
+
+console.table(benchMissing.table())
diff --git a/set-vs-array-lookup.mjs b/set-vs-array-lookup.mjs
new file mode 100644 (file)
index 0000000..30111c8
--- /dev/null
@@ -0,0 +1,68 @@
+import { Bench } from 'tinybench'
+
+import { generateRandomNumberArray } from './benchmark-utils.mjs'
+
+const size = 10000
+const testArray = generateRandomNumberArray(size, 100000)
+const testSet = new Set(testArray)
+// Pick a target that exists
+const existingTarget = testArray[Math.floor(size / 2)]
+// Pick a target that doesn't exist
+const missingTarget = -1
+
+const bench = new Bench({
+  name: `Lookup in ${size} elements (existing value)`,
+  time: 100,
+})
+
+bench
+  .add('Set.has()', () => {
+    return testSet.has(existingTarget)
+  })
+  .add('Array.includes()', () => {
+    return testArray.includes(existingTarget)
+  })
+  .add('Array.indexOf() !== -1', () => {
+    return testArray.indexOf(existingTarget) !== -1
+  })
+  .add('for loop', () => {
+    for (let i = 0; i < testArray.length; i++) {
+      if (testArray[i] === existingTarget) {
+        return true
+      }
+    }
+    return false
+  })
+
+await bench.run()
+
+console.table(bench.table())
+
+// Also benchmark missing value lookup
+const benchMissing = new Bench({
+  name: `Lookup in ${size} elements (missing value)`,
+  time: 100,
+})
+
+benchMissing
+  .add('Set.has()', () => {
+    return testSet.has(missingTarget)
+  })
+  .add('Array.includes()', () => {
+    return testArray.includes(missingTarget)
+  })
+  .add('Array.indexOf() !== -1', () => {
+    return testArray.indexOf(missingTarget) !== -1
+  })
+  .add('for loop', () => {
+    for (let i = 0; i < testArray.length; i++) {
+      if (testArray[i] === missingTarget) {
+        return true
+      }
+    }
+    return false
+  })
+
+await benchMissing.run()
+
+console.table(benchMissing.table())
diff --git a/string-concatenation.mjs b/string-concatenation.mjs
new file mode 100644 (file)
index 0000000..bb25d98
--- /dev/null
@@ -0,0 +1,30 @@
+import { Bench } from 'tinybench'
+
+const str1 = 'Hello'
+const str2 = 'World'
+const str3 = 'JavaScript'
+const str4 = 'Benchmark'
+const str5 = 'Test'
+
+const bench = new Bench({
+  name: 'String concatenation',
+  time: 100,
+})
+
+bench
+  .add('+ operator', () => {
+    return str1 + ' ' + str2 + ' ' + str3 + ' ' + str4 + ' ' + str5
+  })
+  .add('template literal', () => {
+    return `${str1} ${str2} ${str3} ${str4} ${str5}`
+  })
+  .add('concat()', () => {
+    return str1.concat(' ', str2, ' ', str3, ' ', str4, ' ', str5)
+  })
+  .add('array join', () => {
+    return [str1, str2, str3, str4, str5].join(' ')
+  })
+
+await bench.run()
+
+console.table(bench.table())
diff --git a/type-checking.mjs b/type-checking.mjs
new file mode 100644 (file)
index 0000000..4f232e6
--- /dev/null
@@ -0,0 +1,40 @@
+import { Bench } from 'tinybench'
+
+const testValues = {
+  array: [1, 2, 3],
+  boolean: true,
+  null: null,
+  number: 42,
+  object: { a: 1 },
+  string: 'hello',
+  undefined,
+}
+
+const bench = new Bench({
+  name: 'Type checking',
+  time: 100,
+})
+
+// Test with array (common case for type checking)
+const testArray = testValues.array
+
+bench
+  .add('typeof', () => {
+    return typeof testArray
+  })
+  .add('instanceof', () => {
+    return testArray instanceof Array
+  })
+  .add('Array.isArray()', () => {
+    return Array.isArray(testArray)
+  })
+  .add('Object.prototype.toString.call()', () => {
+    return Object.prototype.toString.call(testArray) === '[object Array]'
+  })
+  .add('constructor.name', () => {
+    return testArray.constructor.name === 'Array'
+  })
+
+await bench.run()
+
+console.table(bench.table())