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.
| Feature | interface 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 Interfaceassertion, assigning properties after creating the function.