CoffeeScript Class Inheritance: What extends and super Compile To
CoffeeScript class inheritance is a compile-time abstraction over JavaScript prototypes. Learn how extends and super lower to different output in 1.x vs 2.x, and how to verify the emitted JavaScript.
06 Dec 2025, 07:05 UTC

The problem: inheritance that works until it doesn't
CoffeeScript's class syntax looks like ES6 classes, but it is a compile-time abstraction. Depending on your CoffeeScript version and build target, extends and super lower to different JavaScript patterns. When a subclass behaves unexpectedly—instanceof checks fail, a parent method never runs, or a stack trace lands in generated code—the fix is not to guess. The fix is to look at what your compiler actually emitted.
What extends compiles to
In CoffeeScript 1.x, the compiler emits ES5-compatible code. The extends keyword triggers a helper (commonly named __extends) that copies static properties and links the child prototype to the parent prototype. In CoffeeScript 2.x, when your target environment supports ES6 classes, the compiler can emit a native class statement instead. The two outputs are not byte-identical, and the debugging experience differs.
Because of this split, the first diagnostic step for any inheritance bug is to inspect the generated JavaScript for your specific toolchain. Do not assume the output matches documentation written for a different major version.
How super is wired
Inside a constructor, super() invokes the parent constructor with the child instance's this context. Inside a method, super.methodName() looks up methodName on the parent prototype and invokes it with the current this. CoffeeScript resolves these references at compile time by rewriting them to explicit prototype lookups in the emitted JavaScript.
This resolution is strict: super is only valid inside a class method or constructor. Using it in a plain function or outside a class body produces a compile error, not a runtime error.
A worked example: compile, inspect, verify
Create a file named animals.coffee in your project root:
class Animal
constructor: (@name) ->
speak: ->
"#{@name} makes a sound"
class Dog extends Animal
constructor: (name) ->
super(name)
speak: ->
super.speak() + ": woof"
Compile it with source maps so you can debug the original source later. Run this in your project root; you need write permission for the output directory:
coffee -c -m -o lib src/animals.coffee
Expected check: Open lib/animals.js. In a 1.x build you should see a prototype assignment linking Dog to Animal and a super.speak style call rewritten to a parent-prototype lookup. In a 2.x build targeting ES6, you should see a native class Dog extends Animal with super.speak() preserved. If you see neither pattern, your build configuration is not applying the CoffeeScript transform you expect.
Runtime check: Execute the compiled file in Node (or load it in a browser console) and confirm that new Dog("Rex").speak() returns a string containing both the parent output and woof. If the parent text is missing, the prototype link was not established correctly.
Trade-offs and sharp edges
Implicit returns leak values. CoffeeScript methods return the last expression unless you add an explicit return. When you override a parent method and forget to return, the child method may return a value the parent contract did not intend. If an API expects undefined from a setter-like method, an implicit return can break callers.
Source maps are not optional. Without -m or the equivalent compiler API option, stack traces point to generated JavaScript line numbers. With source maps enabled, debuggers can map frames back to .coffee lines. Verify this by throwing an error inside a method and checking whether the trace references your original file.
ES6 interop can break instanceof. A CoffeeScript subclass compiled to ES5-style prototype code and an ES6 class defined elsewhere may not share the same prototype-chain layout. If you pass instances between the two systems, instanceof checks can fail even though the objects behave similarly. Before relying on instanceof, confirm that both classes were compiled by the same toolchain and target.
| Behavior | CoffeeScript 1.x (ES5 target) | CoffeeScript 2.x (ES6 target) |
|---|---|---|
| Class output | Constructor function + __extends helper | Native class statement |
super in methods | Rewritten to parent prototype lookup | Native super keyword |
| Source-map need | High; stack traces point to generated JS | Lower if native classes are emitted, still recommended |
Actionable check
Before you debug another inheritance issue, compile a minimal two-class file with your project's exact CoffeeScript version and target flags. Read the emitted JavaScript. Confirm that the child prototype is linked to the parent, that constructor super() calls the parent function, and that method super.x() resolves to the parent prototype. If any of those three links is missing, the problem is in your build configuration, not in your CoffeeScript source.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.