Rozmowa Kwalifikacyjna Programista - JavaScript
Specjalistyczne pytania i materiały dla programistów JavaScript
Jak przygotować się do rozmowy z JavaScript
Rekruter zwykle sprawdza nie tylko składnię, ale rozumienie mechanizmów języka, debugowanie i jakość decyzji technicznych. Przygotuj krótkie odpowiedzi z przykładami kodu i umiej wytłumaczyć trade-offy.
Core fundamentals
Typy, scope, mutowalność, porównania, funkcje, obiekty, tablice.
Asynchroniczność
Promise, async/await, obsługa błędów, event loop, kolejność wykonania.
Praktyka
Debugowanie, czytelność kodu, edge case'y, testowanie i refaktoryzacja.
Pytania z technologii JavaScript (55 pytań)
▶ Podstawy (5)
Fundamentalne koncepty JavaScript
Czym różni się `var`, `let` i `const` w JavaScript?
Najważniejsze różnice to scope i możliwość ponownego przypisania.
varma function scope i historycznie robi sporo pułapek,letma block scope i można zmieniać wartość,constma block scope i nie można zrobić ponownego przypisania.
W praktyce w nowym kodzie najczęściej używasz const, a let tylko gdy wartość naprawdę ma się zmieniać.
Warto też dodać na rozmowie: const nie zamraża obiektu, tylko blokuje zmianę referencji.
Co to jest hoisting i jak wpływa na kod?
Hoisting oznacza, że deklaracje są przetwarzane przed wykonaniem kodu, ale to nie znaczy, że wszystko działa tak samo.
Najważniejsze różnice:
varjest hoistowane i przed przypisaniem ma wartośćundefined,leticonstteż są „znane” wcześniej, ale są w tzw. temporal dead zone, więc użycie przed deklaracją rzuca błąd.
Na rozmowie najlepiej pokazać to prostym przykładem i powiedzieć, że to jeden z powodów, dla których dziś unika się var.
Jaka jest różnica między `==` a `===`?
== porównuje po konwersji typów, a === porównuje bez konwersji (wartość + typ).
Dlatego w praktyce prawie zawsze używasz ===, bo daje bardziej przewidywalne wyniki.
Na rozmowie wystarczy powiedzieć, że == potrafi robić zaskakujące rzeczy przez type coercion i dlatego standardem w kodzie produkcyjnym jest ===.
Czym różni się `null` od `undefined`?
undefined zwykle oznacza „brak wartości” wynikający z tego, że coś nie zostało ustawione (np. brakująca właściwość).
null to najczęściej wartość ustawiona świadomie, żeby zaznaczyć „tu celowo nic nie ma”.
Na rozmowie warto podkreślić, że to podobne pojęcia, ale inna semantyka - i to ma znaczenie np. w walidacji danych oraz w API.
Jakie są typy danych w JavaScript?
W JavaScript masz typy prymitywne i obiekty.
Prymitywy to m.in.: string, number, boolean, undefined, null, symbol, bigint.
W praktyce najważniejsze na rozmowie jest nie tylko wymienienie listy, ale zrozumienie różnicy między prymitywem a obiektem (kopiowanie wartości vs referencji) oraz świadomość konwersji typów.
▶ Funkcje i Scope (5)
Funkcje, closures, scope
Co to jest closure i kiedy jest przydatne?
Closure to funkcja, która „pamięta” zmienne z miejsca, w którym została utworzona, nawet jeśli ten zewnętrzny scope już się zakończył.
To jest bardzo ważny mechanizm w JS, bo dzięki niemu działa masa rzeczy: callbacki, fabryki funkcji, debounce, prywatny stan.
Najprostszy przykład to licznik - funkcja zwrócona ma dostęp do zmiennej count i może ją zwiększać przy kolejnych wywołaniach.
Na rozmowie dobrze brzmi: closure = pamięć kontekstu + praktyczne zastosowanie.
Jak działa `this` w JavaScript?
Najważniejsza zasada: this zależy od sposobu wywołania funkcji, a nie od tego, gdzie funkcja została napisana.
Dlatego:
- w metodzie obiektu zwykle wskazuje ten obiekt,
- w zwykłej funkcji może być różnie (w strict mode często
undefined), - w arrow function
thisnie jest własne - jest dziedziczone z zewnętrznego kontekstu.
To klasyczny temat rekrutacyjny. Dobra odpowiedź to krótka zasada + 1 prosty przykład.
Czym różni się funkcja strzałkowa od zwykłej funkcji?
Najważniejsze różnice są praktyczne, nie tylko składniowe.
- Arrow function ma krótszą składnię,
- nie ma własnego
this(dziedziczy z zewnątrz), - nie nadaje się jako konstruktor z
new.
Dlatego arrow function świetnie sprawdza się w callbackach, ale nie zawsze nadaje się do metod obiektów, jeśli potrzebujesz dynamicznego this.
Co to jest IIFE (Immediately Invoked Function Expression)?
IIFE to funkcja uruchamiana od razu po zdefiniowaniu.
(function () {
// kod
})();Historycznie używało się tego do tworzenia prywatnego scope i ograniczania globalnych zmiennych.
Dziś w wielu przypadkach zastępują to let/const + moduły ES, ale na rozmowie warto znać IIFE, bo często pojawia się w starszym kodzie.
Jak działają funkcje wyższego rzędu (higher-order functions)?
Funkcja wyższego rzędu to taka, która przyjmuje inną funkcję jako argument albo zwraca funkcję.
W JS to codzienność - np. map, filter, reduce, event handlery, middleware.
Na rozmowie warto pokazać prosty przykład i powiedzieć, że dzięki temu łatwiej budować reużywalne abstrakcje i pisać bardziej deklaratywny kod.
▶ Obiekty i Tablice (5)
Praca z obiektami i tablicami
Jakie są sposoby tworzenia obiektów w JavaScript?
Najczęściej tworzysz obiekty po prostu literałem:
const user = { name: \"Ada\" };Ale warto znać też inne sposoby:
Object.create()- gdy chcesz kontrolować prototyp,- funkcje konstruktory /
new, - klasy ES6,
- factory functions.
Na rozmowie najważniejsze jest nie wymienienie wszystkich opcji, tylko rozumienie kiedy który styl ma sens.
Czym różni się `Object.freeze()` od `const`?
const blokuje ponowne przypisanie zmiennej, ale nie blokuje zmian wewnątrz obiektu.
const user = { name: \"Ala\" };
user.name = \"Ola\"; // to działaObject.freeze() blokuje modyfikację właściwości obiektu (dodawanie/usuwanie/zmianę), ale standardowo działa płytko (shallow).
Na rozmowie dobrze powiedzieć właśnie to: const dotyczy referencji, a freeze() stanu obiektu.
Jak działają metody tablic: `map`, `filter`, `reduce`?
To trzy podstawowe metody do pracy z tablicami w stylu deklaratywnym:
map- transformuje każdy element i zwraca nową tablicę,filter- zostawia tylko elementy spełniające warunek,reduce- składa tablicę do jednej wartości (np. sumy, obiektu, mapy).
Na rozmowie warto podkreślić, że zwracają nowe wartości i pomagają pisać czytelniejszy kod niż ręczne pętle w wielu przypadkach.
Czym różni się `slice` od `splice`?
Najkrócej:
slice- nie modyfikuje oryginalnej tablicy, zwraca wycinek,splice- modyfikuje oryginalną tablicę (usuwa / dodaje elementy).
To klasyczna pułapka rekrutacyjna, bo nazwy są podobne, a zachowanie inne.
Dobra odpowiedź: „slice = copy fragmentu, splice = mutacja tablicy”.
Jak działa shallow copy vs deep copy?
Shallow copy kopiuje tylko pierwszy poziom, więc zagnieżdżone obiekty nadal są współdzielone.
Deep copy tworzy niezależną kopię całej struktury.
To ważne w praktyce, bo łatwo przez przypadek zmienić dane „oryginału”, myśląc że pracujesz na kopii.
Na rozmowie warto powiedzieć też, że spread ({...obj}) i Array.slice() to zwykle shallow copy, a deep clone wymaga innego podejścia (np. structuredClone).
▶ Asynchroniczność (5)
Promise, async/await, event loop
Jak działa event loop w JavaScript (makrotaski i mikrotaski)?
JavaScript wykonuje kod na jednym wątku, więc kluczowe jest zrozumienie kolejności wykonywania zadań. Tym właśnie zarządza event loop.
Najprostsza wersja:
- najpierw leci kod synchroniczny (call stack),
- potem wykonywane są mikrotaski (np.
Promise.then()), - na końcu brane są makrotaski (np.
setTimeout).
Dlatego w typowym pytaniu rekrutacyjnym z setTimeout(..., 0) i Promise wynik często nie jest intuicyjny.
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);Wynik: 1, 4, 3, 2.
To pytanie sprawdza, czy rozumiesz runtime JavaScript, a nie tylko składnię.
Czym różni się Promise od callback?
Callback to po prostu funkcja przekazana jako argument, która zostanie wywołana później.
Promise to obiekt reprezentujący przyszły wynik operacji asynchronicznej.
W praktyce Promise daje wygodniejszą pracę niż callbacki:
- lepsze łańcuchowanie przez
.then(), - sensowną obsługę błędów przez
.catch(), - wsparcie dla
async/await.
Na rozmowie warto powiedzieć też, że callbacki nadal są używane (np. eventy, niektóre API), ale logikę async zwykle buduje się dziś na Promise.
Jak działa async/await?
async/await to składnia nad Promise, która poprawia czytelność kodu.
- funkcja z
asynczawsze zwracaPromise, awaitczeka na wynik Promise i zwraca resolved value.
Dzięki temu kod wygląda prawie jak synchroniczny, ale nadal działa asynchronicznie.
async function loadData() {
try {
const user = await fetchUser();
const posts = await fetchPosts(user.id);
return { user, posts };
} catch (error) {
console.error(error);
throw error;
}
}Ważny punkt na rozmowie: jeśli operacje są niezależne, lepiej puścić je równolegle przez Promise.all() zamiast czekać po kolei.
Co to jest race condition w JavaScript?
Race condition (wyścig) to sytuacja, w której wynik zależy od tego, która operacja asynchroniczna skończy się pierwsza.
Klasyczny przykład: użytkownik wpisuje tekst, wysyłasz kilka requestów, a starszy request wraca później i nadpisuje nowszy wynik.
Efekt: UI pokazuje nieaktualne dane.
Jak temu zapobiegać:
- akceptować tylko wynik najnowszego requestu,
- anulować poprzednie requesty (np.
AbortController), - stosować debounce tam, gdzie ma to sens.
Na rozmowie dobrze zaznaczyć, że problem wynika z kolejności kończenia async operacji, a nie z „wielowątkowości” samego JS.
Jak działa Promise.all() vs Promise.race()?
Najprościej:
Promise.all()czeka na wszystkie promise,Promise.race()zwraca wynik pierwszego promise, który się zakończy (resolve albo reject).
Promise.all() używasz wtedy, gdy wszystkie wyniki są Ci potrzebne. Jeśli jeden promise się wywali, całość od razu wpada w błąd.
const [user, orders] = await Promise.all([
fetchUser(),
fetchOrders()
]);Promise.race() używasz np. do timeoutu albo wyboru najszybszej odpowiedzi.
const result = await Promise.race([
fetchData(),
timeout(3000)
]);Warto też znać Promise.allSettled() - czeka na wszystko, ale nie przerywa przy pierwszym błędzie. Dobre np. przy kilku niezależnych requestach.
▶ ES6+ Features (5)
Nowoczesne funkcje JavaScript
Jakie są główne cechy ES6/ES2015?
ES6/ES2015 to duża aktualizacja JavaScript, która mocno zmieniła styl pisania kodu.
Najważniejsze rzeczy, które warto wymienić na rozmowie:
let/const,- arrow functions,
- template literals,
- destructuring,
- rest/spread,
- classes, modules, promises.
Nie chodzi o wyrecytowanie całej listy z pamięci - lepiej wymienić kilka i krótko powiedzieć, jak realnie poprawiły czytelność kodu.
Jak działa destructuring?
Destructuring pozwala „wyciągnąć” wartości z obiektów i tablic do zmiennych w krótszej formie.
const user = { name: \"Ada\", age: 25 };
const { name, age } = user;Działa też dla tablic, wartości domyślnych i zagnieżdżonych struktur.
Na rozmowie warto zaznaczyć, że to głównie poprawia czytelność i skraca kod, szczególnie przy pracy z obiektami z API.
Czym różni się rest operator od spread operator?
Składnia jest taka sama (...), ale znaczenie zależy od miejsca użycia.
- rest zbiera wiele wartości do jednej struktury (np. argumenty funkcji),
- spread rozwija strukturę na pojedyncze elementy.
Krótko: rest = „zbierz”, spread = „rozsyp”.
To dobra odpowiedź na rozmowie, bo pokazuje że rozumiesz kontekst, a nie tylko pamiętasz nazwę operatora.
Jak działają klasy w JavaScript?
Klasy w JS dają bardziej znaną składnię OOP (class, constructor, extends, super), ale pod spodem nadal działa mechanizm prototypów.
Na rozmowie warto powiedzieć dokładnie to: klasy w JS to w dużej mierze syntactic sugar nad prototypami.
To pokazuje, że rozumiesz zarówno nową składnię, jak i fundament języka.
Co to są template literals?
Template literals to stringi pisane w backtickach, które wspierają interpolację i wielolinijkowy zapis.
const name = \"Ada\";
const text = `Cześć ${name}`;W praktyce są wygodne do budowania komunikatów, prostych templatek i czytelniejszego składania stringów niż przez +.
▶ DOM i Browser API (5)
Praca z przeglądarką i DOM
Jak działa DOM manipulation?
DOM to reprezentacja HTML-a w postaci drzewa obiektów, na których JavaScript może operować.
W praktyce „DOM manipulation” to np.:
- wyszukiwanie elementów (
querySelector,getElementById), - tworzenie nowych elementów (
createElement), - dodawanie/usuwanie (
append,appendChild,remove), - zmiana treści i atrybutów (
textContent,classList,setAttribute).
Na rozmowie warto dodać, że textContent jest bezpieczniejsze niż innerHTML, jeśli wstawiasz dane użytkownika.
Druga ważna rzecz: przy dużej liczbie zmian DOM trzeba uważać na wydajność (częste reflow/repaint, nadmiar operacji w pętli).
Czym różni się event bubbling od event capturing?
To są dwie fazy propagacji zdarzenia.
- capturing - zdarzenie „schodzi” z góry (np. od
document) do elementu docelowego, - bubbling - po trafieniu w target zdarzenie „idzie do góry” przez rodziców.
Domyślnie najczęściej korzystamy z bubblingu, bo na nim działa np. event delegation.
W addEventListener możesz wskazać fazę (np. trzeci argument / opcje).
element.addEventListener(\"click\", handler, { capture: true });event.stopPropagation() zatrzymuje dalszą propagację zdarzenia.
Na rozmowie dobrze jest powiedzieć jeszcze, że to nie blokuje domyślnej akcji przeglądarki - od tego jest preventDefault().
Na czym polega event delegation?
Event delegation polega na tym, że zamiast podpinać event do każdego elementu osobno, podpinasz jeden listener do rodzica i obsługujesz kliknięcia dzieci przez event.target.
To działa dzięki event bubbling (zdarzenie idzie z dziecka do góry).
Dlaczego to jest dobre:
- mniej listenerów = prostszy kod,
- lepsza wydajność przy dużej liczbie elementów,
- działa też dla elementów dodanych dynamicznie.
list.addEventListener(\"click\", (event) => {
const button = event.target.closest(\"button[data-id]\");
if (!button) return;
console.log(\"Kliknięto:\", button.dataset.id);
});Na rozmowie warto powiedzieć też o closest(), bo samo event.target często wskazuje np. ikonę wewnątrz przycisku.
Co to jest localStorage vs sessionStorage vs cookies?
Najkrócej:
localStorage- dane trzymane lokalnie w przeglądarce, zostają po zamknięciu karty/przeglądarki,sessionStorage- dane tylko na czas sesji karty (znikają po zamknięciu karty),cookies- małe dane, które mogą być wysyłane do serwera przy requestach.
Najważniejsza różnica praktyczna: cookies lecą z requestem, a Web Storage nie.
Dlatego np. tokeny / sesje to temat bezpieczeństwa i konfiguracji cookie (HttpOnly, Secure, SameSite), a nie „wrzucenia czegokolwiek do localStorage”.
Na rozmowie warto też wspomnieć o limitach pojemności i że Web Storage działa synchronicznie.
Jak działa Web Storage API?
Web Storage API to głównie localStorage i sessionStorage.
Podstawowe metody:
setItem(key, value)getItem(key)removeItem(key)clear()
Ważne: Web Storage przechowuje wartości jako stringi, więc obiekty zwykle zapisujesz przez JSON:
localStorage.setItem(\"user\", JSON.stringify(user));
const savedUser = JSON.parse(localStorage.getItem(\"user\"));Na rozmowie warto dopowiedzieć dwie rzeczy:
- API jest synchroniczne (może mieć wpływ na wydajność przy dużych danych),
- trzeba obsłużyć przypadek, gdy klucz nie istnieje (zwróci
null).
▶ Error Handling i Debugging (3)
Obsługa błędów i debugging
Jak działa obsługa błędów w JavaScript?
Podstawą jest try / catch / finally.
try- kod, który może rzucić błąd,catch- obsługa błędu,finally- kod wykonywany niezależnie od wyniku (np. cleanup).
Możesz też sam rzucać błędy przez throw - najlepiej obiekt Error (albo własną klasę błędu).
try {
if (!user) throw new Error(\"Brak użytkownika\");
} catch (error) {
console.error(error.message);
}Dla async kodu:
- Promise ->
.catch(), async/await-> normalnytry/catch.
Na rozmowie dobrze wspomnieć, że warto rozróżniać błędy techniczne (np. network error) od błędów biznesowych (np. brak uprawnień).
Jak debugować JavaScript w browser?
Najważniejsze narzędzie to DevTools w przeglądarce.
W praktyce debugowanie najczęściej wygląda tak:
- ustawiasz breakpoint w zakładce Sources,
- sprawdzasz wartości zmiennych i call stack,
- przechodzisz krok po kroku (step over / into),
- sprawdzasz requesty w zakładce Network.
console.log jest OK, ale na rozmowie warto pokazać wyższy poziom: breakpointy, watch expressions, analiza stack trace.
Dodatkowo przydatne:
debugger;w kodzie,console.table()dla tablic obiektów,console.trace()gdy chcesz zobaczyć skąd przyszło wywołanie,- Performance tab przy problemach z wydajnością.
Co to są source maps?
Source maps to pliki, które mapują kod wynikowy (np. z bundlera/minifikacji/transpilacji) na kod źródłowy.
Dzięki temu w DevTools debugujesz swój normalny kod (np. TypeScript / nowoczesny JS), a nie zminifikowany plik produkcyjny.
To jest ważne, bo bez source map stack trace i breakpointy często wskazują niezrozumiały kod po buildzie.
Na rozmowie wystarczy powiedzieć:
- ułatwiają debugowanie po bundlingu/transpilacji,
- są generowane przez narzędzia typu webpack/vite/babel,
- na produkcji trzeba je publikować świadomie (ze względów bezpieczeństwa i ujawniania kodu źródłowego).
▶ Performance i Optymalizacja (3)
Optymalizacja wydajności
Jak optymalizować performance JavaScript?
Najważniejsza zasada: najpierw mierz, potem optymalizuj.
Na rozmowie dobrze brzmi podejście, nie tylko lista haseł:
- znajdź bottleneck (DevTools Performance / Lighthouse / profiler),
- sprawdź czy problem jest w JS, DOM, sieci czy renderowaniu,
- dobierz konkretną technikę.
Typowe techniki:
- debounce/throttle dla eventów,
- lazy loading i code splitting,
- mniej operacji DOM, batchowanie zmian,
- memoizacja tam, gdzie naprawdę pomaga,
- caching wyników / requestów,
- Web Workers dla ciężkich obliczeń.
Ważne: optymalizacja „na ślepo” często komplikuje kod i nic nie daje.
Czym różni się debounce od throttle?
Różnica jest prosta:
- debounce - wykonaj dopiero wtedy, gdy zdarzenia przestaną przychodzić przez jakiś czas,
- throttle - wykonuj maksymalnie raz na określony interwał (np. co 100 ms).
Przykład praktyczny:
- search input -> zwykle debounce, bo chcesz odpalić akcję dopiero po zakończeniu pisania,
- scroll / resize -> często throttle, bo chcesz reagować na bieżąco, ale nie setki razy na sekundę.
Krótko: debounce = poczekaj aż ucichnie, throttle = ogranicz tempo.
Jak działa garbage collection w JavaScript?
Garbage collection w JavaScript automatycznie usuwa obiekty, do których program nie ma już referencji (czyli są nieosiągalne).
Najczęściej mówi się o podejściu typu mark and sweep.
Dla rozmowy ważniejsze od szczegółów algorytmu jest to, czy rozumiesz skąd biorą się memory leaki:
- zostawione referencje w globalach,
- nieodpinane listenery,
- detached DOM nodes,
- cache rosnący bez limitu,
- closure trzymające duże obiekty dłużej niż trzeba.
WeakMap i WeakSet są przydatne, gdy nie chcesz sztucznie utrzymywać obiektów przy życiu przez referencje w strukturze danych.
Krótko: GC pomaga, ale nie zwalnia z myślenia o cyklu życia danych.
▶ Modern JavaScript i Tools (3)
Narzędzia i nowoczesny JS
Czym różni się CommonJS od ES Modules?
Najprościej:
- CommonJS używa
require()imodule.exports, - ES Modules używają
import/export.
CommonJS kojarzy się głównie ze starszym stylem Node.js, a ES Modules to standard w nowoczesnym JavaScript (browser + Node).
W praktyce ważne różnice:
- ESM lepiej wspiera statyczną analizę (np. tree shaking),
- składnia ESM jest standardowa i bardziej przewidywalna,
- w projektach możesz spotkać oba systemy naraz.
Na rozmowie dobrze dodać, że problemem bywa interoperacyjność między CJS i ESM w Node (importowanie jednego z drugiego).
Jak działa bundling (webpack, rollup, vite)?
Bundling to proces przygotowania kodu frontendu do uruchomienia / wdrożenia.
W uproszczeniu narzędzie:
- zbiera zależności z wielu plików,
- buduje bundle (jeden lub kilka plików),
- robi optymalizacje pod produkcję.
Typowe rzeczy, które robi tooling:
- code splitting,
- minifikacja,
- tree shaking,
- transpilacja,
- obsługa assetów (CSS, obrazy itp.).
Na rozmowie warto rozróżnić:
- webpack/rollup - klasyczne bundlery,
- Vite - świetny DX w dev, korzysta z natywnych modułów i robi build produkcyjny innym silnikiem (Rollup).
Nie chodzi o zapamiętanie wszystkich flag, tylko o zrozumienie po co ten proces istnieje.
Co to jest TypeScript i dlaczego go używać?
TypeScript to JavaScript rozszerzony o typy.
Finalnie i tak kompiluje się do zwykłego JavaScript, ale wcześniej daje Ci dodatkową walidację i lepsze wsparcie narzędzi.
Dlaczego firmy go używają:
- łapie część błędów wcześniej (przed runtime),
- ułatwia refaktoryzację,
- lepiej dokumentuje kontrakty funkcji i obiektów,
- poprawia podpowiedzi w IDE.
Na rozmowie warto powiedzieć też uczciwie: TypeScript nie zastępuje testów i nie eliminuje wszystkich błędów, ale mocno poprawia jakość pracy przy większych projektach.
▶ Design Patterns (3)
Wzorce projektowe
Jakie są popularne design patterns w JavaScript?
Na rozmowie nie chodzi zwykle o recytowanie całej listy wzorców, tylko o to, czy umiesz je rozpoznać i sensownie zastosować.
Najczęściej spotkasz:
- Module (enkapsulacja),
- Observer (subskrypcje / eventy),
- Factory (tworzenie obiektów),
- Strategy (wymienna logika),
- Decorator (rozszerzanie zachowania).
W JavaScript wiele z nich łatwo zrobić dzięki funkcjom wyższego rzędu, closure i obiektom.
Dobra odpowiedź na rozmowie to np. krótki przykład z projektu: „u nas użyliśmy strategii do różnych metod płatności / walidacji”.
Jak działa module pattern w JavaScript?
Module pattern to sposób na ukrycie części implementacji i wystawienie tylko publicznego API.
Klasycznie robiło się to przez closure (często z IIFE), np. prywatne zmienne były dostępne tylko wewnątrz modułu.
const counter = (() => {
let value = 0;
return {
inc() { value++; },
get() { return value; }
};
})();Tutaj value jest „prywatne”, bo trzyma je closure.
Na rozmowie warto dopowiedzieć, że dziś standardem są ES Modules (import/export), ale sam pomysł enkapsulacji dalej jest ten sam.
Co to jest observer pattern?
Observer pattern to model „publisher -> subscribers”.
Jeden obiekt (subject/publisher) emituje zdarzenie lub zmianę stanu, a wiele innych obiektów (observers) reaguje na to.
To jest bardzo częsty wzorzec w JS, bo tak działają m.in.:
- systemy eventów,
- część bibliotek reactive,
- subskrypcje stanu.
Na rozmowie warto pokazać ideę:
subscribe()dodaje listener,unsubscribe()usuwa listener,notify()odpala callbacki.
Praktyczny plus: luźne powiązanie komponentów. Minus: łatwo zrobić chaos, jeśli subskrypcji jest dużo i brak porządnego cleanupu.
▶ Testing (2)
Testowanie kodu
Jak testować kod JavaScript?
Najlepiej odpowiedzieć warstwowo, a nie listą narzędzi.
Podstawowy podział:
- unit tests - testują małe kawałki logiki,
- integration tests - sprawdzają współpracę modułów,
- E2E tests - testują przepływ użytkownika w aplikacji.
Narzędzia to tylko implementacja podejścia (np. Jest/Vitest, Playwright/Cypress).
Na rozmowie dobrze wspomnieć też o:
- mockach / stubach / spy,
- testowaniu edge caseów,
- utrzymaniu testów (żeby nie były kruche),
- CI, żeby testy odpalały się automatycznie.
Dobra odpowiedź pokazuje, że rozumiesz po co testujesz, a nie tylko znasz nazwy bibliotek.
Co to jest test-driven development?
TDD (Test-Driven Development) to podejście, w którym najpierw piszesz test, a dopiero potem kod implementacji.
Klasyczny cykl to:
- Red - test nie przechodzi,
- Green - piszesz minimalny kod, żeby przeszedł,
- Refactor - porządkujesz kod bez psucia testów.
To pomaga wymuszać prostszy design i lepiej definiować wymagania, ale nie zawsze wszędzie się sprawdza 1:1.
Na rozmowie dobra, dojrzała odpowiedź to nie „TDD zawsze najlepsze”, tylko: znam podejście, rozumiem jego zalety i wiem kiedy ma sens.
▶ Security (2)
Bezpieczeństwo
Jakie są główne zagrożenia security w JavaScript?
Najlepiej odpowiedzieć praktycznie, nie samą listą skrótów.
Najczęstsze zagrożenia w aplikacjach webowych z JS to:
- XSS (wstrzyknięcie skryptu),
- CSRF (wymuszenie żądania z kontekstu użytkownika),
- niebezpieczne użycie danych użytkownika w DOM,
- przechowywanie wrażliwych danych w złym miejscu,
- podatne zależności (npm packages).
Dobra odpowiedź na rozmowie powinna zawierać też obronę:
- walidacja i sanitizacja danych,
textContentzamiastinnerHTMLtam gdzie się da,- CSP, HTTPS, bezpieczne cookies,
- aktualizacje zależności i audyty.
W skrócie: bezpieczeństwo to nie jedna funkcja, tylko zestaw dobrych praktyk w całej aplikacji.
Czym jest XSS i jak się przed nim chronić?
XSS (Cross-Site Scripting) to sytuacja, w której aplikacja pozwala uruchomić złośliwy skrypt w przeglądarce innego użytkownika.
Najczęściej dzieje się to wtedy, gdy dane od użytkownika trafiają do DOM bez odpowiedniego zabezpieczenia.
Klasyczny błąd:
element.innerHTML = userInput;Jak się bronić:
- używaj
textContentzamiastinnerHTML, jeśli nie potrzebujesz HTML, - sanityzuj HTML, jeśli musisz go renderować,
- waliduj dane wejściowe,
- włącz CSP,
- trzymaj wrażliwe tokeny ostrożnie (i rozważ HttpOnly cookies po stronie auth).
Na rozmowie dobrze jest pokazać, że rozumiesz mechanizm ataku, a nie tylko znasz skrót „XSS”.
▶ Advanced Concepts (4)
Zaawansowane koncepty
Jak działa prototypal inheritance?
W JavaScript obiekty dziedziczą po innych obiektach przez łańcuch prototypów (prototype chain), a nie przez klasy w stylu klasycznych OOP języków.
Jeśli właściwości nie ma na obiekcie, silnik szuka jej wyżej w prototypie.
Najprostszy przykład:
const animal = { speak() { return \"...\"; } };
const dog = Object.create(animal);
dog.bark = () => \"hau\";dog.speak() zadziała, bo metoda jest w prototypie animal.
Na rozmowie warto dodać, że klasy ES6 to w dużej mierze wygodniejsza składnia nad tym samym mechanizmem prototypów.
Co to są generatory i iteratory?
Iterator to obiekt, który zwraca kolejne wartości przez metodę next().
Generator to wygodny sposób tworzenia iteratora za pomocą function* i yield.
function* ids() {
yield 1;
yield 2;
yield 3;
}
const it = ids();
console.log(it.next()); // { value: 1, done: false }Generator „pauzuje” wykonanie na yield i wznawia przy kolejnym next().
Na rozmowie warto powiedzieć, że to przydatne np. do lazy iteration, własnych struktur danych i przetwarzania sekwencji bez budowania wszystkiego naraz w pamięci.
Jak działa metaprogramowanie w JavaScript?
Metaprogramowanie to pisanie kodu, który modyfikuje lub kontroluje zachowanie innego kodu.
W JavaScript najczęściej rozmawia się o tym w kontekście:
Proxy- przechwytywanie operacji na obiektach (get/set),Reflect- niskopoziomowe operacje na obiektach,- dekorowania / opakowywania funkcji (np. logging, validation).
Przykład praktyczny: możesz logować każdy odczyt/pisanie pola przez Proxy.
Na rozmowie warto zaznaczyć, że to potężne narzędzie, ale łatwo nim przesadzić i utrudnić debugowanie, jeśli użyjesz go bez potrzeby.
Co to są Web Workers?
Web Workers pozwalają uruchomić kod JS w tle, poza głównym wątkiem UI.
To jest ważne, gdy masz ciężkie obliczenia i nie chcesz blokować interfejsu (lagów, przycięć scrolla itp.).
Kluczowe rzeczy na rozmowie:
- worker nie ma bezpośredniego dostępu do DOM,
- komunikacja odbywa się przez wiadomości (
postMessage), - używa się ich do CPU-heavy zadań (np. przetwarzanie danych, obrazu).
Krótko: jeśli problemem jest wydajność UI przez ciężki JS, Web Worker to jedno z pierwszych sensownych narzędzi.
▶ Framework Related (2)
Frameworki i biblioteki
Jakie są różnice między vanilla JS a frameworkami?
Vanilla JS daje pełną kontrolę i mniejszy narzut, ale przy większych aplikacjach szybko rośnie złożoność organizacji kodu.
Frameworki (React/Vue/Angular) dają strukturę, gotowe wzorce i ekosystem, ale kosztują dodatkowy bundle, abstrakcje i learning curve.
Dobra odpowiedź na rozmowie nie brzmi „framework zawsze lepszy” albo „vanilla zawsze lepsze”, tylko:
- mały widget / prosta strona -> często vanilla wystarczy,
- duża aplikacja z wieloma stanami i widokami -> framework zwykle przyspiesza pracę zespołu.
Czyli wybór zależy od skali projektu, zespołu i wymagań utrzymaniowych.
Jak działa virtual DOM?
Virtual DOM to pośrednia reprezentacja UI w pamięci.
Framework (np. React) buduje nową wersję tego drzewa po zmianie stanu, porównuje ją z poprzednią (diff) i dopiero potem aktualizuje realny DOM tylko tam, gdzie trzeba.
Najważniejsze na rozmowie: chodzi nie o „magiczne przyspieszenie wszystkiego”, ale o lepsze zarządzanie aktualizacjami UI.
W praktyce plusy to wygodny model programowania i ograniczenie ręcznego dłubania w DOM. Minusy: dodatkowa warstwa abstrakcji i narzut frameworka.
▶ Practical Coding (3)
Praktyczne implementacje
Jak napisać debounce function?
Debounce to funkcja, która sprawia, że inna funkcja wykona się dopiero po tym, jak przestaniemy ją wywoływać przez określony czas.
Czyli zamiast odpalać coś 100 razy na sekundę (np. przy pisaniu w inpucie), czekamy aż użytkownik przestanie pisać i dopiero wtedy działamy.
Robi się to przez setTimeout i clearTimeout.
Mechanizm jest prosty:
- przy każdym wywołaniu kasujemy poprzedni timer,
- ustawiamy nowy timer,
- jeśli przez określony czas nic nie przerwie timera, funkcja się wykona.
Kluczowe jest tu closure, bo zapamiętujemy timeoutId między wywołaniami.
Przykładowa implementacja:
function debounce(fn, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}Przykład użycia (search input):
const debouncedSearch = debounce((value) => {
console.log("Searching for:", value);
}, 500);
input.addEventListener("input", (e) => {
debouncedSearch(e.target.value);
});Dzięki temu zapytanie wykona się dopiero, gdy użytkownik przestanie pisać na 500 ms.
To samo stosuje się np. przy: resize, scroll, autosave i API calls. Chodzi o wydajność i ograniczenie liczby wywołań.
Jak zaimplementować currying (curry function)?
Currying to zamiana funkcji przyjmującej wiele argumentów na serię funkcji po jednym argumencie.
Zamiast:
sum(2, 3, 4)możesz mieć:
sum(2)(3)(4)To jest przydatne przy partial application i budowaniu małych, reużywalnych funkcji.
Prosty przykład dla funkcji 3-argumentowej:
function currySum(a) {
return function (b) {
return function (c) {
return a + b + c;
};
};
}
console.log(currySum(2)(3)(4)); // 9Na rozmowie warto dopowiedzieć, że to działa dzięki closures - każda kolejna funkcja pamięta poprzednie argumenty.
Jak zrobić deep clone obiektu?
Deep clone to pełna kopia obiektu razem z zagnieżdżonymi obiektami i tablicami.
To ważne, bo zwykłe przypisanie albo płytka kopia (np. spread) kopiuje tylko pierwszy poziom.
const copy = { ...obj }; // to NIE jest deep cloneNajbezpieczniejsza odpowiedź na rozmowie dzisiaj to:
const clone = structuredClone(obj);Bo obsługuje dużo więcej przypadków niż JSON.parse(JSON.stringify(obj)).
Uwaga: podejście JSON ma ograniczenia - gubi np. Date, undefined, funkcje i część typów specjalnych.
Jeśli ktoś pyta o implementację ręczną, warto powiedzieć o problemach:
- rekurencja po obiektach i tablicach,
- circular references,
- typy specjalne (
Date,Map,Set,RegExp).
Czyli nie chodzi tylko o sam kod, ale o świadomość edge caseów.