Python-backend-haastatteluun valmistautuminen

Python-työhaastattelukysymykset ja -vastaukset 2026

Keskitetty valikoima Python-haastattelukysymyksiä backend-kehittäjille. Käytä jokaista vastausta puhutun vastauksen rakennemallina: aloita konseptilla, lisää konkreettinen esimerkki ja mainitse sitten kompromissit.

Aloita Python-harjoitushaastatteluEi vaadi luottokorttia. 1 ilmainen sessio saatavilla.
Teknisten haastattelujen harjoittelu englanniksiTila, jossa ei-äidinkieliset puhujat voivat harjoitella teknisen haastattelun läpäisemistä.

Pythonin datamallin ydin

1Mitä sisäänrakennettuja tietotyyppejä Pythonissa on, ja mitkä niistä ovat muuttuvia?

Pythonissa on skalaarityyppejä, kuten `NoneType`, `bool`, `int`, `float` ja `complex`; teksti- ja binääriytyyppejä, kuten `str`, `bytes` ja `bytearray`; sekvenssityyppejä, kuten `list`, `tuple` ja `range`; sekä kokoelmatyyppejä, kuten `dict`, `set` ja `frozenset`. Haastattelujen kannalta tärkeä ero on muuttuvuus: `list`, `dict`, `set` ja `bytearray` ovat muuttuvia, joten niiden sisältöä voidaan muokata, kun taas luvut, `bool`, `str`, `bytes`, `tuple`, `range`, `frozenset` ja `None` ovat muuttumattomia. Hyödyllinen vivahde on se, että vaikka `tuple`-tyypin omat alkioviittaukset ovat kiinteitä, sen viittaaman muuttuvan olion sisäinen tila voi silti muuttua.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

2Miten `list`, `tuple`, `set` ja `dict` eroavat toisistaan todellisessa koodissa?

`list` on järjestetty ja muuttuva sekvenssi, joten se sopii hyvin työjonoihin, kerättyihin tuloksiin ja muuttuvaan järjestettyyn dataan. `tuple` on järjestetty mutta muuttumaton, joten se on hyödyllinen kiinteille tietueille, funktioiden paluuarvoille ja arvoille, joita ei pitäisi sijoittaa uudelleen. `set` tallentaa yksilöllisiä tiivistettäviä (hashable) olioita ja on ihanteellinen jäsenyystarkistuksiin tai duplikaattien poistoon. `dict` yhdistää tiivistettävät avaimet arvoihin ja on normaali tietorakenne indekseille, hauille ja JSON-tyyppiselle datalle. Nykyaikaisessa Pythonissa `dict` säilyttää lisäysjärjestyksen, mutta sen päätarkoitus on yhä avainpohjainen haku.

items = ["a", "b", "a"]
unique = set(items)
index = {"user_id": 42}
point = (10, 20)
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

3Mitä eroa on operaattoreilla `is` ja `==`?

`==` tarkistaa, ovatko kaksi oliota arvoltaan yhtä suuria, yleensä `__eq__`-metodin kautta. `is` tarkistaa, osoittavatko kaksi viittausta täsmälleen samaan olioon muistissa. Tuotantokoodissa `is`-operaattoria tulisi yleensä käyttää vain singleton-tarkistuksiin, kuten `value is None`, `value is True` ainoastaan silloin, kun identiteetillä on todella merkitystä, tai vartijaolioiden (sentinel objects) kohdalla. Älä luota toteutuksen yksityiskohtiin, kuten pienten kokonaislukujen välimuistiin tai merkkijonojen internointiin; ne voivat saada `is`-operaattorin näyttämään toimivalta esimerkeissä, vaikka se olisi semanttisesti väärä valinta.

value = None

if value is None:
    print("missing")
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

4Mikä on hajautettava (hashable) olio ja miksi sillä on merkitystä?

Hajautettavalla oliolla on hajautusarvo, joka pysyy muuttumattomana sen koko elinkaaren ajan, ja tämän hajautusarvon kanssa johdonmukainen yhtäsuuruuskäyttäytyminen. Hajautettavuudella on merkitystä, koska `dict`-avaimet ja `set`-alkiot riippuvat siitä. Useimmat muuttumattomat sisäänrakennetut tyypit ovat hajautettavia, mutta kaikki muuttumattoman säiliön sisällä olevat oliot eivät automaattisesti muutu hajautettaviksi: listan sisältävä monikko (tuple) ei ole hajautettava. Haastatteluissa tämä kannattaa yhdistää suunnitteluun: jos oliota käytetään avaimena, `__eq__`- tai `__hash__`-metodeihin osallistuvat kentät eivät saa muuttua lisäyksen jälkeen, tai muuten olio voi joutua saavuttamattomiin `dict`- tai `set`-rakenteessa.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

5Miten viipalointi (slicing) toimii, mukaan lukien negatiiviset indeksit ja askellukset (steps)?

Viipale (slice) syntaksilla `sequence[start:stop:step]` sisällyttää alkukohdan (`start`), jättää pois loppukohdan (`stop`) ja etenee askelkoon (`step`) mukaisesti. Puuttuvat rajat päätellään askeleen suunnasta. Negatiiviset indeksit lasketaan lopusta alkaen, joten `s[-1]` on viimeinen alkio. Negatiivinen askel kulkee oikealta vasemmalle; `s[::-1]` luo käännetyn sekvenssin monille sekvenssityypeille. Kompromissina on se, että viipalointi luo usein uuden olion, joten se on ilmaisuvoimainen työkalu, mutta ei välttämättä suorituskyvyn kannalta ilmainen suurilla tietomäärillä käsiteltäessä.

text = "interview"

print(text[1:5])
print(text[::-1])
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Funktiot ja näkyvyysalue

6Miten Python välittää argumentit funktioille?

Python välittää olioviittaukset sijoituksella, mitä kutsutaan usein nimellä 'call by sharing'. Funktio vastaanottaa paikallisia parametrien nimiä, jotka on sidottu samoihin olioihin, joita kutsuja on antanut. Jos olio on muokattava, funktio voi muokata sitä ja kutsuja havaitsee muutoksen. Jos funktio sijoittaa parametrin nimelle uuden arvon, vain paikallinen sidonta muuttuu. Vahva vastaus haastattelussa erottaa toisistaan olion paikallaan tapahtuvan muokkaamisen ja paikallisen uudelleensidonnan: `items.append(...)` tai `items.clear()` muuttaa kutsujan listaa, kun taas `items = []` funktion sisällä ohjaa vain paikallisen nimen osoittamaan uuteen listaan.

def add_item(items):
    items.append("new")

def replace_items(items):
    items = []

values = []
add_item(values)
replace_items(values)

print(values)  # ["new"]
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

7Miksi muuttuvat oletusargumentit ovat vaarallisia?

Oletusargumenttien arvot arvioidaan vain kerran funktiota määriteltäessä, ei jokaisella funktiokutsulla. Jos oletusarvona on `list` tai `dict`, kaikki kutsut voivat jakaa saman olion, mikä voi johtaa vahingollisiin tilavuotoihin. Turvallinen malli on `def f(items=None):` ja luoda `items = []` funktion sisällä, kun `items` on `None`. On olemassa harvinaisia tapauksia, joissa jaettu tila on tarkoituksellinen, kuten pieni välimuisti, mutta silloin sen tulisi olla eksplisiittinen ja dokumentoitu, koska se yllättää lukijat.

def add_tag(tag, tags=[]):
    tags.append(tag)
    return tags

print(add_tag("python"))
print(add_tag("backend"))  # Same list reused

def safe_add_tag(tag, tags=None):
    tags = [] if tags is None else tags
    tags.append(tag)
    return tags
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

8Mihin `*args` ja `**kwargs` -parametreja käytetään?

`*args` kerää ylimääräiset sijaintiargumentit `tuple`-rakenteeseen, kun taas `**kwargs` kerää ylimääräiset nimetyt argumentit `dict`-rakenteeseen. Ne ovat hyödyllisiä kääreissä, dekoraattoreissa, joustavissa rajapinnoissa ja kutsujen välittämisessä eteenpäin. Kutsukohdassa `*iterable` purkaa sijaintiarvot ja `**mapping` purkaa nimetyt arvot. Kompromissi koskee koodin selkeyttä: eksplisiittiset parametrit ovat parempia, kun funktiolla on vakaa julkinen rajapinta, kun taas args ja kwargs sopivat paremmin rajakohtiin, joissa välittäminen tai yhteensopivuus on tärkeää.

def call_api(path, *args, **kwargs):
    print(path, args, kwargs)

call_api("/users", 1, 2, timeout=5)

values = [1, 2]
options = {"timeout": 5}
call_api("/users", *values, **options)
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

9Miten Python selvittää muuttujien nimet LEGB-säännön avulla?

Python etsii nimiä paikallisesta (Local), ympäröivästä (Enclosing), globaalista (Global) ja sisäänrakennetusta (Builtins) näkyvyysalueesta (scope). Nimen lukeminen noudattaa tätä järjestystä. Nimen sijoittaminen funktion sisällä luo normaalisti paikallisen sidonnan, minkä vuoksi koodi voi heittää `UnboundLocalError`-poikkeuksen, jos se lukee nimen ennen paikallista sijoitusta. `global`-avainsana ohjaa sijoituksen moduulin näkyvyysalueelle, ja `nonlocal` ohjaa sijoituksen ympäröivän funktion näkyvyysalueelle. Laadukkaassa tuotantokoodissa kumpaakin tulisi käyttää säästeliäästi, koska ne tekevät tilan kulusta vaikeammin hahmotettavaa.

total = 0

def add(value):
    global total
    total += value

def counter():
    count = 0

    def increment():
        nonlocal count
        count += 1
        return count

    return increment
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

10Mikä on sulkeuma (closure) ja mitä on myöhäinen sidonta (late binding)?

Sulkeuma on funktio, joka säilyttää pääsyn ympäröivän näkyvyysalueen muuttujiin sen jälkeen, kun ulompi funktio on palannut. Tämä on hyödyllistä dekoraattoreille, takaisinkutsujen tehdasfunktioille ja konfiguroiduille funktioille. Myöhäinen sidonta tarkoittaa, että vapaa muuttuja haetaan vasta, kun sisempää funktiota kutsutaan, ei silloin kun se luodaan. Silmukassa luodut lambda-funktiot näkevät kaikki silmukan viimeisen arvon. Sieppaa nykyinen arvo oletusargumentilla, esimerkiksi `lambda i=i: i`, tai käytä pientä tehdasfunktiota.

callbacks = []

for i in range(3):
    callbacks.append(lambda: i)

print([callback() for callback in callbacks])  # [2, 2, 2]

callbacks = []
for i in range(3):
    callbacks.append(lambda i=i: i)

print([callback() for callback in callbacks])  # [0, 1, 2]
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Olio-ohjelmointi, iterointi ja resurssienhallinta

11Miten luokkamuuttujat eroavat instanssimuuttujista Pythonissa?

Luokkamuuttuja tallennetaan itse luokkaolioon, ja se on jaettu kaikkien instanssien kesken, ellei instanssi peitä sitä omalla attribuutillaan. Instanssimuuttuja tallennetaan tiettyyn olioon ja asetetaan yleensä muodossa `self.name` `__init__`-metodissa. Muuttumattomat luokkavakiot ovat yleensä ongelmattomia, mutta muuttuvat luokka-attribuutit aiheuttavat usein bugeja, kun niitä käytetään vahingossa instanssikohtaisena tilana. Haastattelussa on hyvä mainita attribuuttien hakujärjestys: Python tarkistaa ensin instanssin, sitten luokan ja lopuksi sen yliluokat.

class Cart:
    items = []

first = Cart()
second = Cart()

first.items.append("book")
print(second.items)  # ["book"]

class SafeCart:
    def __init__(self):
        self.items = []
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

12Miten periytyminen, MRO (Method Resolution Order) ja `super()` toimivat?

Python ratkaisee attribuutit MRO:n avulla, joka on nähtävissä `ClassName.__mro__`-attribuutista. Yksinkertaisessa periytymisessä tämä on suoraviivaista, mutta moniperiytymisessä Python käyttää C3-linearisointia yhdenmukaisen järjestyksen luomiseen. `super()` ei tarkoita täsmälleen 'kutsu yliluokkaani', vaan se tarkoittaa 'kutsu MRO:n seuraavaa toteutusta'. Tämä mahdollistaa yhteistoiminnallisen moniperiytymisen, kun metodit hyväksyvät yhteensopivia argumentteja ja jokainen luokka kutsuu `super()`-funktiota oikein.

class A:
    def save(self):
        print("A")

class B(A):
    def save(self):
        print("B")
        super().save()

class C(A):
    def save(self):
        print("C")
        super().save()

class D(B, C):
    pass

D().save()  # B, C, A
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

13Mitä eroa on iteroitavalla (iterable), iteraattorilla (iterator) ja generaattorilla (generator)?

Iteroitava (iterable) on mikä tahansa olio, joka voi palauttaa iteraattorin `iter()`-funktiolla, kuten lista tai tiedosto-olio. Iteraattori on tilallinen olio, joka toteuttaa `__next__`-metodin ja nostaa `StopIteration`-poikkeuksen, kun se on käyty loppuun. Generaattori on kätevä tapa luoda iteraattori käyttämällä `yield`-avainsanaa tai generaattorilauseketta. Generaattorit ovat muistitehokkaita, koska ne tuottavat arvoja laiskasti, mutta ne ovat yleensä vain kerran käytettäviä eikä niitä voi indeksoida kuten listoja.

def read_lines(path):
    with open(path, encoding="utf-8") as file:
        for line in file:
            yield line.strip()
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

14Mikä on kontekstinhallitsija ja miksi `with`-lause on hyödyllinen?

Kontekstinhallitsija määrittelee alku- ja lopputoimenpiteet `__enter__`- ja `__exit__`-metodeilla, tai se voidaan luoda `contextlib.contextmanager`:n avulla. `with`-lause tekee resurssienhallinnasta selkeää ja luotettavaa: tiedostot suljetaan, lukot vapautetaan ja tietokantatapahtumat voidaan vahvistaa tai perua, vaikka poikkeuksia ilmenisi. `__exit__` saa tiedot poikkeuksesta ja voi vaimentaa sen palauttamalla arvon `True`, mutta virheiden vaimentamista tulisi käyttää harkiten.

from contextlib import contextmanager

with open("data.txt", encoding="utf-8") as file:
    data = file.read()

@contextmanager
def temporary_setting():
    print("enter")
    try:
        yield
    finally:
        print("exit")
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

15Miten matala kopio (shallow copy) ja syvä kopio (deep copy) eroavat toisistaan?

Matala kopio luo uuden ulomman säiliön, mutta säilyttää viittaukset samoihin sisäkkäisiin olioihin. Syvä kopio kopioi sisäkkäiset oliot rekursiivisesti, kun se on mahdollista. Yksinkertaisille yksiulotteisille listoille matala kopio on usein riittävä. Sisäkkäisissä muuttuvissa tietorakenteissa matala kopio voi kuitenkin johtaa tilan jakamiseen ja yllättäviin tilanteisiin. Syvä kopio on raskaampi operaatio ja se voi epäonnistua `TypeError`- tai `PicklingError`-virheeseen sellaisten resurssien kohdalla, kuten avoimet tiedostot, verkkosocketit, tietokantaistunnot tai oliot, joiden toiminta riippuu niiden identiteetistä.

from copy import copy, deepcopy

original = [[1], [2]]
shallow = copy(original)
deep = deepcopy(original)

original[0].append(99)

print(shallow)  # [[1, 99], [2]]
print(deep)     # [[1], [2]]
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

Ajoympäristö, asynkronisuus ja tuotantokoodi

16Mikä on GIL ja miten se vaikuttaa taustajärjestelmien koodiin?

Standardissa CPython-toteutuksessa GIL (Global Interpreter Lock) sallii vain yhden säikeen suorittaa Python-tavukoodia kerrallaan. Tämä tarkoittaa, että säikeet eivät yleensä nopeuta suoritinsidonnaista (CPU-bound) Python-koodia. Säikeet ovat silti hyödyllisiä I/O-sidonnaisessa työssä, koska GIL vapautetaan useiden estävien operaatioiden odottamisen ajaksi. Raskaaseen suoritinkäyttöön kannattaa käyttää moniprosessointia, natiivilaajennoksia, vektorisoituja natiivikirjastoja tai erillisiä palveluita. Vuoden 2026 työhaastatteluissa on hyvä mainita myös Python 3.13:n ja uudempien versioiden vapaasti säikeistetyt (free-threaded) CPython-koontiversiot: ne tekevät GIL:stä valinnaisen yhteensopivalle koodille, mutta vaativat silti riippuvuuksien ja suorituskyvyn testaamista.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

17Milloin tulisi valita säikeet, prosessit tai `asyncio`?

Käytä säikeitä, kun käytössäsi on estävää I/O:ta tai kirjastoja, jotka tarjoavat vain synkronisia API-rajapintoja. Käytä prosesseja paljon suoritinaikaa vaativaan työhön (CPU-bound), eristämiseen tai kaatumisten rajaamiseen hyväksyen samalla serialisoinnin ja muistin käytön lisäkuorman. Käytä `asyncio`:ta, kun sinulla on paljon samanaikaisia I/O-operaatioita ja asynkronisuuden kanssa yhteensopivia kirjastoja, kuten asynkronisia tietokanta-ajureita tai HTTP-asiakasohjelmia. Käytännön valinta riippuu myös käyttämästäsi kehyksestä ja riippuvuuksista: asynkronisen koodin sekoittaminen estäviin kutsuihin voi heikentää suorituskykyä.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

18Mihin async/await-rakennetta kannattaa käyttää Python-verkkopalveluissa?

`async` ja `await` mahdollistavat yhteistoiminnallisen moniajon. Korutiini palauttaa hallinnan tapahtumasilmukalle jäädessään odottamaan I/O-operaatiota, mikä sallii muiden tehtävien suorittamisen. Tämä on arvokasta API-rajapinnoissa, jotka odottavat tietokantoja, HTTP-kutsuja, jonoja tai verkkosocketeja. Se ei tee suoritinintensiivisestä koodista taianomaisesti nopeampaa; raskaat laskentatehtävät tukkivat tapahtumasilmukan, ellei niitä siirretä erilliselle työntekijälle, prosessille tai natiivikoodiin. FastAPI-kehyksessä tavallinen `def`-päätepiste suoritetaan säiepoolissa, kun taas estävät kutsut `async def` -funktion sisällä voivat tukkia tapahtumasilmukan; `async` auttaa vain, jos kutsupoluilla käytetään estämätöntä asynkronista I/O:ta.

import asyncio

async def load_user():
    await asyncio.sleep(0.1)
    return {"id": 1}

async def main():
    user, settings = await asyncio.gather(
        load_user(),
        load_user(),
    )
    return user, settings
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

19Miten poikkeuksia tulisi käsitellä tuotantotason Python-koodissa?

Sieppaa tarkin mahdollinen poikkeus, jonka osaat käsitellä, ja tee se kerroksessa, joka pystyy toipumaan, lisäämään kontekstia tai kääntämään sen toimialuevirheeksi. Vältä tyyppiä määrittelemättömiä `except`-lauseita äläkä niele virheitä hiljaisesti, sillä se piilottaa tietohävikin ja toiminnalliset ongelmat. Käytä `raise`-avainsanaa saman poikkeuksen nostamiseen uudelleen ja rakennetta `raise NewError(...) from exc` alkuperäisen poikkeuksen syy-yhteyden säilyttämiseksi. Hyvä tuotantokoodi myös lokittaa virheet järjestelmän rajoilla, palauttaa asianmukaiset HTTP-virhekoodit ja pitää poikkeusviestit hyödyllisinä vuotamatta salaisuuksia.

try:
    result = parse_payload(payload)
except ValueError as exc:
    raise InvalidRequest("Invalid payload") from exc
Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa

20Miten rakentaisit ja testaisit tuotantokäyttöön tarkoitetun Python-palvelun?

Erota toimialuelogiikka kehys- ja infrastruktuurikoodista, jotta ydintoiminnallisuutta voidaan testata ilman palvelinta tai tietokantaa. Käytä ympäristömuuttujista tai tyypitetyistä asetuksista tuotua konfiguraatiota, lokitusta `print`-kutsujen sijaan, riippuvuuksien injektointia rajapinnoissa ja nimenomaisia aikakatkaisuja verkkokutsuille. Yhdistä testauksessa yksikkötestit puhtaalle logiikalle, integraatiotestit tietokanta- tai ulkoisille rajapinnoille sekä muutama päästä päähän -testi kriittisille työnkuluille. Korvaa ulkoiset järjestelmät mock-objekteilla selkeissä rajapinnoissa, mutta testaa havaittavaa toimintaa yksityisten toteutusyksityiskohtien sijaan.

Kokeile vastata tähän kysymykseen tekoälyvalmentajan kanssa