Interfaces
Interfaces declare the structure a class or object must satisfy without requiring a specific class identity. Because classes in Flow are nominally typed, two classes with identical members are not interchangeable — interfaces provide structural typing to bridge that gap.
1interface Serializable {2 serialize(): string;3}4
5class Foo {6 serialize(): string { return '[Foo]'; }7}8
9class Bar {10 serialize(): string { return '[Bar]'; }11}12
13const foo: Foo = new Bar(); // Error!incompatible-typeCannot assign new Bar() to foo because Bar [1] is incompatible with Foo [2].14
15const foo2: Serializable = new Foo(); // Works!16const bar2: Serializable = new Bar(); // Works!TypeScript types classes structurally, making interfaces and object types largely interchangeable. Flow diverges in three places: an interface is not a subtype of an object type because interfaces may be backed by classes, which Flow types nominally; implements and extends clauses must name an interface or class, not an arbitrary object type; and primitives are not subtypes of interfaces or object types in Flow, while TS lets a string satisfy {length: number}.
When to use this
Use interfaces over object types when you need to accept both class instances and plain objects with the same shape. Use interfaces over classes when you want structural compatibility — any value with matching properties is assignable, regardless of which class it was constructed from.
Interfaces for instances and objects
Interfaces can describe both instances and objects, unlike object types which can only describe objects:
1class Foo {2 a: number;3}4const foo = new Foo();5const o: {a: number} = {a: 1};6
7interface MyInterface {8 a: number;9}10
11function acceptsMyInterface(x: MyInterface) { /* ... */ }12acceptsMyInterface(o); // Works!13acceptsMyInterface(foo); // Works!14
15function acceptsObj(x: {a: number, ...}) { /* ... */ }16acceptsObj(o); // Works!17acceptsObj(foo); // Error!class-object-subtypingCannot call acceptsObj with foo bound to x because Foo [1] is not a subtype of object type [2]. Class instances are not subtypes of object types; consider rewriting object type [2] as an interface.The underlying reason is method unbinding: Flow tracks this on class methods, and letting a class instance flow into an object type would be a backdoor around that rule — extract obj.method through the object-type alias and call it without a receiver. Interfaces have the same hazard, so an interface-typed value also can't flow into an object type. Plain object literals carry no this binding to lose, which is why they flow into object types freely.
So three kinds of values relate to the two structural type shapes asymmetrically:
| value | assignable to object type | assignable to interface |
|---|---|---|
| object literal | ✓ | ✓ |
| class instance | ✓ | |
| interface-typed value | ✓ |
The error code depends on the object type's exactness: against an exact object type (the default) it's [incompatible-exact]; against an inexact object type ({a: number, ...}) it's [class-object-subtyping] with the suggestion to rewrite the object type as an interface. The fix in both cases is to switch the parameter type to an interface — interfaces accept all three kinds.
Unlike objects, interfaces cannot be exact, as they can always have other, unknown properties.
Implementing interfaces
You can use implements to tell Flow that you want the class to match an
interface. This prevents you from making incompatible changes when editing the
class.
1interface Serializable {2 serialize(): string;3}4
5class Foo implements Serializable {6 serialize(): string { return '[Foo]'; } // Works!7}8
9class Bar implements Serializable {10 serialize(): number { return 42; } // Error!incompatible-typeCannot implement Serializable [1] with Bar because in property serialize > the return value: number [2] is incompatible with string [3].11}You can also use implements with multiple interfaces.
class Foo implements Bar, Baz {
// ...
}
Interface Syntax
Interfaces are created using the keyword interface followed by its name and
a block which contains the body of the type definition.
1interface MyInterface {2 // ...3}The syntax of the block matches the syntax of object types.
Anonymous interfaces
You can also declare an anonymous interface inline:
1class Foo {2 a: number;3}4
5function getNumber(o: interface {a: number}): number {6 return o.a;7}8
9getNumber(new Foo()); // Works!Interface Methods
You can add methods to interfaces following the same syntax as class methods. Any this parameters you
provide are also subject to the same restrictions as class methods.
1interface MyInterface {2 method(value: string): number;3}Also like class methods, interface methods must also remain bound to the interface on which they were defined.
You can define overloaded methods by declaring the same method name multiple times with different type signatures:
1interface MyInterface {2 method(value: string): string;3 method(value: boolean): boolean;4}5
6function func(a: MyInterface) {7 const x: string = a.method('hi'); // Works!8 const y: boolean = a.method(true); // Works!9
10 const z: boolean = a.method('hi'); // Error!incompatible-typeCannot assign a.method(...) to z because string [1] is incompatible with boolean [2].11}Interface Properties
You can add properties to interfaces following the same syntax as class properties:
1interface MyInterface {2 property: string;3}Interface properties can be optional as well:
1interface MyInterface {2 property?: string;3}Interfaces as maps
You can create indexer properties the same way as with objects:
1interface MyInterface {2 [key: string]: number;3}Interface Generics
Interfaces can also have their own generics:
1interface MyInterface<A, B, C> {2 property: A;3 method(val: B): C;4}Interface generics are parameterized. When you use an interface you need to pass parameters for each of its generics:
1interface MyInterface<A, B, C> {2 foo: A;3 bar: B;4 baz: C;5}6
7const val: MyInterface<number, boolean, string> = {8 foo: 1,9 bar: true,10 baz: 'three',11};Primitives are not subtypes of interfaces
Flow rejects primitives (string, number, boolean) flowing into an interface or object type, even when their boxed prototype would satisfy the contract. The hazard is silent shape ambiguity: a bare string structurally satisfies Iterable<string> (via String.prototype[Symbol.iterator]), but iterating it yields code points instead of items — almost always a logic bug rather than the intent.
1function logAll(items: Iterable<string>) {2 for (const x of items) console.log(x);3}4
5const msgs = "foo";6logAll(msgs); // Error: `string` is not a subtype of `Iterable<string>`incompatible-typeCannot call logAll with msgs bound to items because string literal foo [1], a primitive, cannot be used as a subtype of $Iterable [2]. You can wrap it in new String(...)) to turn it into an object and attempt to use it as a subtype of an interface.If a one-element iterable was intended, wrap the value with [s] at the call site; otherwise the call was passing the wrong shape.
Extending interfaces
An interface can extend other interfaces or classes to inherit their members:
1interface Animal {2 name: string;3}4interface Dog extends Animal {5 breed: string;6}7
8const d: Dog = {name: "Rex", breed: "Lab"};9const a: Animal = d; // Works!The right-hand side of extends must name an interface or class — passing an object-type alias errors with [incompatible-use] ("not inheritable"). The same constraint applies to the implements clause on a class ([cannot-implement]). Mapped or utility types applied to interfaces produce object types, so they also won't work in either position. The rewrite is to introduce a named interface (or inline the members directly) instead.
Interface property variance (read-only and write-only)
Interface properties are invariant by default. But you can add modifiers to make them covariant (read-only) or contravariant (write-only).
1interface MyInterface {2 readonly covariant: number; // read-only3 writeonly contravariant: number; // write-only4}Covariant (read-only) properties on interfaces
You can make a property covariant by adding the readonly keyword in front of the
property name:
1interface MyInterface {2 readonly readOnly: number | string;3}This allows you to pass a more specific type in place of that property:
1interface Invariant {2 property: number | string;3}4interface Covariant {5 readonly readOnly: number | string;6}7
8const x: {property: number} = {property: 42};9const y: {readOnly: number} = {readOnly: 42};10
11const value1: Invariant = x; // Error!incompatible-typeCannot assign x to value1 because in property property: number [1] is not exactly the same as number | string [2].12const value2: Covariant = y; // WorksBecause of how covariance works, covariant properties also become read-only when used. Which can be useful over normal properties.
1interface Invariant {2 property: number | string;3}4interface Covariant {5 readonly readOnly: number | string;6}7
8function func1(value: Invariant) {9 value.property; // Works!10 value.property = 3.14; // Works!11}12
13function func2(value: Covariant) {14 value.readOnly; // Works!15 value.readOnly = 3.14; // Error!cannot-writeCannot assign 3.14 to value.readOnly because property readOnly is not writable.16}Contravariant (write-only) properties on interfaces
You can make a property contravariant by adding the writeonly keyword in front of
the property name.
1interface InterfaceName {2 writeonly writeOnly: number;3}This allows you to pass a less specific type in place of that property.
1interface Invariant {2 property: number;3}4interface Contravariant {5 writeonly writeOnly: number;6}7
8const numberOrString = Math.random() > 0.5 ? 42 : 'forty-two';9
10const value1: Invariant = {property: numberOrString}; // Error!incompatible-typeCannot assign object literal to value1 because in property property: string [1] is incompatible with number [2].11const value2: Contravariant = {writeOnly: numberOrString}; // Works!Because of how contravariance works, contravariant properties also become write-only when used. Which can be useful over normal properties.
1interface Invariant {2 property: number;3}4interface Contravariant {5 writeonly writeOnly: number;6}7
8function func1(value: Invariant) {9 value.property; // Works!10 value.property = 3.14; // Works!11}12
13function func2(value: Contravariant) {14 value.writeOnly; // Error!cannot-readCannot get value.writeOnly because property writeOnly is not readable.15 value.writeOnly = 3.14; // Works!16}See Also
- Classes — nominally typed values that can implement interfaces
- Objects — structurally typed object types, which cannot describe class instances
- Nominal & Structural Typing — the difference between name-based and shape-based typing
- Variance — covariant (read-only) and contravariant (write-only) properties