Skip to content
TypeScript ts interfaces 3 min read

Function & Hybrid Interfaces

Interfaces usually describe the shape of objects, but they can also describe functions — and even values that are simultaneously a function and an object. By placing a call signature inside an interface you make instances of that interface callable; by adding ordinary members alongside it you create a hybrid type. This pattern shows up throughout real-world typings: jQuery’s $, libraries that expose a callable default plus helper methods, and configurable factory functions. This page covers function interfaces, hybrid interfaces, generic and construct variants, and when to prefer them over a type alias.

A function interface

A call signature is a nameless member of the form (params): ReturnType. An interface containing one describes a callable value.

interface Comparator {
  (a: number, b: number): number;
}

const ascending: Comparator = (a, b) => a - b;

[3, 1, 2].sort(ascending); // [1, 2, 3]

This is equivalent to the type alias type Comparator = (a: number, b: number) => number. The interface form becomes worthwhile the moment you need more than a single call.

Hybrid interfaces: function plus properties

A hybrid type is callable and also carries properties or methods. This is the canonical use of a function interface — describing values like a configured logger or a counter.

interface Counter {
  (start: number): string; // call signature
  interval: number; // property
  reset(): void; // method
}

function getCounter(): Counter {
  const counter = ((start: number) => `counting from ${start}`) as Counter;
  counter.interval = 123;
  counter.reset = () => {
    counter.interval = 0;
  };
  return counter;
}

const c = getCounter();
c(10); // "counting from 10"
c.interval = 5; // property access
c.reset(); // method call

The call signature handles c(10), while interval and reset describe the object side. The as Counter assertion is the usual way to build such a value incrementally.

Function interface vs type alias

Both can describe a callable value, so which to pick? The table summarizes the trade-offs.

Featureinterface F { (x): R }type F = (x) => R
Callable
Extra properties / methods✅ (via intersection)
Multiple call signatures (overloads)❌ (needs overloaded function)
Declaration merging
Unions, mapped, conditional

For a plain callback, a type alias reads better. For a callable that also merges, overloads, or carries a rich set of members, the interface wins.

Overloaded and generic call signatures

An interface can list multiple call signatures to express overloads, and the call signature itself may be generic.

interface Mapper {
  <T, U>(items: T[], fn: (item: T) => U): U[];
}

const mapItems: Mapper = (items, fn) => items.map(fn);

const lengths = mapItems(["a", "bb"], (s) => s.length); // number[]

Placing the type parameters on the signature (<T, U>(...)) makes each call independently generic, so T and U are inferred fresh per invocation.

Construct signatures in interfaces

Adding new before a signature describes a constructor. Combined with a call signature, an interface can model a value usable both as a function and with new.

interface Factory {
  new (id: string): { id: string }; // construct signature
  create(id: string): { id: string }; // static-style helper
}

A hybrid interface mirrors JavaScript reality: functions are objects, so they can hold properties. Use it to type libraries whose default export is “a function you can also configure.”

Best Practices

  • Use a call signature inside an interface to make the type callable.
  • Reach for a hybrid interface when a value is both callable and carries properties or methods.
  • Prefer a plain type alias ((x) => R) for simple callbacks; use an interface when you need overloads, merging, or many members.
  • Place type parameters on the call signature to make each call independently generic.
  • Use new (...) construct signatures to describe constructor-style callables.
  • Build hybrid values with an as Interface assertion, assigning properties after creating the function.
Last updated June 29, 2026
Was this helpful?