Skip to content

Instantly share code, notes, and snippets.

@TrevorBurnham
Created January 27, 2026 14:10
Show Gist options
  • Select an option

  • Save TrevorBurnham/4645e6cdddac26bad32d98971d359ddb to your computer and use it in GitHub Desktop.

Select an option

Save TrevorBurnham/4645e6cdddac26bad32d98971d359ddb to your computer and use it in GitHub Desktop.

getBaseProps Optimization Benchmark Results

Environment

  • Node.js: v22.17.1
  • Platform: darwin arm64 (Apple Silicon)
  • Date: 2026-01-27

Methodology

The benchmark compares two implementations:

  1. Original: Object.keys().forEach() + prop.match(/^data-/)
  2. Optimized: for-in loop + prop.startsWith('data-')

Each test case runs 1,000,000 iterations after a 1,000-iteration warmup phase.

Test Cases

Test Case Description Props Count
minimal Just id and className 2
withDataAttributes id, className, and 3 data-* attributes 5
realistic Typical component props (button-like) 10
manyProps Component with many props and data attributes 16

Results

Test Case Original (ms) Optimized (ms) Speedup
minimal 56.73 35.71 1.59x
withDataAttributes 195.39 96.10 2.03x
realistic 277.03 91.63 3.02x
manyProps 471.83 145.13 3.25x
Average - - 2.47x

Analysis

The optimization provides a 2.5x average speedup across all test cases. The improvement scales with the number of props:

  • Minimal props (2): ~1.6x faster
  • Many props (16): ~3.3x faster

Why the improvement?

  1. for-in vs Object.keys().forEach(): The for-in loop avoids creating an intermediate array and the overhead of callback function invocations.

  2. startsWith() vs match(): String.prototype.startsWith() is a simple string comparison, while match() involves regex compilation and matching overhead.

Correctness

All test cases pass correctness verification—both implementations produce identical results.

Impact

Since getBaseProps is called on every component render, this optimization benefits all Cloudscape components. For applications rendering many components, the cumulative time savings can be significant.

Reproducing

npx tsx benchmarks/getBaseProps-benchmark.ts
/**
* Benchmark comparing the original getBaseProps implementation vs the optimized version.
*
* Original: Object.keys().forEach() + regex match
* Optimized: for-in loop + String.prototype.startsWith()
*/
interface BaseComponentProps {
className?: string;
id?: string;
[key: string]: any;
}
// Original implementation (before optimization)
function getBasePropsOriginal(props: BaseComponentProps) {
const baseProps: Record<string, any> = {};
Object.keys(props).forEach(prop => {
if (prop === 'id' || prop === 'className' || prop.match(/^data-/)) {
baseProps[prop] = (props as Record<string, any>)[prop];
}
});
return baseProps as BaseComponentProps;
}
// Optimized implementation (after optimization)
function getBasePropsOptimized(props: BaseComponentProps) {
const baseProps: Record<string, any> = {};
for (const prop in props) {
if (prop === 'id' || prop === 'className' || prop.startsWith('data-')) {
baseProps[prop] = (props as Record<string, any>)[prop];
}
}
return baseProps as BaseComponentProps;
}
// Test data representing typical component props
const testCases = {
minimal: {
id: 'test-id',
className: 'test-class',
},
withDataAttributes: {
id: 'test-id',
className: 'test-class',
'data-testid': 'my-component',
'data-analytics': 'click-event',
'data-custom': 'value',
},
realistic: {
id: 'button-1',
className: 'custom-button',
'data-testid': 'submit-button',
'data-analytics-id': 'submit',
disabled: false,
onClick: () => {},
children: 'Submit',
variant: 'primary',
loading: false,
ariaLabel: 'Submit form',
},
manyProps: {
id: 'complex-component',
className: 'complex-class',
'data-testid': 'complex',
'data-a': '1',
'data-b': '2',
'data-c': '3',
prop1: 'value1',
prop2: 'value2',
prop3: 'value3',
prop4: 'value4',
prop5: 'value5',
prop6: 'value6',
prop7: 'value7',
prop8: 'value8',
prop9: 'value9',
prop10: 'value10',
},
};
function runBenchmark(name: string, fn: () => void, iterations: number): number {
// Warmup
for (let i = 0; i < 1000; i++) {
fn();
}
const start = performance.now();
for (let i = 0; i < iterations; i++) {
fn();
}
const end = performance.now();
return end - start;
}
function formatNumber(n: number): string {
return n.toLocaleString('en-US', { maximumFractionDigits: 2 });
}
console.log('='.repeat(80));
console.log('getBaseProps Benchmark Results');
console.log('='.repeat(80));
console.log(`Date: ${new Date().toISOString()}`);
console.log(`Node.js: ${process.version}`);
console.log(`Platform: ${process.platform} ${process.arch}`);
console.log('='.repeat(80));
console.log();
const iterations = 1_000_000;
const results: Array<{
testCase: string;
originalMs: number;
optimizedMs: number;
speedup: number;
}> = [];
for (const [testCaseName, props] of Object.entries(testCases)) {
console.log(`Test case: ${testCaseName}`);
console.log(`Props: ${JSON.stringify(Object.keys(props))}`);
console.log(`Iterations: ${formatNumber(iterations)}`);
console.log();
const originalTime = runBenchmark('original', () => getBasePropsOriginal(props), iterations);
const optimizedTime = runBenchmark('optimized', () => getBasePropsOptimized(props), iterations);
const speedup = originalTime / optimizedTime;
results.push({
testCase: testCaseName,
originalMs: originalTime,
optimizedMs: optimizedTime,
speedup,
});
console.log(` Original (Object.keys + forEach + regex): ${formatNumber(originalTime)} ms`);
console.log(` Optimized (for-in + startsWith): ${formatNumber(optimizedTime)} ms`);
console.log(` Speedup: ${formatNumber(speedup)}x`);
console.log();
}
// Verify correctness
console.log('='.repeat(80));
console.log('Correctness Verification');
console.log('='.repeat(80));
let allCorrect = true;
for (const [testCaseName, props] of Object.entries(testCases)) {
const originalResult = getBasePropsOriginal(props);
const optimizedResult = getBasePropsOptimized(props);
const originalKeys = Object.keys(originalResult).sort();
const optimizedKeys = Object.keys(optimizedResult).sort();
const keysMatch = JSON.stringify(originalKeys) === JSON.stringify(optimizedKeys);
const valuesMatch = originalKeys.every(key => originalResult[key] === optimizedResult[key]);
const correct = keysMatch && valuesMatch;
allCorrect = allCorrect && correct;
console.log(`${testCaseName}: ${correct ? '✓ PASS' : '✗ FAIL'}`);
if (!correct) {
console.log(` Original keys: ${JSON.stringify(originalKeys)}`);
console.log(` Optimized keys: ${JSON.stringify(optimizedKeys)}`);
}
}
console.log();
console.log('='.repeat(80));
console.log('Summary');
console.log('='.repeat(80));
const avgSpeedup = results.reduce((sum, r) => sum + r.speedup, 0) / results.length;
console.log(`Average speedup: ${formatNumber(avgSpeedup)}x`);
console.log(`All correctness tests: ${allCorrect ? '✓ PASSED' : '✗ FAILED'}`);
console.log();
// Output as markdown table for easy copy-paste
console.log('='.repeat(80));
console.log('Markdown Table (for PR comment)');
console.log('='.repeat(80));
console.log();
console.log('| Test Case | Original (ms) | Optimized (ms) | Speedup |');
console.log('|-----------|---------------|----------------|---------|');
for (const r of results) {
console.log(`| ${r.testCase} | ${formatNumber(r.originalMs)} | ${formatNumber(r.optimizedMs)} | ${formatNumber(r.speedup)}x |`);
}
console.log(`| **Average** | - | - | **${formatNumber(avgSpeedup)}x** |`);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment