CoffeeScript Fat Arrows: Lexical this and the Trade-offs of Automatic Binding
CoffeeScript's fat arrow solves JavaScript's this problem in callbacks by binding to the surrounding scope. Here is how it works, when to use it, and what it costs you.
01 Jul 2026, 06:23 UTC

The callback that loses its context
You write a class method that schedules a timeout. When the timer fires, this is no longer the object you expected. The usual JavaScript fixes—stashing var self = this or calling .bind(this)—work, but they add noise. CoffeeScript's fat arrow (=>) removes that noise by capturing this from the surrounding scope, a concept called lexical scope: the variable environment where the function is written, not where it is called.
The useful takeaway: fat arrows are a deliberate scoping decision, not a universal replacement for regular functions. Choosing the wrong arrow introduces subtle bugs in inheritance and library callbacks.
Thin arrow versus fat arrow
CoffeeScript gives you two function syntaxes:
->creates a standard JavaScript function.thisis determined by the caller at runtime, exactly like a normalfunctionexpression.=>creates a function whosethisis fixed to the enclosing lexical scope at definition time.
That distinction lets you signal intent. A thin arrow says "I am a method; call me on an object." A fat arrow says "I am a closure; keep the this from where I was defined."
A worked example and compiler check
Consider a simple timer class. The start method schedules a callback, and the callback needs to call tick on the same instance:
class Timer
constructor: (@label) ->
start: ->
setTimeout (=> @tick()), 1000
tick: ->
console.log "Tick for #{@label}"To see what the compiler does, save that as timer.coffee and run the official compiler from your project root:
coffee -c timer.coffeeIf CoffeeScript is installed locally, use ./node_modules/.bin/coffee -c timer.coffee. No special permissions are required beyond write access to the directory. The command produces timer.js.
Open the generated file and inspect the start method. With the fat arrow, the callback passed to setTimeout should reference the outer instance—often through a captured variable such as _this or a bound wrapper—rather than relying on the callback's own this. If you change => to -> and recompile, that binding disappears. At runtime, the thin-arrow version would lose the instance context, and @tick would fail because this inside the callback is not the Timer object.
Risk: some libraries deliberately set this inside callbacks (jQuery event handlers, for example). A fat arrow overrides that caller-provided context, which can break integrations that depend on it.
Trade-offs and limitations
Fat arrows are not free. Three limitations matter in production code:
- Prototype inheritance friction. When you place a fat arrow inside a class body, CoffeeScript creates a bound function for each instance rather than a shared method on the prototype. That increases memory use and makes it harder to override behavior by patching the prototype later.
- Debugging indirection. The compiled output introduces an extra scope. When you step through a stack trace, the
thisvisible in your source does not map one-to-one to a plain JavaScript function scope, which can confuse developers reading the generated file. - Reduced uniqueness. ES6 arrow functions provide the same lexical
thisbehavior. In new code, CoffeeScript's fat arrow no longer offers a capability that vanilla JavaScript lacks; its value is mainly in existing CoffeeScript codebases.
Practical guidance
Default to thin arrows for object methods. Reach for fat arrows only when a callback crosses an asynchronous boundary—timers, promises, event listeners—and needs the surrounding instance. To confirm your mental model, compile a two-line snippet with both arrow types and compare the generated JavaScript. Look for the outer-scope capture in the fat-arrow version and its absence in the thin-arrow version. If you are maintaining a CoffeeScript 1.x project, note that CoffeeScript 2.x targets modern JavaScript, so the compiled shape differs even though the lexical this rule stays the same.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.