15 Najtrudniejszych Pytań Rekrutacyjnych z JavaScript

Sławomir Plamowski 20 min czytania
frontend interview-questions javascript programowanie rekrutacja

Dlaczego ten kod wypisuje trzy razy liczbę 3 zamiast 0, 1, 2? To klasyczne pytanie z pętlą for i setTimeout potrafi zaskakiwać nawet programistów z wieloletnim doświadczeniem. Odpowiedź wymaga połączenia wiedzy o closure, scope i asynchroniczności - a właśnie takie pytania najczęściej padają na rozmowach rekrutacyjnych.

Najtrudniejsze pytania z JavaScript nie dotyczą egzotycznych API czy obscurycznych edge case'ów. Dotyczą fundamentów języka - rzeczy, które używamy codziennie, ale rzadko się nad nimi zastanawiamy.

Poniżej znajdziesz 15 pytań, które konsekwentnie sprawiają trudności nawet doświadczonym programistom. Nie są to pytania-zagadki ani gotcha questions wymyślone żeby kogoś przyłapać. To pytania, które naprawdę weryfikują zrozumienie JavaScript na głębokim poziomie.

1. Closure - Fundament, Który Wszyscy Używają, Ale Niewielu Rozumie

Zacznijmy od closure, bo to temat, który pojawia się na praktycznie każdej rozmowie rekrutacyjnej. Co ciekawe, większość programistów używa closure codziennie - w React hooks, w event handlerach, w callbackach - ale gdy przychodzi do wyjaśnienia mechanizmu, zaczynają się problemy.

Odpowiedź w 30 sekund

Gdy rekruter pyta "Co to jest closure?", moja odpowiedź brzmi tak:

Closure to funkcja, która 'pamięta' zmienne z zakresu, w którym została utworzona, nawet gdy ten zakres już nie istnieje. Funkcja wewnętrzna ma dostęp do zmiennych funkcji zewnętrznej nawet po jej zakończeniu. Używamy tego do tworzenia prywatnych zmiennych i zachowania stanu między wywołaniami.

Poczekaj na reakcję rekrutera. Jeśli chce więcej szczegółów, kontynuuj.

Odpowiedź w 2 minuty

Technicznie, closure to połączenie funkcji i jej środowiska leksykalnego - czyli miejsca w kodzie, gdzie została zdefiniowana. W JavaScript każda funkcja tworzy closure. Kluczowe jest to, że zmienne nie są kopiowane do closure - zachowywana jest referencja do nich.

Pokażę na przykładzie:

function createCounter() {
    let count = 0;  // ta zmienna jest 'zamknięta' w closure

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

const counter = createCounter();
console.log(counter());  // 1
console.log(counter());  // 2
console.log(counter());  // 3

// Każde wywołanie createCounter tworzy nowy, niezależny closure
const anotherCounter = createCounter();
console.log(anotherCounter());  // 1 (nie 4!)

Zwróć uwagę, że zmienna count nie jest dostępna z zewnątrz - nie możemy napisać counter.count ani createCounter.count. To właśnie dlatego closure jest podstawą wzorca modułu i emulacji prywatnych zmiennych w JavaScript.

Klasyczny Problem: Pętla i setTimeout

Niemal na pewno spotkasz się z tym pytaniem lub jego wariacją. Rekruter pokazuje kod:

for (var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

I pyta: "Co zostanie wypisane i dlaczego?"

Wielu kandydatów odpowiada "0, 1, 2". Błąd. Wypisane zostanie "3, 3, 3".

Dlaczego? Bo var ma zakres funkcyjny, nie blokowy. Istnieje tylko jedna zmienna i dla całej pętli. Gdy callbacki setTimeout się wykonują (po sekundzie), pętla już dawno się zakończyła, a i ma wartość 3. Wszystkie trzy funkcje closure odwołują się do tej samej zmiennej.

Rozwiązanie z let:

for (let i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}
// Wypisuje: 0, 1, 2

Z let każda iteracja pętli tworzy nowy binding dla i. Każdy callback ma swoje własne i.

Rozwiązanie z IIFE (przed ES6):

for (var i = 0; i < 3; i++) {
    (function(j) {
        setTimeout(function() {
            console.log(j);
        }, 1000);
    })(i);
}
// Wypisuje: 0, 1, 2

IIFE tworzy nowy zakres dla każdej iteracji, "przechwytując" aktualną wartość i w parametrze j.

2. Hoisting - Dlaczego JavaScript Wydaje Się Czytać Kod Od Końca

Hoisting to mechanizm, który sprawia, że możesz użyć zmiennej lub funkcji przed jej deklaracją w kodzie. Brzmi magicznie, ale gdy zrozumiesz jak działa, przestanie być zagadką.

Odpowiedź w 30 sekund

Hoisting to przenoszenie deklaracji zmiennych i funkcji na początek ich zakresu podczas fazy kompilacji. Ale uwaga - przenoszone są tylko deklaracje, nie inicjalizacje. Dla var oznacza to, że zmienna istnieje od początku funkcji z wartością undefined. Dla let i const deklaracja jest podniesiona, ale zmienna jest w 'temporal dead zone' do momentu definicji.

Odpowiedź w 2 minuty

Pokażę różnicę na kodzie:

console.log(a);  // undefined (nie błąd!)
var a = 5;

console.log(b);  // ReferenceError: Cannot access 'b' before initialization
let b = 10;

Dla interpretera pierwszy fragment wygląda tak:

var a;           // deklaracja przeniesiona na górę
console.log(a);  // undefined
a = 5;           // inicjalizacja zostaje na miejscu

Funkcje deklarowane słowem function są hoistowane w całości - razem z ciałem:

greet();  // "Hello!" - działa!

function greet() {
    console.log("Hello!");
}

Ale wyrażenia funkcyjne zachowują się jak zmienne:

greet();  // TypeError: greet is not a function

var greet = function() {
    console.log("Hello!");
};

Na Co Zwracają Uwagę Rekruterzy

Kandydat, który rozumie hoisting, nie napisze kodu polegającego na tym mechanizmie. Deklaracje powinny być na początku zakresu - nie dlatego, że muszą, ale dlatego, że to czytelniejsze. Dobry programista zna hoisting, żeby debugować cudzy kod i rozumieć dziwne zachowania, ale w swoim kodzie unika niejednoznaczności.

3. Słowo Kluczowe this - Najbardziej Niespójny Element JavaScript

Jeśli miałbym wskazać jeden temat, który powoduje najwięcej zamieszania w JavaScript, byłoby to this. W innych językach this zawsze wskazuje na obiekt, w którym jesteśmy. W JavaScript wartość this zależy od sposobu wywołania funkcji.

Odpowiedź w 30 sekund

this w JavaScript nie jest ustalane przy definicji funkcji, tylko przy jej wywołaniu. W metodzie obiektu this to obiekt. W zwykłej funkcji to window lub undefined w strict mode. W funkcji strzałkowej this jest dziedziczone z zakresu zewnętrznego. Można też ustawić this jawnie przez call, apply lub bind.

Pięć Kontekstów this

Pozwól, że pokażę wszystkie przypadki:

// 1. Metoda obiektu - this to obiekt
const user = {
    name: 'Anna',
    greet() {
        console.log(`Hello, ${this.name}`);
    }
};
user.greet();  // "Hello, Anna"

// 2. Zwykła funkcja - this to window (lub undefined w strict mode)
function showThis() {
    console.log(this);
}
showThis();  // Window {...} lub undefined

// 3. Funkcja strzałkowa - this z zakresu zewnętrznego
const user2 = {
    name: 'Bartek',
    greet: () => {
        console.log(this.name);  // this to Window, nie user2!
    },
    greetDelayed() {
        setTimeout(() => {
            console.log(this.name);  // this to user2 - dziedziczone!
        }, 100);
    }
};

// 4. Konstruktor (new) - this to nowy obiekt
function Person(name) {
    this.name = name;
}
const person = new Person('Celina');
console.log(person.name);  // "Celina"

// 5. call/apply/bind - this ustawione jawnie
function introduce() {
    console.log(`I'm ${this.name}`);
}
const dev = { name: 'Developer' };
introduce.call(dev);  // "I'm Developer"

Klasyczny Problem: Zagubiony Kontekst

Rekruter pokazuje kod:

const user = {
    name: 'Anna',
    greet() {
        console.log(`Hello, ${this.name}`);
    }
};

const greetFn = user.greet;
greetFn();  // Co zostanie wypisane?

Odpowiedź: "Hello, undefined" (lub błąd w strict mode).

Gdy przypisujemy metodę do zmiennej, tracimy kontekst obiektu. greetFn to teraz zwykła funkcja, wywołana bez kontekstu. Rozwiązania:

// Użyj bind
const greetFn = user.greet.bind(user);
greetFn();  // "Hello, Anna"

// Lub wywołaj z kontekstem
const greetFn = user.greet;
greetFn.call(user);  // "Hello, Anna"

// Lub użyj funkcji strzałkowej w definicji
const user = {
    name: 'Anna',
    greet: () => {  // Uwaga: teraz this to Window!
        console.log(`Hello, ${this.name}`);
    }
};

Ten ostatni przykład to pułapka - funkcja strzałkowa jako metoda obiektu to prawie zawsze błąd, bo this będzie z zakresu zewnętrznego (globalnego), nie z obiektu.

4. Różnica Między == i ===

To pytanie wydaje się proste, ale rekruterzy lubią je rozwijać o edge case'y i praktyczne zastosowania.

Odpowiedź w 30 sekund

== porównuje wartości po konwersji typów - nazywamy to loose equality. === porównuje wartości i typy bez konwersji - strict equality. Zawsze używaj ===, chyba że świadomie chcesz konwersji. Jedyny wyjątek: x == null do sprawdzenia zarówno null jak i undefined.

Tabela Niespodzianek

Oto przypadki, które zaskakują nawet doświadczonych programistów:

Wyrażenie == === Dlaczego?
1 == '1' true false String konwertowany do Number
0 == false true false Boolean konwertowany do Number
null == undefined true false Specjalna reguła w specyfikacji
NaN == NaN false false NaN nie jest równe niczemu!
[] == false true false [] -> '' -> 0, false -> 0
[] == ![] true false ![] to false, [] to 0

Ten ostatni przypadek, [] == ![] zwracające true, to ulubione pytanie rekruterów. Wyjaśnienie: ![] to false (bo tablica jest truthy). Następnie [] == false konwertuje obie strony do liczb: [] staje się pustym stringiem, który staje się 0, a false też staje się 0. 0 == 0 to true.

5. Promise i Async/Await - Serce Nowoczesnego JavaScript

Asynchroniczność to serce JavaScript w przeglądarce i Node.js. Rekruterzy sprawdzają nie tylko czy znasz składnię, ale czy rozumiesz jak to działa pod spodem.

Odpowiedź w 30 sekund

Promise to obiekt reprezentujący przyszły wynik operacji asynchronicznej - może być pending, fulfilled lub rejected. Async/await to składnia zbudowana na Promise, pozwalająca pisać kod asynchroniczny jak synchroniczny. Funkcja async zawsze zwraca Promise. await wstrzymuje wykonanie do rozwiązania Promise, ale nie blokuje głównego wątku.

Odpowiedź w 2 minuty

// Promise - tradycyjna składnia
function fetchUser(id) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (id > 0) {
                resolve({ id, name: 'User ' + id });
            } else {
                reject(new Error('Invalid ID'));
            }
        }, 1000);
    });
}

// Użycie z .then/.catch
fetchUser(1)
    .then(user => console.log(user))
    .catch(error => console.error(error));

// To samo z async/await
async function getUser(id) {
    try {
        const user = await fetchUser(id);
        console.log(user);
    } catch (error) {
        console.error(error);
    }
}

Klasyczny Problem: Kolejność Wykonania

Rekruter pokazuje kod i pyta o kolejność wypisania:

console.log('1');

setTimeout(() => console.log('2'), 0);

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

console.log('4');

Odpowiedź: 1, 4, 3, 2

Dlaczego nie 1, 4, 2, 3? Bo Promise callbacks (microtasks) mają wyższy priorytet niż setTimeout callbacks (macrotasks). Event loop najpierw opróżnia microtask queue przed wzięciem kolejnego task z task queue.

Równoległe vs Sekwencyjne Wywołania

To częsty błąd - sekwencyjne await tam, gdzie można równolegle:

// ZŁE - sekwencyjne, wolne
async function slow() {
    const user = await fetchUser(1);      // czeka 1s
    const posts = await fetchPosts(1);    // czeka kolejną 1s
    return { user, posts };               // łącznie 2s
}

// DOBRE - równoległe, szybkie
async function fast() {
    const [user, posts] = await Promise.all([
        fetchUser(1),
        fetchPosts(1)
    ]);
    return { user, posts };               // łącznie 1s
}

6. Event Loop - Jak JavaScript Robi Wiele Rzeczy Naraz

JavaScript jest jednowątkowy, ale obsługuje tysiące równoczesnych operacji. Jak to możliwe? Dzięki Event Loop.

Odpowiedź w 30 sekund

Event Loop to mechanizm zarządzający wykonaniem kodu w JavaScript. Przetwarza synchroniczny kod z call stack, następnie microtasks (Promise callbacks), a potem macrotasks (setTimeout, I/O). Jest jednowątkowy, ale dzięki asynchroniczności nie blokuje - operacje I/O są delegowane do systemu, a wyniki wracają przez callbacki.

Wizualizacja Cyklu

Wyobraź sobie Event Loop jako pętlę nieskończoną:

while (true) {
    1. Wykonaj wszystko z Call Stack
    2. Wykonaj WSZYSTKIE microtasks (Promise.then, queueMicrotask)
    3. Opcjonalnie: Renderuj UI (co ~16ms)
    4. Weź JEDEN task z Task Queue (setTimeout, setInterval, I/O)
    5. Wróć do kroku 1
}

Kluczowa różnica: wszystkie microtasks vs jeden macrotask. Dlatego ten kod może zablokować przeglądarkę:

function blockForever() {
    Promise.resolve().then(blockForever);
}
blockForever();  // Nieskończona pętla microtasks!

Przeglądarka nigdy nie dojdzie do renderowania ani obsługi kliknięć.

7. Prototypy i Dziedziczenie

ES6 dało nam klasy, ale pod spodem nadal działają prototypy. Rekruterzy sprawdzają, czy rozumiesz ten mechanizm.

Odpowiedź w 30 sekund

W JavaScript obiekty dziedziczą przez łańcuch prototypów. Każdy obiekt ma ukryty link [[Prototype]] do innego obiektu. Gdy szukamy właściwości, JavaScript sprawdza obiekt, potem jego prototyp, potem prototyp prototypu, aż do null. Klasy ES6 to składniowy cukier na tę mechanikę.

Jak To Działa

// Każda funkcja ma właściwość prototype
function Animal(name) {
    this.name = name;
}
Animal.prototype.speak = function() {
    console.log(`${this.name} makes a sound`);
};

const dog = new Animal('Rex');
dog.speak();  // "Rex makes a sound"

// dog nie ma metody speak - jest ona w Animal.prototype
console.log(dog.hasOwnProperty('speak'));  // false
console.log(dog.hasOwnProperty('name'));   // true

// Łańcuch: dog -> Animal.prototype -> Object.prototype -> null

Klasy ES6 vs Prototypy

// To samo z klasą ES6
class Animal {
    constructor(name) {
        this.name = name;
    }
    speak() {
        console.log(`${this.name} makes a sound`);
    }
}

// Pod spodem nadal prototypy!
console.log(typeof Animal);  // "function"
console.log(Animal.prototype.speak);  // [Function: speak]

8. Typy Wartościowe vs Referencyjne

To fundamentalne rozróżnienie, które wpływa na każdy aspekt pracy z JavaScript.

Odpowiedź w 30 sekund

Typy prymitywne (number, string, boolean, null, undefined, symbol, bigint) są kopiowane przez wartość. Obiekty, tablice i funkcje są przekazywane przez referencję - kopiowana jest tylko referencja do tego samego miejsca w pamięci. Dlatego modyfikacja obiektu w funkcji zmienia oryginalny obiekt.

Kod Demonstrujący Różnicę

// Prymitywy - kopiowanie wartości
let a = 5;
let b = a;
b = 10;
console.log(a);  // 5 - niezmienione

// Obiekty - kopiowanie referencji
let obj1 = { value: 5 };
let obj2 = obj1;
obj2.value = 10;
console.log(obj1.value);  // 10 - zmienione!

// Jak skopiować obiekt?
let obj3 = { ...obj1 };           // Shallow copy
let obj4 = JSON.parse(JSON.stringify(obj1));  // Deep copy (uwaga: gubi funkcje i daty)
let obj5 = structuredClone(obj1); // Deep copy (nowoczesne przeglądarki)

Klasyczny Problem: Funkcje i Obiekty

function modifyPrimitive(x) {
    x = x + 1;
}

function modifyObject(obj) {
    obj.value = obj.value + 1;
}

let num = 5;
modifyPrimitive(num);
console.log(num);  // 5 - niezmienione

let obj = { value: 5 };
modifyObject(obj);
console.log(obj.value);  // 6 - zmienione!

9. Spread i Rest Operator

Te trzy kropki ... robią różne rzeczy w zależności od kontekstu. Rekruterzy lubią sprawdzać, czy kandydat rozumie obie strony.

Odpowiedź w 30 sekund

Spread ... 'rozpyla' elementy tablicy lub właściwości obiektu - używamy go do kopiowania, łączenia, przekazywania argumentów. Rest ... 'zbiera' wiele elementów w jeden - używamy go w parametrach funkcji i destrukturyzacji. Ta sama składnia, przeciwne działanie.

Spread w Akcji

// Kopiowanie tablicy
const arr = [1, 2, 3];
const copy = [...arr];

// Łączenie tablic
const merged = [...arr, 4, 5, ...arr];  // [1,2,3,4,5,1,2,3]

// Kopiowanie obiektu
const user = { name: 'Anna', age: 25 };
const userCopy = { ...user, age: 26 };  // nadpisuje age

// Argumenty funkcji
const numbers = [1, 2, 3];
console.log(Math.max(...numbers));  // 3

Rest w Akcji

// W parametrach funkcji
function sum(...numbers) {
    return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4);  // 10

// W destrukturyzacji tablicy
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first = 1, second = 2, rest = [3, 4, 5]

// W destrukturyzacji obiektu
const { name, ...others } = { name: 'Anna', age: 25, city: 'Warsaw' };
// name = 'Anna', others = { age: 25, city: 'Warsaw' }

10. var vs let vs const

Proste pytanie, ale rekruterzy lubią wchodzić w szczegóły temporal dead zone i re-deklaracji.

Odpowiedź w 30 sekund

var ma zakres funkcyjny, jest hoistowana z undefined, pozwala na re-deklarację. let i const mają zakres blokowy, są w temporal dead zone do definicji, nie pozwalają na re-deklarację. const dodatkowo nie pozwala na re-przypisanie, ale nie czyni obiektu niemutowalnym.

Kluczowe Różnice

// var - function scope
function example() {
    if (true) {
        var x = 1;
    }
    console.log(x);  // 1 - dostępne!
}

// let - block scope
function example2() {
    if (true) {
        let y = 1;
    }
    console.log(y);  // ReferenceError
}

// const - nie można re-przypisać
const obj = { a: 1 };
obj.a = 2;          // OK - modyfikacja właściwości
obj = { b: 2 };     // TypeError - re-przypisanie

// var pozwala na re-deklarację
var a = 1;
var a = 2;  // OK

// let nie pozwala
let b = 1;
let b = 2;  // SyntaxError

11. Funkcje Strzałkowe vs Zwykłe Funkcje

Funkcje strzałkowe to nie tylko krótsza składnia. Mają fundamentalne różnice w zachowaniu.

Odpowiedź w 30 sekund

Funkcje strzałkowe nie mają własnego this, arguments, super ani new.target - dziedziczą z zakresu zewnętrznego. Nie można ich używać jako konstruktorów. Nie mają właściwości prototype. Są idealne do callbacków i krótkich wyrażeń, ale nie jako metody obiektów.

Kiedy Używać Której

// Strzałkowa w callbacku - idealna
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);

// Strzałkowa w setTimeout - idealna (dziedziczy this)
const obj = {
    name: 'Anna',
    greetLater() {
        setTimeout(() => {
            console.log(this.name);  // 'Anna' - this z greetLater
        }, 100);
    }
};

// Strzałkowa jako metoda - ZŁE
const user = {
    name: 'Anna',
    greet: () => console.log(this.name)  // undefined!
};

// Zwykła funkcja jako konstruktor - OK
function Person(name) {
    this.name = name;
}
new Person('Anna');

// Strzałkowa jako konstruktor - BŁĄD
const Person2 = (name) => { this.name = name; };
new Person2('Anna');  // TypeError

12. Object.freeze() vs Object.seal() vs const

Trzy sposoby na "zamrożenie" rzeczy w JavaScript, każdy robi co innego.

Odpowiedź w 30 sekund

const zapobiega re-przypisaniu zmiennej, ale nie chroni zawartości obiektu. Object.seal() blokuje dodawanie i usuwanie właściwości, ale pozwala na modyfikację istniejących. Object.freeze() całkowicie blokuje obiekt - żadnych zmian. Wszystkie są płytkie - zagnieżdżone obiekty nie są chronione.

Porównanie w Kodzie

// const - tylko binding zmiennej
const obj1 = { a: 1 };
obj1.a = 2;      // OK
obj1.b = 3;      // OK
obj1 = {};       // TypeError

// Object.seal - struktura zamknięta, wartości modyfikowalne
const obj2 = { a: 1 };
Object.seal(obj2);
obj2.a = 2;      // OK
obj2.b = 3;      // Ignorowane (lub TypeError w strict mode)
delete obj2.a;   // Ignorowane

// Object.freeze - całkowicie zamrożony
const obj3 = { a: 1 };
Object.freeze(obj3);
obj3.a = 2;      // Ignorowane
obj3.b = 3;      // Ignorowane

// Ale uwaga - płytkie!
const obj4 = { nested: { a: 1 } };
Object.freeze(obj4);
obj4.nested.a = 2;  // OK - zagnieżdżony obiekt nie jest zamrożony!

13. Destructuring - Więcej Niż Składniowy Cukier

Destrukturyzacja wydaje się prosta, ale ma wiele niuansów, które rekruterzy lubią sprawdzać.

Odpowiedź w 30 sekund

Destrukturyzacja pozwala wyciągać wartości z tablic i obiektów do zmiennych. Obsługuje wartości domyślne, zmianę nazw, zagnieżdżenie i rest operator. W funkcjach pozwala na eleganckie definiowanie nazwanych parametrów z domyślnymi wartościami.

Zaawansowane Przypadki

// Wartości domyślne
const { name = 'Anonymous', age = 0 } = { name: 'Anna' };
// name = 'Anna', age = 0

// Zmiana nazwy
const { name: userName } = { name: 'Anna' };
// userName = 'Anna'

// Zagnieżdżenie
const { address: { city } } = { address: { city: 'Warsaw' } };
// city = 'Warsaw'

// W parametrach funkcji
function createUser({ name, age = 18, role = 'user' } = {}) {
    return { name, age, role };
}
createUser({ name: 'Anna' });  // { name: 'Anna', age: 18, role: 'user' }
createUser();                   // { name: undefined, age: 18, role: 'user' }

// Zamiana wartości bez zmiennej tymczasowej
let a = 1, b = 2;
[a, b] = [b, a];
// a = 2, b = 1

14. Null vs Undefined - Subtelna Różnica

Oba oznaczają "brak wartości", ale mają różne znaczenia semantyczne.

Odpowiedź w 30 sekund

undefined oznacza, że zmienna została zadeklarowana, ale nie zainicjalizowana - lub właściwość nie istnieje. null to jawne przypisanie 'brak wartości' przez programistę. Używaj null gdy chcesz wyrazić intencję, undefined zostawiaj systemowi. Porównanie: null == undefined jest true, ale null === undefined jest false.

Kiedy Który

// undefined - brak inicjalizacji
let x;
console.log(x);  // undefined

// undefined - brak właściwości
const obj = {};
console.log(obj.name);  // undefined

// undefined - brak argumentu
function greet(name) {
    console.log(name);
}
greet();  // undefined

// null - jawnie brak wartości
const user = {
    name: 'Anna',
    manager: null  // jawnie: nie ma managera
};

// Sprawdzanie obu naraz
if (value == null) {
    // value jest null LUB undefined
}

// typeof quirk
console.log(typeof undefined);  // "undefined"
console.log(typeof null);       // "object" - historyczny błąd JS!

15. Metody Tablicowe - map, filter, reduce, find, some, every

Rekruterzy często proszą o implementację jednej z tych metod lub rozwiązanie problemu z ich użyciem.

Odpowiedź w 30 sekund

map transformuje każdy element, filter wybiera elementy spełniające warunek, reduce akumuluje tablicę do jednej wartości. find zwraca pierwszy pasujący element, some sprawdza czy którykolwiek pasuje, every sprawdza czy wszystkie pasują. Żadna z nich nie modyfikuje oryginalnej tablicy.

Praktyczne Przykłady

const users = [
    { name: 'Anna', age: 25, active: true },
    { name: 'Bartek', age: 30, active: false },
    { name: 'Celina', age: 28, active: true }
];

// map - transformacja
const names = users.map(u => u.name);
// ['Anna', 'Bartek', 'Celina']

// filter - selekcja
const activeUsers = users.filter(u => u.active);
// [{ name: 'Anna', ... }, { name: 'Celina', ... }]

// reduce - akumulacja
const totalAge = users.reduce((sum, u) => sum + u.age, 0);
// 83

// find - pierwszy pasujący
const bartek = users.find(u => u.name === 'Bartek');
// { name: 'Bartek', age: 30, active: false }

// some - czy którykolwiek
const hasInactive = users.some(u => !u.active);
// true

// every - czy wszystkie
const allActive = users.every(u => u.active);
// false

Klasyczny Problem: Implementacja reduce

Rekruter może poprosić o implementację reduce:

function myReduce(array, callback, initialValue) {
    let accumulator = initialValue !== undefined ? initialValue : array[0];
    let startIndex = initialValue !== undefined ? 0 : 1;

    for (let i = startIndex; i < array.length; i++) {
        accumulator = callback(accumulator, array[i], i, array);
    }

    return accumulator;
}

// Test
const sum = myReduce([1, 2, 3, 4], (acc, val) => acc + val, 0);
console.log(sum);  // 10

Na Co Rekruterzy Naprawdę Zwracają Uwagę

Po przeprowadzeniu setek rozmów rekrutacyjnych mogę powiedzieć, że nie chodzi tylko o znajomość odpowiedzi. Sprawdzam:

Czy kandydat potrafi wyjaśnić "dlaczego", nie tylko "co". Każdy może zapamiętać, że closure "zamyka" zmienne. Dobry programista wyjaśni mechanizm środowiska leksykalnego i praktyczne zastosowania.

Czy kandydat przyznaje się, gdy czegoś nie wie. Nikt nie zna wszystkiego. Odpowiedź "Nie jestem pewien, ale podejrzewam że..." jest lepsza niż zgadywanie.

Czy kandydat myśli o edge case'ach. Gdy pytam o ===, czy wspomni o NaN !== NaN? Gdy pytam o const, czy wspomni, że nie czyni obiektu niemutowalnym?

Czy kandydat pisze czytelny kod. Nawet na rozmowie - czy używa sensownych nazw zmiennych, czy formatuje kod.

Praktyka na Koniec

Zanim pójdziesz na rozmowę, upewnij się, że potrafisz odpowiedzieć na te pytania bez googlowania:

  1. Napisz funkcję debounce(fn, delay) używając closure.
  2. Wyjaśnij, dlaczego [] == ![] zwraca true.
  3. Napisz funkcję, która głęboko klonuje obiekt (bez JSON.parse/stringify).
  4. Jaka jest różnica między for...in a for...of?
  5. Jak działa garbage collection w JavaScript?
  6. Czym różni się Object.create(null) od {}?

Jeśli potrafisz na nie odpowiedzieć bez wahania, jesteś gotowy na większość rozmów rekrutacyjnych z JavaScript.

Zobacz też


Chcesz Więcej Pytań z JavaScript?

Ten artykuł to tylko wierzchołek góry lodowej. Mamy ponad 150 pytań z JavaScript z szczegółowymi odpowiedziami, przykładami kodu i wyjaśnieniami - od podstaw po zaawansowane tematy jak performance optimization, memory leaks i design patterns.

Sprawdź Pełny Zestaw Pytań JavaScript →

Lub wypróbuj nasz darmowy podgląd pytań JavaScript, żeby zobaczyć więcej pytań w tym stylu.


Napisane przez zespół Flipcards, na podstawie ponad 15 lat doświadczenia w branży IT i setek przeprowadzonych rozmów rekrutacyjnych w firmach takich jak BNY Mellon, UBS i wiodących firmach fintech.

Chcesz więcej pytań rekrutacyjnych?

To tylko jeden temat z naszego kompletnego przewodnika po rozmowach rekrutacyjnych. Uzyskaj dostęp do 800+ pytań z 13 technologii.

Kup pełny dostęp Zobacz bezpłatny podgląd
Powrót do blogu

Zostaw komentarz

Pamiętaj, że komentarze muszą zostać zatwierdzone przed ich opublikowaniem.