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
| Recurso | interface | type |
|---|---|---|
| Formatos de objeto | sim | sim |
| Opcionais, readonly, métodos, index signatures | sim | sim |
| Generics | sim | sim |
Unions (A | B) | não | sim |
| Tuplas, primitivos e tipos de função isolados | não (tipos de função só como call signatures) | sim |
| Mapped e conditional types | não | sim |
| Estender | extends, conflitos são erros | &, conflitos viram never |
| Declaration merging | sim | não (identificador duplicado) |
Atribuível a Record<string, T> | não | sim, quando as propriedades cabem |
implements em uma classe | sim | sim, se for um tipo de objeto |
| Definições recursivas | sim | sim |
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:
- Formatos de objeto:
interface. Ela dáextendsverificado, 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: "useinterfaceuntil you need to use features fromtype". - Todo o resto:
type. Unions, tuplas, tipos de função, literal types e tudo o que é montado com mapped, conditional ou template literal types. - Exceção: use
typepara um formato de objeto que precisa caber em parâmetrosRecord<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 &.