Na rozmowach z Pythona najczęściej pada kilkanaście pytań: różnica między listą, krotką i słownikiem, mutowalność (w tym pułapka z argumentem domyślnym),==kontrais, dekoratory, generatory, GIL oraz podstawy OOP i frameworków. Rekruter sprawdza, czy rozumiesz, dlaczego kod zachowuje się tak, a nie inaczej, a nie czy znasz definicje na pamięć. Dobra odpowiedź to jedno zdanie definicji i krótki przykład.
Rozmowa techniczna z Pythona rzadko jest egzaminem z całego języka. Najczęściej wracają te same tematy, w których rekruter sprawdza, czy rozumiesz mechanikę Pythona, a nie tylko potrafisz zbudować działający skrypt. Poniżej znajdziesz pytania, które padają na rozmowach na stanowisko juniora, wraz z tym, jak na nie odpowiadać. Jeśli celujesz też w frontend, zobacz analogiczną listę: pytania rekrutacyjne z JavaScript. Całą ścieżkę od nauki do pierwszej pracy opisuje artykuł o ścieżkach rozwoju w Pythonie.
Jak odpowiadać na pytania rekrutacyjne z Pythona?
Najlepiej w trzech krokach: jedno zdanie definicji, krótki przykład kodu, a na końcu informacja, kiedy to ma znaczenie w praktyce. Taka odpowiedź zajmuje 30–60 sekund i pokazuje, że rozumiesz temat. Jeśli nie znasz odpowiedzi, powiedz, co wiesz, i rozumuj na głos. Rekruterzy oceniają sposób myślenia, nie tylko wynik.
Podstawy języka
Czym różni się lista od krotki, zbioru i słownika?
Lista (list) to uporządkowana, mutowalna kolekcja, w której elementy mogą się powtarzać. Krotka (tuple) jest uporządkowana, ale niemutowalna. Zbiór (set) przechowuje unikalne elementy i nie gwarantuje kolejności. Słownik (dict) przechowuje pary klucz-wartość, a od Pythona 3.7 zachowuje kolejność wstawiania. Klucze słownika i elementy zbioru muszą być hashowalne, więc lista nie może być kluczem, a krotka z liczbami może.
Co to znaczy, że typ jest mutowalny?
Obiekt mutowalny można zmienić w miejscu bez tworzenia nowego obiektu. Mutowalne są list, dict i set. Niemutowalne są int, float, bool, str, tuple i frozenset. To ważne, bo Python przekazuje do funkcji referencję do obiektu: funkcja, która zmieni listę, zmieni ją także u wywołującego. Przypisanie nowej wartości do nazwy takiego efektu nie ma.
Czym różni się == od is?
== porównuje wartości, a is sprawdza tożsamość, czyli czy to ten sam obiekt w pamięci. Dwie osobne listy z tymi samymi elementami są równe, ale nie są tym samym obiektem.
a = [1, 2]
b = [1, 2]
c = a
print(a == b) # True: te same wartości
print(a is b) # False: dwa różne obiekty
print(a is c) # True: c wskazuje na ten sam obiekt co a
is stosuje się niemal wyłącznie do porównań z None (if x is None). Nie polegaj na is przy liczbach i napisach, bo to, kiedy Python użyje tego samego obiektu, jest szczegółem implementacji.
Dlaczego mutowalny argument domyślny to pułapka?
Wartość domyślna argumentu jest tworzona raz, przy definicji funkcji, a nie przy każdym wywołaniu. Jeśli to lista, kolejne wywołania dopisują do tej samej listy.
def add_tag(tag, tags=[]):
tags.append(tag)
return tags
print(add_tag("a")) # ['a']
print(add_tag("b")) # ['a', 'b'], a nie ['b']
def add_tag_safe(tag, tags=None):
if tags is None:
tags = []
tags.append(tag)
return tags
print(add_tag_safe("a")) # ['a']
print(add_tag_safe("b")) # ['b']
To jedno z najczęstszych pytań, bo sprawdza jednocześnie mutowalność i moment ewaluacji domyślnych wartości. Rozwiązanie: None jako wartość domyślna i utworzenie listy w ciele funkcji.
Czym różni się płytka kopia od głębokiej?
Płytka kopia (copy.copy, list.copy(), lista[:]) kopiuje tylko pierwszy poziom, a zagnieżdżone obiekty pozostają współdzielone. Głęboka kopia (copy.deepcopy) kopiuje rekurencyjnie wszystko.
import copy
nested = [[1], [2]]
shallow = copy.copy(nested)
deep = copy.deepcopy(nested)
nested[0].append(9)
print(shallow) # [[1, 9], [2]]: zagnieżdżona lista jest współdzielona
print(deep) # [[1], [2]]: niezależna kopia
Funkcje
Co oznaczają _args i _*kwargs?
*args zbiera dodatkowe argumenty pozycyjne do krotki, a **kwargs dodatkowe argumenty nazwane do słownika. Dzięki temu funkcja przyjmuje dowolną liczbę argumentów, co jest podstawą dekoratorów i funkcji opakowujących inne funkcje.
def show(*args, **kwargs):
return args, kwargs
print(show(1, 2, x=3)) # ((1, 2), {'x': 3})
Czym jest dekorator i jak go napisać?
Dekorator to funkcja, która przyjmuje funkcję i zwraca nową, rozszerzoną wersję, bez zmiany kodu oryginału. Zapis @dekorator nad definicją to skrót od funkcja = dekorator(funkcja). Używa się ich do logowania, mierzenia czasu, sprawdzania uprawnień czy cache'owania.
import functools
def log_call(func):
@functools.wraps(func) # zachowuje nazwę i dokumentację oryginału
def wrapper(*args, **kwargs):
print("wywołanie:", func.__name__)
return func(*args, **kwargs)
return wrapper
@log_call
def add(a, b):
return a + b
print(add(2, 3)) # wywołanie: add, potem 5
print(add.__name__) # add (bez functools.wraps byłoby: wrapper)
Pytanie uzupełniające dotyczy zwykle functools.wraps: bez niego dekorowana funkcja traci nazwę i docstring, co utrudnia debugowanie.
Czym są generatory i co robi yield?
Generator to funkcja, która zamiast zwracać wszystko naraz, produkuje wartości jedna po drugiej przez yield i zapamiętuje swój stan między wywołaniami. Jest leniwy: liczy kolejny element dopiero na żądanie, więc nie trzyma całej kolekcji w pamięci. To dobre rozwiązanie dla dużych plików czy strumieni danych.
def squares(n):
for i in range(n):
yield i * i
gen = squares(3)
print(next(gen)) # 0
print(list(gen)) # [1, 4]: reszta wartości
Co to jest list comprehension i czym różni się od pętli?
To zwięzły zapis tworzący listę z istniejącej sekwencji, często z warunkiem. Jest zwykle czytelniejszy i szybszy niż pętla z append, ale gdy logika się komplikuje, lepsza jest zwykła pętla. Podobnie działają słowniki i zbiory składane.
print([n * n for n in range(5) if n % 2 == 0]) # [0, 4, 16]
print({n: n * n for n in range(3)}) # {0: 0, 1: 1, 2: 4}
Jeśli ktoś zapyta o różnicę względem map i filter: comprehension jest w Pythonie bardziej idiomatyczne, a po wyrażenie generatorowe w nawiasach okrągłych sięgniesz, gdy nie potrzebujesz całej listy naraz.
Co to jest closure i pułapka z lambdą w pętli?
Closure to funkcja zapamiętująca zmienne z zasięgu, w którym powstała. Pułapka: funkcje tworzone w pętli widzą zmienną w momencie wywołania, nie w momencie tworzenia.
functions = [lambda: i for i in range(3)]
print([f() for f in functions]) # [2, 2, 2], a nie [0, 1, 2]
functions = [lambda i=i: i for i in range(3)]
print([f() for f in functions]) # [0, 1, 2]
Programowanie obiektowe
Do czego służy self i metoda init?
self to referencja do bieżącej instancji, którą Python przekazuje jako pierwszy argument metody. __init__ to inicjalizator wywoływany tuż po utworzeniu obiektu, w którym ustawiasz jego początkowy stan. Formalnie to nie konstruktor, bo obiekt istnieje już w momencie wywołania, ale w rozmowie nikt się o to nie przyczepi.
Czym różni się @classmethod od @staticmethod i zwykłej metody?
Zwykła metoda dostaje instancję (self) i pracuje na jej danych. @classmethod dostaje klasę (cls), więc nadaje się do alternatywnych konstruktorów. @staticmethod nie dostaje niczego, to zwykła funkcja umieszczona w klasie dla porządku.
Jak działa dziedziczenie i MRO?
Klasa pochodna przejmuje atrybuty i metody klasy bazowej, a super() pozwala wywołać wersję z klasy nadrzędnej. Przy dziedziczeniu wielokrotnym Python ustala kolejność przeszukiwania klas według MRO (Method Resolution Order, algorytm C3), którą zobaczysz w NazwaKlasy.__mro__. Powszechną praktyką jest preferowanie kompozycji nad głębokim dziedziczeniem. Wzorce i polimorfizm opisujemy w artykule o wzorcach projektowych i polimorfizmie w Pythonie.
Czym różni się str od repr?
__str__ zwraca czytelny dla użytkownika opis obiektu (używa go print), a __repr__ jednoznaczną reprezentację dla programisty, najlepiej taką, z której można odtworzyć obiekt. Gdy zdefiniujesz tylko __repr__, Python użyje go także zamiast __str__.
Co to są metody specjalne (dunder)?
To metody o nazwach w podwójnych podkreśleniach, np. __len__, __eq__, __enter__, dzięki którym własne klasy zachowują się jak wbudowane typy i współpracują z operatorami oraz funkcjami len() czy with.
Mechanika języka
Co to jest GIL i czy Python obsługuje wielowątkowość?
GIL (Global Interpreter Lock) to blokada w CPythonie, która pozwala, by w danym momencie bajtkod Pythona wykonywał tylko jeden wątek. Dlatego wątki nie przyspieszą zadań obciążających procesor, ale dobrze sprawdzają się przy operacjach wejścia-wyjścia (sieć, pliki), bo w trakcie oczekiwania GIL jest zwalniany. Dla zadań CPU używa się multiprocessing, a dla wielu operacji I/O naraz także asyncio. Od Pythona 3.13 istnieje eksperymentalna wersja bez GIL (free-threaded), ale standardowa instalacja nadal go zawiera.
Czym jest context manager i instrukcja with?
Context manager to obiekt, który przygotowuje zasób na wejściu do bloku with i gwarantuje jego sprzątnięcie na wyjściu, nawet gdy wystąpi wyjątek. Najczęściej spotkasz go przy plikach (with open(...)), połączeniach z bazą i blokadach. Własny napiszesz przez metody __enter__ i __exit__ albo dekorator contextlib.contextmanager.
class Resource:
def __enter__(self):
print("otwarcie")
return self
def __exit__(self, exc_type, exc, tb):
print("zamknięcie")
with Resource():
print("praca")
# otwarcie, praca, zamknięcie
Jak obsługuje się wyjątki w Pythonie?
Kod, który może się nie powieść, umieszczasz w try, a błędy łapiesz w except. Blok else wykona się, gdy wyjątku nie było, a finally zawsze, więc nadaje się do sprzątania. Łap konkretne wyjątki (ValueError, KeyError), a nie gołe except:, które ukrywa błędy i przechwytuje nawet przerwanie programu.
Co to są type hints i do czego służą?
Adnotacje typów (def add(a: int, b: int) -> int) dokumentują oczekiwane typy, ale Python ich w czasie działania nie wymusza. Korzystają z nich edytory i narzędzia takie jak mypy. Bibliotekę, która na ich podstawie waliduje dane w czasie działania, opisuje artykuł czym jest Pydantic w Pythonie.
Narzędzia i frameworki
Po co środowisko wirtualne i jak je utworzyć?
Środowisko wirtualne (venv) izoluje zależności projektu, więc dwa projekty mogą używać różnych wersji tej samej biblioteki bez konfliktu. Tworzysz je poleceniem python -m venv .venv, aktywujesz i dopiero wtedy instalujesz pakiety przez pip. Listę zależności zapisuje się w requirements.txt albo w pyproject.toml.
Jak testuje się kod w Pythonie?
Najpopularniejszym narzędziem jest pytest: proste asercje assert, fixtures do przygotowania danych i parametryzacja testów. Moduł unittest jest wbudowany, ale bardziej rozbudowany składniowo. Praktykę opisują artykuły o TDD i BDD w pytest oraz o testowaniu bazy danych w pytest z SQLAlchemy. Gdy test zawodzi, przyda się debugowanie w Pythonie z pdb.
Czym różnią się Django, Flask i FastAPI?
Django to framework „z bateriami”: ORM, panel administracyjny, uwierzytelnianie i szablony w jednym pakiecie, dobry do pełnych aplikacji. Flask jest minimalny i elastyczny, a FastAPI nastawiony na nowoczesne API z walidacją na bazie type hints i obsługą async. Pełne porównanie znajdziesz w artykule Django vs Flask vs FastAPI. Pierwsze kroki opisują teksty co to jest Django i FastAPI: pierwsze API.
Co to jest problem N+1 w Django ORM?
Dzieje się tak, gdy pobierasz listę obiektów jednym zapytaniem, a potem dla każdego z nich osobnym zapytaniem dociągasz powiązane dane, czyli zamiast 2 zapytań masz ich N+1. Rozwiązaniem jest select_related (dla relacji jeden-do-wielu od strony „wielu” i jeden-do-jednego, robi JOIN) oraz prefetch_related (dla relacji wiele-do-wielu i odwrotnych, wykonuje dodatkowe zapytanie i łączy wyniki w Pythonie). Inne typowe wpadki opisuje artykuł o najczęstszych błędach początkujących w Django.
Co daje async w FastAPI i kiedy może zaszkodzić?
Endpoint zdefiniowany jako async def działa w pętli zdarzeń i może obsłużyć wiele żądań naraz, o ile czeka na operacje asynchroniczne (await). Jeśli wewnątrz wywołasz blokującą funkcję, np. zwykłe zapytanie do bazy czy time.sleep, zablokujesz całą pętlę i inne żądania będą czekać. Blokujący kod umieszcza się w zwykłej funkcji def (FastAPI uruchomi ją w puli wątków) albo używa bibliotek asynchronicznych.
Jak przygotować się do rozmowy z Pythona?
Samo czytanie odpowiedzi nie wystarczy, bo pytania uzupełniające szybko pokazują, czy rozumiesz temat. Skuteczniej działa pisanie własnych małych projektów i głośne tłumaczenie tematów mentorowi lub koledze. Dla porządku:
| Obszar | Co umieć wyjaśnić własnymi słowami |
|---|---|
| Podstawy | list/tuple/set/dict, mutowalność, == vs is, argument domyślny |
| Funkcje | *args/**kwargs, dekoratory, generatory, comprehension, closures |
| OOP | self, dziedziczenie i MRO, __str__ vs __repr__, @classmethod |
| Mechanika | GIL, context manager, wyjątki, type hints |
| Narzędzia | venv, pytest, Django ORM (N+1), Flask vs FastAPI |
Częste zadania z kodem to: odwrócenie napisu, sprawdzenie palindromu, policzenie wystąpień elementów (collections.Counter), usunięcie duplikatów z zachowaniem kolejności, FizzBuzz i ciąg Fibonacciego. Rekruter patrzy na sposób rozwiązywania, więc nazwij zmienne czytelnie, omów przypadki brzegowe (pusta lista, None) i powiedz, jaka jest złożoność.
Na czym juniorzy najczęściej tracą punkty?
Nie na trudnych tematach, tylko na trzech drobiazgach, które wychodzą dopiero przy pytaniach uzupełniających:
iszamiast==przy liczbach i napisach.issprawdza tożsamość obiektu, a nie wartość. To, czy dwie równe liczby lub napisy są tym samym obiektem, zależy od implementacji, więc porównanieismoże raz zadziałać, a raz nie. Do wartości używaj==, aiszostaw dlaNone.- Goły
except:. Łapie wszystkie wyjątki, takżeKeyboardInterruptiSystemExit, więc ukrywa błędy i utrudnia zatrzymanie programu. Łap konkretny wyjątek, np.except ValueError:. - Blokujący kod w
async def. Zwykłe zapytanie do bazy czytime.sleepwewnątrz endpointu asynchronicznego w FastAPI zatrzymuje pętlę zdarzeń, więc wszystkie inne żądania czekają. Wyjaśnij, że blokujący kod należy do zwykłej funkcjidefalbo do biblioteki asynchronicznej.
Jak brzmi dobra odpowiedź, a jak słaba?
Weźmy pytanie o dekoratory:
| Odpowiedź | Dlaczego | |
|---|---|---|
| Słaba | „Dekorator to taka funkcja ze znaczkiem @ nad inną funkcją.” | To opis składni, nie mechanizmu, a pytanie uzupełniające od razu to pokaże. |
| Dobra | „Dekorator to funkcja, która przyjmuje funkcję i zwraca nową, rozszerzoną wersję, np. z logowaniem. Zapis @dekorator to skrót od funkcja = dekorator(funkcja), a functools.wraps zachowuje nazwę i docstring oryginału.” | Jest definicja, mechanizm i szczegół, który pokazuje praktykę. |
Ten sam schemat działa przy każdym pytaniu z tego artykułu: definicja, mechanizm, przykład użycia.
Co zrobić, gdy nie znasz odpowiedzi?
Nie zgaduj i nie milcz. Powiedz, co wiesz na pewno, a potem rozumuj na głos, np.: „Nie pamiętam dokładnych szczegółów asyncio, ale wiem, że służy do współbieżności przy operacjach I/O, więc sprawdziłbym to w dokumentacji Pythona.” Taka odpowiedź pokazuje, że potrafisz sobie poradzić w pracy, a umiejętność znalezienia odpowiedzi jest w codziennej pracy ważniejsza niż pamięć do szczegółów.
Dobrze też wiedzieć, jak wygląda sama rozmowa. W podcaście Sonia Chojnik opowiada, jak rekrutację juniora widzi rekruter IT.
Znajomość odpowiedzi to połowa sukcesu, drugą jest znalezienie rozmów, na które warto się umówić. Jeśli szukasz ofert, sięgnij po darmowy poradnik o tym, jak szukać pracy w 2026 roku, w którym zebraliśmy 3 sposoby poszukiwania pracy, które u naszych podopiecznych dają najlepsze efekty. Całość procesu, od CV po rozmowę, opisuje artykuł o tym, jak przejść cały proces rekrutacyjny na juniora.
Jeśli braki wyjdą przy dekoratorach, generatorach czy ORM-ie, to sygnał, że warto uporządkować wiedzę z mentorem. Ścieżka Python prowadzi od podstaw, przez OOP i testy, po Django i bazy danych. Jeśli dopiero zaczynasz, przeczytaj też, dlaczego Python bywa dobrym pierwszym językiem.


