# UNDF: UNDF-2026-000000552
# threejs-0006: EventDispatcher.addEventListener() O(N²) via indexOf dedup on every add

**File:** `src/core/EventDispatcher.js`
**Lines:** 43 (`addEventListener` indexOf dedup), 64 (`hasEventListener` indexOf)
**Severity:** MEDIUM
**CWE:** CWE-407 (Inefficient Algorithmic Complexity)

## Description

`EventDispatcher.addEventListener(type, listener)` checks for duplicate
listeners using a linear array scan:

```js
addEventListener( type, listener ) {
  ...
  if ( listeners[ type ] === undefined ) {
    listeners[ type ] = [];
  }

  if ( listeners[ type ].indexOf( listener ) === - 1 ) {   // O(N) scan — CWE-407
    listeners[ type ].push( listener );
  }
}
```

`listeners[type]` is a plain `Array`.  `Array.prototype.indexOf()` performs a
linear scan over all existing listeners for that event type.

When `addEventListener` is called N times for the same event type (e.g. adding
N unique disposal/resize handlers in a scene setup loop, or attaching per-object
listeners in a particle system), the total dedup cost is:

```
O(0) + O(1) + O(2) + ... + O(N-1) = O(N²/2)
```

`hasEventListener(type, listener)` has the same O(N) scan.  If called in a
render loop that validates listeners exist before dispatching, it compounds the
problem.

Three.js objects — `Material`, `BufferGeometry`, `Texture`, `Object3D` — all
extend `EventDispatcher`.  A scene with M materials each gaining N listeners
during load would incur O(M × N²) total work.

## Fix

Replace the per-type `Array` with a `Map<type, Set<listener>>`:

```js
addEventListener( type, listener ) {
  if ( this._listenerSets === undefined ) this._listenerSets = new Map();

  let listenerSet = this._listenerSets.get( type );
  if ( listenerSet === undefined ) {
    listenerSet = new Set();
    this._listenerSets.set( type, listenerSet );
  }

  if ( ! listenerSet.has( listener ) ) {        // O(1) — fixed
    listenerSet.add( listener );
  }
}

hasEventListener( type, listener ) {
  if ( this._listenerSets === undefined ) return false;
  const listenerSet = this._listenerSets.get( type );
  return listenerSet !== undefined && listenerSet.has( listener );  // O(1)
}
```

`dispatchEvent` iterates the set (same O(N) iteration, no change in
semantics since insertion order is preserved by `Set`).

## Complexity

| Path | Before | After |
|------|--------|-------|
| `addEventListener()` per call | O(N) | O(1) |
| Total for N unique listeners, same type | O(N²) | O(N) |
| `hasEventListener()` per call | O(N) | O(1) |

**Speedup:** ~N/2 × at N=500: ~250×

## References

- `Material`, `BufferGeometry`, `Texture`, `Object3D` all extend `EventDispatcher`
- `WebGLRenderer` calls `material.addEventListener('dispose', ...)` — guarded but pattern propagates
- threejs-0001..0005: previous CWE-407 defects in Three.js
