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

CategoryFeatures
Functionsfunction, arrow functions =>, default params, rest params ...args
Classesclass, constructor, extends, methods, static, this, super
Control flowif/else, while, do-while, for, for-in, for-of, switch/case, break, continue
DestructuringArray [a, b] = arr, object {x, y} = obj, nested, with defaults
OperatorsSpread ..., 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 methodsObject.keys(), Object.values(), Object.entries()
MathMath.floor(), Math.ceil(), Math.round(), Math.abs(), Math.max(), Math.min(), Math.pow(), Math.sqrt(), Math.random(), Math.PI
Outputconsole.log() with auto-coercion
TypesNumbers (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() or import of npm modules
  • async/await and 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