JavaScript Energy Analysis
Joule lifts JavaScript into its energy analysis pipeline, providing per-function energy estimates for Node.js and browser-style code. Arrow functions, template literals, classes, destructuring, and 20+ array methods are fully supported.
Quick Start
# Static energy analysis
joulec --lift js app.js
# Execute with energy tracking
joulec --lift-run js app.js
# Execute with energy optimization
joulec --energy-optimize --lift-run js app.js
Supported Features
| Category | Features |
|---|---|
| Functions | function, arrow functions =>, default params, rest params ...args |
| Classes | class, constructor, extends, methods, static, this, super |
| Control flow | if/else, while, do-while, for, for-in, for-of, switch/case, break, continue |
| Destructuring | Array [a, b] = arr, object {x, y} = obj, nested, with defaults |
| Operators | Spread ..., nullish coalescing ??, optional chaining ?., typeof, bitwise |
| Template literals | `Hello ${name}` with expression interpolation |
| Array methods | .push(), .pop(), .map(), .filter(), .reduce(), .find(), .findIndex(), .some(), .every(), .forEach(), .indexOf(), .includes(), .slice(), .splice(), .concat(), .reverse(), .sort(), .join(), .flat(), .length |
| String methods | .length, .charAt(), .indexOf(), .includes(), .slice(), .substring(), .toUpperCase(), .toLowerCase(), .trim(), .split(), .replace(), .startsWith(), .endsWith(), .repeat() |
| Object methods | Object.keys(), Object.values(), Object.entries() |
| Math | Math.floor(), Math.ceil(), Math.round(), Math.abs(), Math.max(), Math.min(), Math.pow(), Math.sqrt(), Math.random(), Math.PI |
| Output | console.log() with auto-coercion |
| Types | Numbers (f64), strings, booleans, arrays, objects, null, undefined |
Common Energy Anti-Patterns
1. Chained Array Methods Creating Intermediates
// BAD — 3 intermediate arrays allocated
const result = data
.filter(x => x > 0) // allocates filtered array
.map(x => x * 2) // allocates mapped array
.reduce((a, b) => a + b, 0); // iterates again
// GOOD — single pass, one allocation
let result = 0;
for (const x of data) {
if (x > 0) result += x * 2;
}
Category: ALLOCATION | Severity: High | Savings: ~3x (eliminates 2 intermediate allocations)
2. indexOf on Large Arrays
// BAD — O(n) per check
for (const query of queries) {
if (data.indexOf(query) !== -1) {
process(query);
}
}
// GOOD — O(1) per check with Set
const lookup = new Set(data);
for (const query of queries) {
if (lookup.has(query)) {
process(query);
}
}
Category: DATA STRUCTURE | Severity: High | Savings: ~50x for 10K elements
3. Template Literals in Tight Loops
// BAD — string allocation every iteration
for (let i = 0; i < 10000; i++) {
const msg = `Processing item ${i} of ${total}`;
log(msg);
}
// GOOD — build once if constant parts dominate
const prefix = "Processing item ";
const suffix = " of " + total;
for (let i = 0; i < 10000; i++) {
log(prefix + i + suffix);
}
Category: STRING | Severity: Medium | Savings: ~2x
4. Nested for-of Loops
// BAD — O(n*m) with no early exit
function findPair(arr1, arr2, target) {
for (const a of arr1) {
for (const b of arr2) {
if (a + b === target) return [a, b];
}
}
return null;
}
// GOOD — O(n+m) with hash set
function findPair(arr1, arr2, target) {
const seen = new Set(arr1);
for (const b of arr2) {
if (seen.has(target - b)) return [target - b, b];
}
return null;
}
Category: ALGORITHM | Severity: Critical | Savings: ~100x for large inputs
5. forEach with Closure Allocation
// BAD — allocates closure object per iteration
data.forEach(function(item) {
if (item.active) results.push(item.name);
});
// GOOD — for-of avoids closure overhead
for (const item of data) {
if (item.active) results.push(item.name);
}
Category: ALLOCATION | Severity: Low | Savings: ~1.3x
Worked Example
class EventQueue {
constructor() {
this.events = [];
this.handlers = [];
}
on(type, handler) {
this.handlers.push({ type: type, fn: handler });
}
emit(type, data) {
this.events.push({ type: type, data: data, time: Date.now() });
const matching = this.handlers.filter(h => h.type === type);
matching.forEach(h => h.fn(data));
}
getEventsByType(type) {
return this.events.filter(e => e.type === type);
}
}
function main() {
const queue = new EventQueue();
let total = 0;
queue.on("data", function(val) { total += val; });
queue.on("data", function(val) { console.log(`Received: ${val}`); });
for (let i = 0; i < 100; i++) {
queue.emit("data", i);
}
console.log(`Total: ${total}`);
const dataEvents = queue.getEventsByType("data");
console.log(`Events logged: ${dataEvents.length}`);
}
main();
$ joulec --lift js events.js
Energy Analysis: events.js
EventQueue__constructor 1.20 nJ (confidence: 0.95)
EventQueue__on 2.10 nJ (confidence: 0.90)
EventQueue__emit 18.50 nJ (confidence: 0.60)
EventQueue__getEventsByType 5.30 nJ (confidence: 0.65)
main 8.40 nJ (confidence: 0.55)
Total: 35.50 nJ
Recommendations:
!! [ALLOCATION] EventQueue__emit — filter() + forEach() chain allocates intermediate array
Suggestion: use a single for-of loop to filter and dispatch in one pass
Estimated savings: 2-3x
Limitations
- No DOM APIs (
document,window,fetch, etc.) - No
require()orimportof npm modules async/awaitand Promises are approximated as synchronous- No
WeakMap,WeakSet,Proxy,Reflect - No regular expressions (regex literals are parsed but not executed)
Date.now()returns a simulated timestamp- No
eval()or dynamic code execution