Menu

Interface vs type em TypeScript: diferenças e quando usar

interface e type conseguem descrever formatos de objeto, e na maior parte do tempo qualquer um serve. Veja as diferenças reais: declaration merging, unions e mapped types, extends vs intersections, index signatures implícitas, mensagens de erro e desempenho do compilador, além de uma regra clara para escolher.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Para descrever o formato de um objeto, interface e type fazem o mesmo trabalho, e valores de um podem ser atribuídos ao outro quando os formatos coincidem. As diferenças estão nas bordas: o type pode nomear coisas que uma interface não pode (unions, tuplas, tipos calculados), e uma interface faz algumas coisas que um type alias não faz (merging, extends verificado).

O TypeScript compara tipos de objeto pela estrutura, então o nome da declaração não importa para a compatibilidade. A seguir estão os lugares em que a escolha importa.

Tabela comparativa

Recursointerfacetype
Formatos de objetosimsim
Opcionais, readonly, métodos, index signaturessimsim
Genericssimsim
Unions (A | B)nãosim
Tuplas, primitivos e tipos de função isoladosnão (tipos de função só como call signatures)sim
Mapped e conditional typesnãosim
Estenderextends, conflitos são erros&, conflitos viram never
Declaration mergingsimnão (identificador duplicado)
Atribuível a Record<string, T>nãosim, quando as propriedades cabem
implements em uma classesimsim, se for um tipo de objeto
Definições recursivassimsim

O que só o type faz

Tudo o que não é um único formato de objeto precisa de um type alias:

Nada disso pode ser escrito com interface (os dois últimos são tratados em mapped types e conditional types). Esse é o motivo prático de toda base de código acabar usando type em algum lugar, seja lá o que use para formatos de objeto.

O que só a interface faz: declaration merging

Duas declarações interface com o mesmo nome no mesmo escopo se combinam em uma. Duas declarações type com o mesmo nome geram o erro TS2300, Duplicate identifier.

interface Settings {
  theme: string;
}
interface Settings {
  fontSize: number;
}
const s: Settings = { theme: "dark", fontSize: 14 }; // needs both

type Options = { a: number };
type Options = { b: number }; // error TS2300: Duplicate identifier 'Options'.

O merging é como os tipos de bibliotecas são estendidos de fora: acrescentar uma propriedade ao Window global, ao Request do Express ou ao tipo de tema de uma biblioteca. Se você publica tipos que os usuários podem precisar ampliar, use interfaces. No código da sua própria aplicação, uma junção acidental (dois arquivos de script declarando o mesmo nome de interface global) acontece em silêncio, a menos que os dois declarem a mesma propriedade com tipos diferentes, e esse é um dos argumentos que alguns times usam para preferir type.

extends vs intersection

Uma interface estende com extends, um type alias com &. Na maioria das vezes o resultado é o mesmo, mas eles tratam de forma diferente uma propriedade conflitante. O extends aponta o conflito na declaração:

index.ts(7,11): error TS2430: Interface 'Broken' incorrectly extends interface 'Base'.
  Types of property 'id' are incompatible.
    Type 'number' is not assignable to type 'string'.

Uma intersection aceita o mesmo conflito em silêncio e transforma a propriedade em never (uma string que também é number). O erro só aparece depois, quando você tenta criar um valor:

O erro aponta para o objeto, longe da declaração que o causou. Para montar tipos de objeto a partir de outros tipos de objeto, o extends dá a mensagem melhor.

Index signatures: uma diferença fácil de perder

Um type alias para um tipo de objeto recebe uma index signature implícita, então pode ser passado onde um Record<string, unknown> é esperado. Uma interface não. Isso é proposital: a explicação do time do TypeScript (issue #15300 no repositório do TypeScript) é que uma interface pode ser ampliada por declarações posteriores, então inferir uma index signature para ela é menos seguro, e mudar a regra agora quebraria código demais.

A chamada marcada com @ts-expect-error roda mesmo assim e imprime os campos de Alan: a regra vale em tempo de compilação. Esse é o motivo comum de um erro confuso quando um valor de interface é passado para um helper de log, serialização ou consulta tipado com Record<string, ...>. Troque essa declaração específica para type, espalhe o valor com spread ou tipe o parâmetro do helper com uma interface ou um generic.

Desempenho e mensagens de erro

A página Performance da wiki do TypeScript (seção "Preferring Interfaces Over Intersections") sugere interface Foo extends Bar, Baz { ... } em vez de type Foo = Bar & Baz & { ... } ao compor tipos de objeto. Os motivos: uma interface é um único tipo de objeto plano que detecta conflitos de propriedades, as relações de tipo entre interfaces ficam em cache (intersections como um todo não ficam), e verificar um valor contra uma intersection verifica cada componente antes do tipo achatado. A diferença importa em bases de código grandes com muitos tipos compostos; com alguns formatos de objeto comuns você não vai conseguir medi-la.

A mesma página observa que interfaces aparecem melhor. Uma interface é mostrada pelo nome nos tooltips e nas mensagens de erro, enquanto um alias de intersection muitas vezes é impresso expandido em suas partes, o que deixa mensagens longas mais difíceis de ler.

Qual usar

Uma regra que funciona:

  1. Formatos de objeto: interface. Ela dá extends verificado, erros mais claros em composições grandes e permite que usuários de bibliotecas a ampliem. Isso segue a heurística do handbook do TypeScript: "use interface until you need to use features from type".
  2. Todo o resto: type. Unions, tuplas, tipos de função, literal types e tudo o que é montado com mapped, conditional ou template literal types.
  3. Exceção: use type para um formato de objeto que precisa caber em parâmetros Record<string, ...>, ou quando você quer impedir o merging de propósito.

Usar type para tudo também é uma escolha coerente, e muitas bases de código fazem isso. Misturar os dois ao acaso é a única opção a evitar: faz quem lê se perguntar se havia uma diferença intencional.

Perguntas frequentes

Qual é a diferença entre type e interface no TypeScript?

Os dois descrevem formatos de objeto e são intercambiáveis para isso. O type também pode nomear unions, tuplas, primitivos e mapped ou conditional types, o que a interface não pode. A interface suporta declaration merging e extends, que verifica propriedades conflitantes. Tipos de objeto escritos com type também podem ser atribuídos a tipos com index signature como Record<string, unknown>, e interfaces não.

Devo usar type ou interface?

A heurística do handbook do TypeScript é: use interface até precisar de um recurso que só o type tem. Na prática, isso significa interfaces para formatos de objeto e type para unions, tuplas, tipos de função e tipos calculados. Times que usam type para tudo também se saem bem; o importante é uma regra consistente.

Interface é mais rápida que type no TypeScript?

Para compor tipos de objeto, em alguns casos sim. As orientações de desempenho do time do TypeScript recomendam interface ... extends em vez de intersections grandes (A & B & { ... }), porque as relações entre interfaces ficam em cache e uma interface é um único tipo plano. Para um formato de objeto simples não há diferença relevante.

Uma classe pode implementar um type alias?

Sim, se o alias for um tipo de objeto (ou uma intersection de tipos de objeto): class Point implements PointType { ... } funciona. Uma classe não pode implementar um union type; isso gera o erro TS2422.

Uma interface pode estender um type alias?

Sim, desde que o alias seja um tipo de objeto: type Base = { id: string }; interface User extends Base { name: string } é válido. Ela não pode estender um alias de union. No sentido contrário, um type alias pode partir de uma interface com &.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR