Skip to main content

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!
Try
TypeScript comparison

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.
Try

The underlying reason is receiver safety: Flow preserves the receiver requirement of class instance and interface methods when they are extracted. Letting a class or interface value flow into an object type would erase that requirement, since object-type methods have no implicit receiver requirement. Plain object-literal methods carry no implicit receiver requirement because Flow bans this references in their bodies.

So three kinds of values relate to the two structural type shapes asymmetrically:

valueassignable to object typeassignable 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}
Try

You can also use implements with multiple interfaces.

1interface Bar {}2interface Baz {}3
4class Foo implements Bar, Baz {5  // ...6}
Try

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}
Try

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!
Try

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}
Try

Also like class methods, extracted interface methods retain their receiver requirement, so calling one without a compatible receiver is an error.

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}
Try

Interface Properties​

You can add properties to interfaces following the same syntax as class properties:

1interface MyInterface {2  property: string;3}
Try

Interface properties can be optional as well:

1interface MyInterface {2  property?: string;3}
Try

Interfaces as maps​

You can create indexer properties the same way as with objects:

1interface MyInterface {2  [key: string]: number;3}
Try

Interface Generics​

Interfaces can also have their own generics:

1interface MyInterface<A, B, C> {2  property: A;3  method(val: B): C;4}
Try

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};
Try

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.
Try

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!
Try

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}
Try

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}
Try

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; // Works
Try

Because 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}
Try

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}
Try

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: "forty-two" [1] is incompatible with number [2].11const value2: Contravariant = {writeOnly: numberOrString}; // Works!
Try

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}
Try

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
  • Modernizing Legacy Flow Syntax — migrating the legacy + / - property variance sigils to the readonly / writeonly keywords