JavaScript é uma das linguagens de programação mais utilizadas no mundo. Sua sintaxe simples faz com que seja relativamente fácil começar a programar, mas por trás dessa simplicidade existem diversos conceitos e comportamentos que nem sempre são intuitivos.

Tópicos como hoisting, closures, event loop, this, coerção de tipos, prototype chain e arrow functions costumam gerar dúvidas até mesmo entre desenvolvedores experientes.

Além disso, é muito comum que entrevistas técnicas — especialmente para vagas de nível pleno e sênior — tragam perguntas sobre esses conceitos.

Neste artigo, reuni 30 perguntas frequentes de entrevistas sobre JavaScript, acompanhadas de explicações, exemplos práticos e respostas que vão ajudar você a revisar os principais conceitos e se preparar melhor para sua próxima entrevista.

Não deixe de responder o questionário no final da página para testar seu conhecimento.

1. Qual é a diferença entre var, let e const?

Recursovarletconst
EscopoFunçãoBlocoBloco
HoistingSimSimSim
Valor inicialUndefinedNenhum (não inicializado)Nenhum (não inicializado)
Temporal Dead ZoneNãoSimSim
Acesso antes da linhaPermitidoLança ReferenceErrorLança ReferenceError
Pode redeclarar
Pode reatribuir
if (true) {
  var a = 1;
  let b = 2;
  const c = 3;
}

console.log(a); // 1
console.log(b); // ReferenceError
console.log(c); // ReferenceError

2. O que é Hoisting?

Hoisting é o comportamento do JavaScript de mover declarações para o topo do seu escopo antes da execução.

console.log(a);
var a = 5;

Equivalente a:

var a;

console.log(a); // undefined

a = 5;

Com let:

console.log(a);

let a = 5;

Lança:

ReferenceError

por causa da Temporal Dead Zone (TDZ).


3. O que é Temporal Dead Zone?

É o período entre a entrada em um escopo e a declaração de uma variável com let ou const.

{
    console.log(name);
    let name = "John";
}

Saída:

ReferenceError

4. Explique o this

O valor de this depende de como uma função é chamada, não de onde ela foi definida (exceto em arrow functions).

Método regular

const person = {
    name: "John",

    say() {
        console.log(this.name);
    }
};

person.say();

Saída:

John

Função destacada

const fn = person.say;

fn();

Saída (strict mode):

undefined

Arrow function

const person = {
    name: "John",

    say: () => {
        console.log(this.name);
    }
};

person.say();

Saída:

undefined

Arrow functions não criam seu próprio this.


5. Arrow Functions vs Funções Regulares

RecursoFunção RegularArrow Function
Possui seu próprio this✅ Sim❌ Não (herda do escopo externo)
Possui seu próprio arguments✅ Sim❌ Não
Pode ser usada como construtor (new)✅ Sim❌ Não
Possui prototype✅ Sim❌ Não
Melhor para métodos de objetos✅ Sim⚠️ Geralmente não
Melhor para callbacks✅ Sim✅ Sim

Arrow functions herdam o this do escopo ao redor (léxico), em vez de criar o próprio this.

6. O que é Closure?

Uma closure é criada quando uma função interna lembra variáveis do seu escopo externo (léxico), mesmo depois que a função externa terminou sua execução.

Closures são comumente usadas para:

  • Variáveis privadas
  • Encapsulamento de dados
  • Manipuladores de eventos
  • Callbacks
  • React Hooks
function counter() {
    let count = 0;

    return function () {
        count++;
        return count;
    };
}

const increment = counter();

increment(); // 1
increment(); // 2
increment(); // 3

Mesmo que counter() já tenha terminado sua execução, a função interna ainda tem acesso à variável count por causa da closure.


7. Explique o Event Loop

JavaScript é single-threaded, o que significa que ele executa uma parte do código JavaScript por vez.

Para lidar com operações assíncronas, o JavaScript depende do Event Loop.

O fluxo de execução é:

Call Stack
      ↓
Web APIs (Browser / Node.js)
      ↓
Microtask Queue
      ↓
Macrotask Queue

Call Stack

Onde o código síncrono é executado.

console.log("A");
console.log("B");

Executa imediatamente.


Web APIs

Operações como:

  • setTimeout
  • fetch
  • Eventos do DOM

são tratadas fora da Call Stack pelo navegador ou pelo runtime do Node.js.


Microtask Queue

Contém:

  • Callbacks de Promise (.then())
  • await
  • queueMicrotask()

Microtasks sempre têm prioridade maior que macrotasks.


Macrotask Queue

Contém:

  • setTimeout
  • setInterval
  • setImmediate (Node.js)
  • MessageChannel

Exemplo:

console.log(1);

setTimeout(() => console.log(2));

Promise.resolve().then(() => console.log(3));

console.log(4);

Saída:

1
4
3
2

Execução:

  1. console.log(1)
  2. setTimeout é enviado para as Web APIs
  3. O callback da Promise vai para a Microtask Queue
  4. console.log(4)
  5. A Call Stack fica vazia
  6. Executa todas as Microtasks
  7. Executa a primeira Macrotask

É por isso que callbacks de Promise sempre executam antes de setTimeout, mesmo quando o timeout é 0.


8. Qual a diferença entre == e ===?

== realiza coerção de tipo.

1 == "1"

Saída:

true

=== compara valor e tipo.

1 === "1"

Saída:

false

9. Explique Coerção de Tipo

A coerção de tipo (type coercion) é o processo em que o JavaScript converte automaticamente um valor de um tipo para outro ao avaliar uma expressão.

Essa conversão acontece de forma implícita e as regras variam de acordo com o operador utilizado.

Por exemplo, o operador de subtração (-) espera números. Se receber strings que representam números, ele tenta convertê-las automaticamente.

"5" - 1

Saída:

4

O JavaScript converte "5" para o número 5 antes de realizar a operação.

Já o operador de adição (+) possui um comportamento diferente. Se um dos operandos for uma string, ele realiza concatenação.

"5" + 1

Saída:

"51"

Nesse caso, o número 1 é convertido para string e concatenado com "5".

Outro exemplo comum:

true + 1

Saída:

2

O valor true é convertido para 1.

Da mesma forma:

false + 1

Saída:

1

Porque false é convertido para 0.

Por esse motivo, é recomendado utilizar === em vez de ==, pois === compara valor e tipo sem realizar coerção automática.


10. O que são valores Truthy e Falsy?

Sempre que o JavaScript avalia uma condição (if, while, operadores lógicos etc.), ele converte automaticamente o valor para booleano.

Valores que se tornam false são chamados de Falsy.

Todo o resto é Truthy.

Valores Falsy:

false
0
-0
0n
""
null
undefined
NaN

Todo o resto é Truthy.

Exemplos:

if ("Hello") {
    console.log("Runs");
}

Saída:

Runs

Porque strings não vazias são truthy.


if ([]) {
    console.log("Runs");
}

Saída:

Runs

Mesmo um array vazio é truthy.


if ({}) {
    console.log("Runs");
}

Saída:

Runs

Um objeto vazio também é truthy.

Essa é uma pergunta comum em entrevistas porque muitos desenvolvedores esperam que arrays ou objetos vazios sejam falsy.

11. O que é a Prototype Chain?

O JavaScript utiliza herança baseada em protótipos (prototypal inheritance).

Quando você acessa uma propriedade ou método de um objeto, o JavaScript primeiro procura nessa propriedade no próprio objeto.

Se ela não existir, ele procura no protótipo do objeto.

Se ainda não encontrar, continua percorrendo a cadeia de protótipos até chegar em null.

const obj = {};

obj.toString();

Mesmo que obj não defina o método toString(), o JavaScript o encontra em:

Object.prototype

O processo de busca é semelhante a:

obj
  ↓
Object.prototype
  ↓
null

Esse mecanismo permite que objetos compartilhem métodos sem precisar copiá-los para cada instância.


12. Qual a diferença entre null e undefined

undefined

let value;

console.log(value);

Saída

undefined

null

const user = {
    age: null
};

Significa:

Intencionalmente vazio.


13. Por que typeof null retorna "object"?

typeof null;

Saída

"object"

Esse é um bug histórico do JavaScript que foi mantido por compatibilidade com versões antigas da linguagem.


14. Passagem por Valor vs Passagem por Referência

Valores primitivos (number, string, boolean, null, undefined, symbol e bigint) são passados por valor.

let a = 5;
let b = a;

b = 10;

console.log(a);

Saída

5

Alterar b não afeta a, pois cada variável possui sua própria cópia do valor.


Objetos e arrays se comportam de maneira diferente.

const a = {
    x: 1
};

const b = a;

b.x = 2;

console.log(a.x);

Saída

2

As duas variáveis apontam para o mesmo objeto em memória.

Tecnicamente, o JavaScript é sempre pass-by-value.

No caso de objetos, o valor copiado é uma referência para o objeto, fazendo com que ambas as variáveis apontem para a mesma instância.

Essa diferença costuma aparecer em entrevistas de nível pleno e sênior.


15. É possível modificar um objeto declarado com const?

Sim.

const person = {
    age: 20
};

person.age = 30;

Funciona normalmente.

Porém:

person = {};

Lança

TypeError

O const impede a reatribuição da variável, mas não impede a modificação do conteúdo do objeto.


16. Explique Destructuring

Desestruturação de objetos:

const person = {
    name: "John",
    age: 30
};

const { name } = person;

Desestruturação de arrays:

const [a, b] = [1, 2];

A desestruturação permite extrair valores de objetos e arrays de forma mais concisa e legível.


17. Qual a diferença entre Spread e Rest Operator

Spread (...)

O operador Spread expande os elementos de um array ou objeto.

const arr = [1, 2];

const copy = [...arr];

Também pode ser usado para copiar ou combinar objetos.

Rest (...)

O operador Rest agrupa vários argumentos em um único array.

function sum(...numbers) {
    return numbers.reduce((a, b) => a + b);
}

Embora utilizem a mesma sintaxe (...), o comportamento depende do contexto em que são utilizados.


18. Qual a diferença entre debounce vs throttle

Debounce

Executa uma função apenas depois que o usuário para de disparar um evento por um determinado tempo.

Casos de uso:

  • Campo de busca
  • Autocomplete
  • Validação de formulário

Throttle

Executa uma função no máximo uma vez a cada intervalo de tempo.

Casos de uso:

  • Scroll
  • Resize
  • Mouse Move
  • Eventos que acontecem com muita frequência

19. O que é uma Promise?

Uma Promise representa um valor que estará disponível no futuro.

Ela pode estar em três estados:

  • Pending (Pendente)
  • Fulfilled (Resolvida)
  • Rejected (Rejeitada)

Promises são utilizadas para representar operações assíncronas, como chamadas HTTP, leitura de arquivos e consultas a banco de dados.


20. Qual a diferença entre Promise e Async/Await?

Promise:

Uma Promise representa o resultado de uma operação assíncrona que ainda não foi concluída. Ela pode estar em um dos seguintes estados:

  • Pending (Pendente): a operação ainda está em andamento.
  • Fulfilled (Resolvida): a operação foi concluída com sucesso.
  • Rejected (Rejeitada): ocorreu um erro durante a operação.

Para consumir uma Promise, normalmente utilizamos os métodos .then() para tratar o sucesso e .catch() para tratar erros.

fetchData()
    .then(data => console.log(data))
    .catch(console.error);

Async/Await

async/await é uma sintaxe que facilita o trabalho com código assíncrono, tornando-o mais legível e parecido com código síncrono.

Uma função marcada com async sempre retorna uma Promise, e o operador await faz com que a execução da função espere o resultado dessa Promise antes de continuar.

try {
    const data = await fetchData();
} catch (error) {
    console.error(error);
}

Internamente, async/await continua utilizando Promises. A principal diferença é que ele evita longas cadeias de .then() e .catch(), deixando o código mais limpo e fácil de entender.


21. Qual a diferença entre map() e forEach()?

map()

  • Retorna um novo array.
  • Deve ser utilizado para transformar dados.
const doubled = numbers.map(n => n * 2);

forEach()

  • Retorna undefined.
  • Deve ser utilizado para efeitos colaterais (side effects), como imprimir informações, modificar variáveis externas ou fazer chamadas de API.
numbers.forEach(n => console.log(n));

Em geral:

  • Use map() quando precisar retornar um novo array.
  • Use forEach() quando apenas quiser executar alguma ação para cada elemento.

22. Qual a diferença entre filter() e find()?

filter()

Retorna um novo array contendo todos os elementos que satisfazem a condição.

const numbers = [1, 2, 3, 4, 5];

const even = numbers.filter(n => n % 2 === 0);

console.log(even);

Saída

[2, 4]

find()

Retorna apenas o primeiro elemento que satisfaz a condição.

const numbers = [1, 2, 3, 4, 5];

const even = numbers.find(n => n % 2 === 0);

console.log(even);

Saída

2

Se nenhum elemento for encontrado, find() retorna:

undefined

23. Qual a diferença entre Shallow Copy e Deep Copy?

Shallow Copy

Cria uma cópia apenas do primeiro nível do objeto.

const copy = {
    ...obj
};

Objetos aninhados continuam compartilhando a mesma referência.

const original = {
    address: {
        city: "São Paulo"
    }
};

const copy = {
    ...original
};

copy.address.city = "Rio de Janeiro";

console.log(original.address.city);

Saída

Rio de Janeiro

Deep Copy

Cria uma cópia completamente independente.

const copy = structuredClone(obj);

Agora alterações na cópia não afetam o objeto original.


24. O que é Optional Chaining (?.)?

O operador de encadeamento opcional permite acessar propriedades profundas sem precisar verificar manualmente se cada nível existe.

user?.address?.city

Sem Optional Chaining:

if (user && user.address) {
    console.log(user.address.city);
}

Com Optional Chaining, caso alguma propriedade seja null ou undefined, o resultado será simplesmente:

undefined

Em vez de lançar:

Cannot read property 'city' of undefined

25. Qual a diferença entre ?? e ||?

??

O operador ?? retorna o valor da direita apenas quando o valor da esquerda é:

  • null
  • undefined
const value = input ?? "default";

Exemplos:

null ?? "default"

Resultado

"default"
undefined ?? "default"

Resultado

"default"
0 ?? "default"

Resultado

0

||

O operador || também substitui:

  • 0
  • false
  • ""

Por isso ?? costuma ser a melhor escolha quando queremos apenas tratar valores ausentes.


26. O que é Escopo Léxico (Lexical Scope)?

No JavaScript, as variáveis são resolvidas com base em onde uma função foi definida, e não onde ela é chamada.

const name = "global";

function outer() {
    const name = "local";

    function inner() {
        console.log(name);
    }

    return inner;
}

outer()();

Saída

local

Mesmo que inner() seja executada em outro lugar, ela continua enxergando o escopo onde foi criada.

Esse comportamento é a base das closures.


27. O que é uma IIFE?

IIFE significa Immediately Invoked Function Expression.

É uma função que é criada e executada imediatamente.

(function () {
    console.log("Executa imediatamente");
})();

Antes da chegada do let e const, IIFEs eram muito utilizadas para criar escopos privados e evitar poluir o escopo global.


28. Qual a diferença entre call, apply e bind?

Todos permitem definir o valor de this.

function greet() {
    console.log(this.name);
}

const person = {
    name: "Alice"
};

call()

Executa imediatamente.

greet.call(person);

Saída

Alice

Também aceita argumentos separados por vírgula.

fn.call(thisArg, arg1, arg2);

apply()

Também executa imediatamente.

A diferença é que recebe os argumentos em um array.

Math.max.apply(null, [1, 2, 3]);

Formato:

fn.apply(thisArg, [arg1, arg2]);

bind()

Não executa imediatamente.

Retorna uma nova função com o this permanentemente associado.

const bound = greet.bind(person);

bound();

É muito utilizado em callbacks e event handlers.


29. Por que [] == false retorna true?

[] == false;

Resultado

true

Isso acontece porque o operador == realiza coerção de tipo.

O processo é aproximadamente:

[] → ""
"" → 0
false → 0

0 == 0

Por isso o resultado final é:

true

Esse é um ótimo exemplo de por que normalmente preferimos utilizar:

===

em vez de:

==

30. Qual a diferença entre Function Declaration e Function Expression?

Function Declaration

É içada (hoisted) juntamente com sua implementação.

sayHello();

function sayHello() {
    console.log("Hello");
}

Funciona normalmente.


Function Expression

A variável é içada, mas permanece na Temporal Dead Zone até sua inicialização.

sayHello();

const sayHello = function () {};

Resultado

ReferenceError

Resumo

CaracterísticaFunction DeclarationFunction Expression
Hoisting da implementação✅ Sim❌ Não
Pode ser chamada antes da declaração✅ Sim❌ Não
Pode ser anônima❌ Geralmente não✅ Sim
Pode ser atribuída a variáveis✅ Sim✅ Sim

Hoje em dia, Function Expressions e Arrow Functions são mais comuns em aplicações modernas, especialmente em React e Node.js, enquanto Function Declarations continuam sendo bastante utilizadas para funções utilitárias e APIs públicas.


Teste seus conhecimentos em Javascript