Preserving `this` in CoffeeScript Callbacks with the Fat Arrow
CoffeeScript's `=>` binds `this` lexically so class methods survive as callbacks. Learn when to use it and what it costs.
27 Sept 2025, 10:33 UTC

The problem: a method loses its object
Imagine a CoffeeScript class that tracks a count and schedules an increment:
class Counter
constructor: (@count = 0) -
increment: ->
@count += 1
console.log @count
start: ->
setTimeout @increment, 1000
When the timer fires, increment runs as a plain function. setTimeout does not supply the Counter instance, so this inside the method refers to the global object or is undefined in strict mode. Reading @count then fails or mutates the wrong object.
This is standard JavaScript behavior: a method reference detached from its instance loses its receiver.
The fix: bind this with =>
CoffeeScript's fat arrow, written =>, creates a function that captures the lexical this from where it is defined. Inside a class body, that means the method stays attached to the instance no matter who invokes it later.
class Counter
constructor: (@count = 0) -
increment: =>
@count += 1
console.log @count
start: ->
setTimeout @increment, 1000
Replacing -> with => tells the compiler to bind increment to the current this at construction time. When setTimeout calls the function, this still points to the Counter instance.
What the compiler emits
The emitted JavaScript depends on your CoffeeScript version and target.
In CoffeeScript 1.x, classes compile to ES5 constructor functions. A => method is usually captured in the constructor with a helper variable, so each instance gets its own bound closure. The emitted code is conceptually similar to this illustration, not literal compiler output:
function Counter(count) {
this.count = count != null ? count : 0;
this.increment = (function(_this) {
return function() {
return _this.increment__bound.apply(_this, arguments);
};
})(this);
}
CoffeeScript 2 can target ES6. With an ES6 target, => may compile to a native arrow function, which also captures this lexically. With an ES5 target, it falls back to closure-based binding similar to 1.x. Do not assume native arrows unless you set the target and inspect the output.
Verify the emitted code
Run the CoffeeScript compiler from a terminal. No special permissions are required beyond a working Node.js installation and the coffeescript package.
coffee -c counter.coffee
This writes counter.js next to the source file. Open it and look for binding helpers, .bind(this), or arrow functions. To generate a source map for debugging, add -m:
coffee -c -m counter.coffee
Risk: -c overwrites an existing JavaScript file with the same base name without warning. Back up generated files if you have edited them by hand.
Verify runtime behavior
Compile and run this check:
class Counter
constructor: (@count = 0) ->
increment: =>
console.log "this is Counter:", @ instanceof Counter
@count += 1
c = new Counter
setTimeout c.increment, 50
Expected check: when the callback fires, the console should print this is Counter: true. If it prints false or throws, binding did not take effect.
Trade-offs and limitations
- Per-instance allocation. A
=>method creates a new function for every instance. Prototype methods defined with->are shared. With thousands of objects, bound methods can increase memory use and garbage-collection pressure. - Identity breaks. Each instance owns its copy, so
counter1.increment !== counter2.increment. Code relying on method identity or prototype checks will not recognize the bound copy as the original method. argumentsis rebound. Inside a=>wrapper,argumentsrefers to the wrapper call, not the enclosing invocation.- Debugging opacity. Generated closures and helper variables can obscure line numbers. Enable source maps (
-m) to map compiled output back to CoffeeScript source.
When to use => versus ->
Use => when you pass a method as a callback to timers, event listeners, or higher-order functions and need the instance context. Use -> for ordinary prototype methods always called as obj.method(), because they are shared across instances and cheaper to create.
A practical rule: define the public API with ->, then bind only the specific callbacks you hand away.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.