A Microbenchmark Across Languages: What Two Sum Taught Me About Performance Tradeoffs


As engineers, we often focus on algorithms, assuming language choice is secondary unless we're dealing with systems level code. But even simple problems can expose meaningful performance gaps depending on the language they’re implemented in. To test this idea in a clean, repeatable way, I turned to a familiar and lightweight problem: LeetCode’s Two Sum II. This wasn’t about solving the problem, it was about what happens under the hood when identical logic meets different runtimes.
Why This Problem?
- It uses basic iteration and hash maps, avoiding language specific tricks
- It’s quick to implement across multiple languages
- It allows for easy benchmarking of runtime and memory differences
Methodology
I implemented the same hash map based logic in four languages: JavaScript, TypeScript, Java, and C++. I used LeetCode’s built in benchmarking tools to collect runtime and memory usage data. While not a rigorous performance suite, the platform offers consistent trends across test runs, which serve as directional indicators.
Each implementation used the same hash map-based approach. Below is the JavaScript version for illustration:
let cache = {};
for (let i = 0; i < numbers.length; i++) {
let complement = target - numbers[i];
if (complement in cache) {
return [cache[complement] + 1, i + 1];
}
cache[numbers[i]] = i;
}
The logic is structurally identical in the other three languages. Full versions are available in Appendix A.
Results
Performance Analysis
C++ is the fastest because it turns your code directly into machine code that the computer runs. There’s no extra layer like a virtual machine or browser engine. It also uses something called the Standard Template Library (STL) where basically, super efficient built-in tools like hash maps and arrays that are highly optimized. On top of that, C++ doesn’t use garbage collection, so it doesn’t pause to clean up memory the way other languages might.
Java is slower than C++, but still performs well. It runs on something called the Java Virtual Machine (JVM), which adds a small delay since it sits between your code and the hardware. But Java uses a feature called Just-In-Time (JIT) compilation, which means the JVM watches your program as it runs and optimizes the parts you use most. This helps Java speed up while keeping things safe and portable.
JavaScript and TypeScript are the slowest in this test. They run in engines like V8, which try to be fast, but they also have to do a lot of extra work. JavaScript is dynamically typed, so it figures out what type each variable is while the program runs. That flexibility makes it easier to write code, but slower to execute. TypeScript helps during development with types, but once it runs, it’s just JavaScript underneath. Both also use garbage collection, which can randomly slow things down as memory is cleaned up in the background.
Why This Matters
- C++ is ideal for performance-critical paths like embedded systems and trading engines.
- Java is suited for backend services that require reliability and scalability.
- JavaScript/TypeScript trade raw speed for rapid iteration and ecosystem breadth.
Engineering judgment is knowing when performance trumps developer velocity and vice versa.
What’s Next?
This microbenchmark prompted ideas for deeper comparisons:
- Graph algorithms (BFS/DFS)
- Recursion heavy workloads
- String heavy processing
Benchmarking isn’t just for compilers or low level engineers. It’s more like a lens to better understand the systems we build on and the tradeoffs that define modern software.
References
Appendix A: Full Code Listings
var twoSum = function (nums, target) {
let cache = {};
for (let i = 0; i < nums.length; i++) {
let pair = target - nums[i];
if (pair in cache) {
return [cache[pair], i]
} else {
cache[nums[i]] = i;
}
}
return [];
};
function twoSum(nums: number[], target: number): number[] {
const numMap = {};
for (let i = 0; i < nums.length; i++) {
let secondary = target - nums[i];
if (secondary in numMap) {
return [numMap[secondary], i];
}
numMap[nums[i]] = i;
}
return [];
};
import java.util.HashMap;
import java.util.Map;
public class TwoSum {
public static int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> cache = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int pair = target - nums[i];
if (cache.containsKey(pair)) {
return new int[] { cache.get(pair), i };
} else {
cache.put(nums[i], i);
}
}
return new int[] {};
}
}
#include <unordered_map>
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> cache;
for (int i = 0; i < nums.size(); i++) {
int complement = target - nums[i];
if (cache.find(complement) != cache.end()) {
return {cache[complement], i};
} else {
cache[nums[i]] = i;
}
}
return {};
}
};
Subscribe to my newsletter
Read articles from Chris Bolosan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Chris Bolosan
Chris Bolosan
I’m a Software Engineer in the Chicago area interested in building products that drive positive change. I am comfortable working both independently and as part of a team. When I’m not coding, you can find me helping others in the martial arts community, working on DIY home projects, tinkering with cars, or cooking up a new recipe. My expertise is in Southern-style BBQ. If you’d like to talk code, cars, food, or just plain building stuff, shoot me a message.