Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIntermediáriocódigo testado

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.

Rodolfo Mori5 min de leitura

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:

ts
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);
Mouse Teclado

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:

ts
interface Identificavel {
  id: number;
}

const produtoCompleto = {
  id: 7,
  nome: 'Headset',
  preco: 320,
};

function mostrarId(item: Identificavel): void {
  console.log(`ID: ${item.id}`);
}

mostrarId(produtoCompleto);
ID: 7

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:

ts
interface Produto {
  nome: string;
  preco: number;
}

const produto: Produto = {
  nome: 'Webcam',
};
error TS2741: Property 'preco' is missing in type '{ nome: string; }' but required in type 'Produto'.

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:

ts
interface Pessoa {
  nome: string;
}

interface Cliente extends Pessoa {
  pontos: number;
}

const ana: Cliente = {
  nome: 'Ana',
  pontos: 120,
};

console.log(`${ana.nome}: ${ana.pontos} pontos`);
Ana: 120 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:

ts
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}>`);
Bia <bia@example.com>

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:

ts
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'));
0009 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:

ts
type Coordenada = [latitude: number, longitude: number];

const loja: Coordenada = [-23.561, -46.656];
const [latitude, longitude] = loja;

console.log(latitude, longitude);
-23.561 -46.656

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:

ts
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);
escuro pt-BR

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:

ts
type Configuracao = {
  tema: 'claro' | 'escuro';
};

type Configuracao = {
  idioma: 'pt-BR' | 'en';
};
error TS2300: Duplicate identifier 'Configuracao'.

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

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

  • typescript
  • interface
  • type alias
  • tipos estruturais
  • declaration merging

Perguntas frequentes

Interface e type geram JavaScript diferente?
Não. Os dois existem durante a checagem e são removidos na compilação. A escolha muda como você expressa e compõe contratos, não a velocidade do programa executado.
Posso usar type para descrever um objeto?
Sim. Type aliases descrevem objetos, e na maior parte dos objetos simples o resultado prático se parece com interface. As diferenças aparecem em abertura, extensão e formas que não são apenas objetos.
Por que duas interfaces com o mesmo nome não dão erro?
Porque interfaces participam de declaration merging. O TypeScript combina as declarações compatíveis. Esse comportamento ajuda a estender APIs, mas também pode esconder uma abertura que você não queria.
Qual padrão devo adotar numa equipe?
Use interface para contratos de objeto que devem permanecer abertos e type para unions, tuples e composições fechadas. Para objetos simples, escolha uma convenção e documente as exceções.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

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

  1. TypeScript Handbook — Object Types — typescriptlang.org
  2. TypeScript Handbook — Everyday Types — typescriptlang.org
  3. TypeScript Handbook — Declaration Merging — typescriptlang.org

Continue por aqui