skip to content

Which of JavaScript's callable forms carry a `.prototype` property — function declarations, arrow functions, object-literal method shorthand, classes, and generator functions?

level: middleimportance: should knowfreq 40%

answer

  1. not every callable gets one
  2. the lightweight forms were left out
  3. shorthand differs from a function expression
  4. classes get one but cannot reassign it
  5. generators have one for a different reason

basics

~10 s

Function declarations and expressions, classes, generator functions and async generator functions have a .prototype property. Arrow functions, method shorthand, async functions and bound functions do not; reading .prototype on them gives undefined.

solid answer

~40 s

Not every callable gets one. Ordinary function declarations and expressions do — a writable, non-enumerable object with a `constructor` back-pointer. Classes do too, but theirs is non-writable, so you cannot reassign it. Generator and async generator functions have a `.prototype` as well, though it serves a different job: it becomes the prototype of the generator object the call returns, not of anything made with `new`. Arrow functions, object-literal and class method shorthand, async functions and the result of `.bind()` have **no** `.prototype` property at all — `(() => {}).prototype` is `undefined`. The rule of thumb is that the property exists when the function is meant to produce objects that inherit from it, and the forms without it were deliberately defined as the lighter, non-instance-producing kind.

code

javascript · 15 lines
javascript
const forms = {
  declaration: function f() {},
  arrow: () => {},
  shorthand: { m() {} }.m,
  klass: class C {},
  generator: function* g() {},
  asyncFn: async function a() {},
  bound: function b() {}.bind(null),
};

for (const [name, fn] of Object.entries(forms)) {
  console.log(name, typeof fn.prototype);
}
// declaration object / arrow undefined / shorthand undefined
// klass object / generator object / asyncFn undefined / bound undefined

go deeper

for a junior

Know that arrow functions have no .prototype and cannot be used with new, and that ordinary function declarations and classes do have one.

for a middle

Explain the full split — declarations, classes and generators have it; arrows, method shorthand, async functions and bound functions do not — and describe what each existing .prototype object actually holds.

for a senior

Show where this bites in real code: generic helpers and decorators that read fn.prototype break on the forms that lack it, and an innocuous shorthand refactor can be the trigger.

for a principal

Own the API-design angle: a utility that accepts arbitrary callables must define which forms it supports and fail loudly on the rest, rather than assuming .prototype exists and breaking for callers years later.

## The property is not universal It is easy to assume `.prototype` comes with every function. It does not. Whether a callable gets one depends on how it was written: ```js (function f() {}).prototype; // object (class C {}).prototype; // object (function* g() {}).prototype; // object (async function* ag() {}).prototype; // object (() => {}).prototype; // undefined ({ m() {} }).m.prototype; // undefined (async function a() {}).prototype; // undefined (function f() {}).bind(null).prototype; // undefined ``` ## The forms that have one, and what it holds A **function declaration or expression** gets a freshly created plain object whose own `constructor` property points back at the function and whose own prototype is `Object.prototype`. The property is writable, non-enumerable and non-configurable, which is why you can replace it wholesale and why it never appears in enumeration. A **class** gets the same arrangement with one difference: `C.prototype` is non-writable as well as non-enumerable and non-configurable, so an assignment to it throws in strict mode (and class bodies are always strict). The instance methods you write in the class body land on that object. ```js class C { m() {} } Object.getOwnPropertyDescriptor(C, 'prototype'); // { value: {...}, writable: false, enumerable: false, configurable: false } Object.getOwnPropertyNames(C.prototype); // ['constructor', 'm'] ``` A **generator function** has a `.prototype` too, but it plays a different role. It is not for instances — a generator function is not a constructor at all, and `new g()` throws a `TypeError`. Instead, calling `g()` returns a generator object whose prototype is `g.prototype`, and `g.prototype` itself inherits the generator machinery (`next`, `return`, `throw`). Consequently it has no `constructor` back-pointer, unlike a normal function's prototype object. Putting a method on `g.prototype` makes it available on every generator that function produces. **Async generator functions** work the same way. ## The forms that have none **Arrow functions** are defined without a `prototype` property. They were designed as lightweight callables that do not create instances — which is the same design decision that makes `new (() => {})` throw. **Method shorthand** — `{ m() {} }` in an object literal, and every method in a class body including static ones and accessors — also has no `prototype`. Writing `{ m: function () {} }` instead *does* produce one, because that is an ordinary function expression assigned to a property. The two spellings look interchangeable and are not. **Async functions** have no `prototype` property either; they always return a promise and never produce instances. Note the asymmetry with async *generators*, which do have one. **Bound functions** are their own kind of exotic object. `f.bind(null)` produces a callable that has no `prototype` property of its own, even when `f` had one — yet `new boundF()` still works, because construction is forwarded to the original target function, and the resulting object inherits from the *target's* `.prototype`. ## Why an interviewer asks this The practical payoff is diagnosing a specific class of bug. Code that inspects functions generically — a decorator, a mixin helper, a serialiser — often reaches for `fn.prototype` to find methods or to build an instance, and then fails on exactly the inputs that have none: ```js function listMethods(fn) { return Object.getOwnPropertyNames(fn.prototype); } listMethods(() => {}); // TypeError: Cannot convert undefined to object ``` The same shape explains why converting a method to an arrow function, or a function expression to method shorthand during a refactor, can break code that was quietly depending on `.prototype` existing. A defensive helper checks first: ```js const hasProto = typeof fn === 'function' && typeof fn.prototype === 'object'; ``` ## A consistent way to remember it The property exists when the function is meant to hand a prototype to objects it produces — instances for constructors and classes, generator objects for generator functions. It is absent for the forms defined as "just a callable": arrows, methods, async functions, and bound wrappers. That framing predicts every case above without memorising a table.

  • A generator function has a `.prototype`, yet `new g()` throws. What is that object for?
    It is the prototype of the generator *object* the call returns, not of a constructed instance. `g()` produces an iterator whose prototype is `g.prototype`, and that object in turn inherits `next`, `return` and `throw` from the generator machinery. Anything you attach to `g.prototype` is visible on every generator that function yields. Generator functions are simply not constructors, so `new` is rejected.
  • `f.bind(null)` has no `.prototype`, so why does `new (f.bind(null))()` still produce an object that inherits from `f.prototype`?
    A bound function is an exotic object that forwards construction to its target. `new` on the bound wrapper runs the original `f` as the constructor, so the instance's prototype comes from `f.prototype`, not from the wrapper — which has no such property to offer. This is why code that inspects `boundFn.prototype` to discover a class's methods silently finds `undefined`.
  • During a refactor someone rewrites `{ greet: function () {} }` as `{ greet() {} }`. What can break?
    Anything relying on `greet.prototype`. The shorthand form is defined without a `prototype` property and is not a constructor, so `new obj.greet()` now throws and generic helpers that read `fn.prototype` get `undefined` instead of an object. The two spellings are widely treated as equivalent style choices; on this one point they are not.

saying these in an interview costs you the question

  • Says every function has a .prototype property
  • Thinks method shorthand equals a function expression
  • Assumes generator .prototype is for new instances
  • Believes a bound function copies the target's .prototype
  • Claims a class's .prototype can be reassigned

context