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?
| Recurso | var | let | const |
|---|---|---|---|
| Escopo | Função | Bloco | Bloco |
| Hoisting | Sim | Sim | Sim |
| Valor inicial | Undefined | Nenhum (não inicializado) | Nenhum (não inicializado) |
| Temporal Dead Zone | Não | Sim | Sim |
| Acesso antes da linha | Permitido | Lança ReferenceError | Lanç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
| Recurso | Função Regular | Arrow 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:
setTimeoutfetch- 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()) awaitqueueMicrotask()
Microtasks sempre têm prioridade maior que macrotasks.
Macrotask Queue
Contém:
setTimeoutsetIntervalsetImmediate(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:
console.log(1)setTimeouté enviado para as Web APIs- O callback da Promise vai para a Microtask Queue
console.log(4)- A Call Stack fica vazia
- Executa todas as Microtasks
- 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 é:
nullundefined
const value = input ?? "default";
Exemplos:
null ?? "default"
Resultado
"default"
undefined ?? "default"
Resultado
"default"
0 ?? "default"
Resultado
0
||
O operador || também substitui:
0false""
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ística | Function Declaration | Function 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.