Interface vs type no TypeScript: quando usar cada um
Compare interface e type com objetos, extensão, unions, tuples e declaration merging para escolher pela necessidade do código, não por preferência.
Use interface quando quiser nomear principalmente a forma de um objeto e
permitir extensão; use type quando precisar nomear unions, tuples, primitivos
ou composições fechadas. Para um objeto simples, os dois costumam resolver, e a
consistência do projeto vale mais que uma disputa por sintaxe.
Esta lição mostra a diferença aparecendo no compilador. A gente começa com o
mesmo objeto, reproduz uma propriedade ausente, estende contratos dos dois modos
e chega ao ponto em que apenas type representa a ideia diretamente. O
guia de TypeScript traz a visão do cluster, enquanto a
lição de tipos básicos prepara
unions e propriedades opcionais usadas aqui.
Planta aberta e ficha de composição: o modelo mental
Imagine dois documentos de uma loja. A planta do ponto comercial pode receber uma nova área numa revisão autorizada. Já uma ficha de composição pode dizer “kit é teclado e mouse” ou “pagamento é cartão ou Pix”. Os dois documentos descrevem algo, mas foram feitos para tipos diferentes de composição.
No mapa técnico, interface lembra a planta aberta: outra declaração compatível
pode ampliar o mesmo nome, e uma interface pode estender outra. type lembra a
ficha: ele dá nome a qualquer expressão de tipo, inclusive A | B, [A, B] e
A & B, mas o mesmo alias não pode ser reaberto.
O limite da analogia é que TypeScript usa tipagem estrutural. Ele não exige um carimbo dizendo de qual planta um objeto veio; confere se a forma necessária está presente. Interface também consegue usar interseções ao redor dela, e type consegue modelar objetos extensos. O modelo ajuda a escolher, não cria duas categorias isoladas.
No objeto simples, as duas formas se encontram
Estas declarações expressam o mesmo conjunto de propriedades:
interface ProdutoInterface {
nome: string;
preco: number;
}
type ProdutoType = {
nome: string;
preco: number;
};
const mouse: ProdutoInterface = { nome: 'Mouse', preco: 180 };
const teclado: ProdutoType = { nome: 'Teclado', preco: 250 };
console.log(mouse.nome, teclado.nome);Nenhuma das duas formas cria objeto, construtor ou validação em runtime. Elas nomeiam contratos que o compilador apaga. Se o projeto inteiro usa interfaces para modelos de domínio, trocar um objeto isolado por type não traz um ganho automático. A diferença merece aparecer só quando a necessidade aparece.
A forma importa mais que o nome de origem
Na tipagem estrutural, um valor é compatível se possui a estrutura exigida. O objeto pode ter vindo de outro lugar e pode carregar propriedades extras:
interface Identificavel {
id: number;
}
const produtoCompleto = {
id: 7,
nome: 'Headset',
preco: 320,
};
function mostrarId(item: Identificavel): void {
console.log(`ID: ${item.id}`);
}
mostrarId(produtoCompleto);produtoCompleto não declarou “implements Identificavel”. Ainda assim, possui
id: number, então pode entrar. Isso também acontece com type aliases de objeto;
não é uma exclusividade de interface. Para revisar como objetos se comportam em
execução, consulte objetos em JavaScript.
Uma propriedade obrigatória ausente, porém, quebra o contrato:
interface Produto {
nome: string;
preco: number;
}
const produto: Produto = {
nome: 'Webcam',
};Leia o diagnóstico como uma lista de conferência: o objeto encontrado tem nome;
Produto exige também preço. Marcar preco? só é correção se preço puder faltar
de verdade no domínio. Caso contrário, acrescente o valor que ficou de fora.
Interface estende por nome
Uma interface usa extends para herdar propriedades de outra e acrescentar as
suas:
interface Pessoa {
nome: string;
}
interface Cliente extends Pessoa {
pontos: number;
}
const ana: Cliente = {
nome: 'Ana',
pontos: 120,
};
console.log(`${ana.nome}: ${ana.pontos} pontos`);O termo técnico é interface extension. A tradução direta: Cliente precisa cumprir tudo de Pessoa e ainda ter pontos. Use isso quando a relação “é uma especialização de” deixa o domínio mais claro. Uma árvore funda de herança pode esconder a origem das propriedades; muitas vezes objetos menores compostos são mais legíveis.
Type compõe objetos por interseção
Um alias usa &, a intersection type, para exigir os dois lados:
type ComNome = {
nome: string;
};
type ComContato = {
email: string;
};
type Assinante = ComNome & ComContato;
const bia: Assinante = {
nome: 'Bia',
email: 'bia@example.com',
};
console.log(`${bia.nome} <${bia.email}>`);Interseção significa que o valor precisa satisfazer ComNome e ComContato.
Não é “misturar objetos” em runtime; nenhum Object.assign é gerado. É uma regra
da checagem. Interfaces também podem estender vários contratos, então escolha a
forma que melhor comunica a composição do projeto.
Union e tuple são território natural de type
Uma interface descreve forma de objeto. Ela não declara diretamente “texto ou número”. Um type alias nomeia essa alternativa:
type Codigo = string | number;
function formatarCodigo(codigo: Codigo): string {
return typeof codigo === 'number'
? codigo.toString().padStart(4, '0')
: codigo.toUpperCase();
}
console.log(formatarCodigo(9));
console.log(formatarCodigo('ab-2'));O | é uma union. Como cada alternativa permite operações diferentes, a função
usa narrowing com typeof. A próxima lição ensina narrowing em
TypeScript com casos mais ricos.
Tuple também é um tipo de array em que posição e tamanho carregam significado:
type Coordenada = [latitude: number, longitude: number];
const loja: Coordenada = [-23.561, -46.656];
const [latitude, longitude] = loja;
console.log(latitude, longitude);Os rótulos latitude e longitude ajudam o editor, mas o valor executado ainda
é um array. Se o domínio cresceu a ponto de haver muitas posições ou campos
opcionais, um objeto costuma comunicar melhor que uma tuple longa.
Declaration merging abre a interface
Duas interfaces de mesmo nome, no mesmo escopo, são combinadas:
interface Configuracao {
tema: 'claro' | 'escuro';
}
interface Configuracao {
idioma: 'pt-BR' | 'en';
}
const configuracao: Configuracao = {
tema: 'escuro',
idioma: 'pt-BR',
};
console.log(configuracao.tema, configuracao.idioma);Esse comportamento chama-se declaration merging. Ele é útil para ampliar tipos de bibliotecas e objetos globais quando a plataforma prevê extensão. Em um modelo de negócio que deveria ser fechado, a mesma abertura pode surpreender: uma declaração distante muda as exigências do nome em todo o programa.
Type aliases não reabrem:
type Configuracao = {
tema: 'claro' | 'escuro';
};
type Configuracao = {
idioma: 'pt-BR' | 'en';
};O erro não torna type inferior; ele expressa um contrato fechado. Se quiser formar um terceiro tipo, dê outro nome e use interseção. A decisão é entre abertura intencional e composição explícita.
Uma regra de escolha que cabe na revisão
Minha política inicial é curta: interface para contratos públicos de objetos que podem ser estendidos; type para unions, tuples, funções e composição fechada. Em um objeto interno simples, sigo o padrão já usado no arquivo. Essa política não alega diferença de performance, porque não existe código dos tipos no runtime.
| Necessidade | Escolha | Sinal no código |
|---|---|---|
| Forma de objeto aberta | interface |
extends ou declaration merging é parte consciente do design. |
| Forma de objeto fechada | type ou convenção do projeto |
Ninguém precisa reabrir o mesmo nome. |
| Union de alternativas | type |
Há `A |
| Tuple ou primitivo nomeado | type |
O contrato não é apenas uma forma de objeto. |
| Extensão de biblioteca | Geralmente interface |
A API documenta module ou global augmentation. |
Evite converter centenas de declarações só para impor gosto pessoal. Faça a mudança quando ela revela intenção, remove uma abertura indesejada ou permite representar uma composição que antes estava escondida.
Missão: modele um pedido dos dois jeitos
Crie interface Produto com id, nome e preco. Estenda para
interface ProdutoDigital com urlDownload. Depois crie type StatusPedido
como 'aberto' | 'pago' | 'cancelado' e type ItemPedido como a interseção de
Produto com { quantidade: number }.
interface Produto {
id: number;
nome: string;
preco: number;
}
interface ProdutoDigital extends Produto {
urlDownload: string;
}
type StatusPedido = 'aberto' | 'pago' | 'cancelado';
type ItemPedido = Produto & { quantidade: number };O critério de sucesso tem três provas: um ProdutoDigital completo compila; um
status 'finalizado' falha; um ItemPedido sem quantidade reproduz propriedade
ausente. Explique em voz alta por que interface foi escolhida para os objetos e
type para a union. Se a explicação não mencionar a necessidade do contrato,
revise a escolha antes de seguir.
Perguntas frequentes
Interface e type geram JavaScript diferente?
Posso usar type para descrever um objeto?
Por que duas interfaces com o mesmo nome não dão erro?
Qual padrão devo adotar numa equipe?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Node 24.16.0 com TypeScript 7.0.2, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- TypeScript Handbook — Object Types — typescriptlang.org
- TypeScript Handbook — Everyday Types — typescriptlang.org
- TypeScript Handbook — Declaration Merging — typescriptlang.org


