Forberedelse til Python-backend-interview

Python-interviewspørgsmål og svar 2026

Et målrettet sæt Python-interviewspørgsmål til backend-udviklere. Brug hvert svar som en mundtlig struktur: Start med konceptet, tilføj et konkret eksempel, og nævn derefter kompromiser.

Start simuleret Python-interviewIntet kreditkort kræves. 1 gratis session er tilgængelig.
Øv tekniske jobsamtaler på engelskEn tilstand, hvor ikke-modersmålstalende kan øve sig i at bestå tekniske interviews.

Pythons grundlæggende datamodel

1Hvilke indbyggede datatyper har Python, og hvilke er foranderlige?

Python har skalartyper som NoneType, bool, int, float og complex; tekst- og binære typer som str, bytes og bytearray; sekvenstyper som list, tuple og range; samt samlingstyper som dict, set og frozenset. Den vigtige skelnen til jobsamtaler er, om de er foranderlige: list, dict, set og bytearray kan ændres direkte, mens tal, bool, str, bytes, tuple, range, frozenset og None er uforanderlige. En nyttig nuance er, at selvom en tuples egne elementreferencer er fastlåste, kan et refereret foranderligt objekts interne tilstand stadig ændres.

Prøv at besvare dette spørgsmål med en AI-coach

2Hvordan adskiller `list`, `tuple`, `set` og `dict` sig fra hinanden i praktisk kode?

En `list` er en ordnet og foranderlig sekvens, så den er velegnet til arbejdskøer, opsamlede resultater og ordnede data, der ændrer sig. En `tuple` er ordnet, men uforanderlig, så den er nyttig til faste poster (records), returværdier fra funktioner og værdier, der ikke bør tildeles igen. Et `set` gemmer unikke "hashable" objekter og er ideelt til at kontrollere medlemskab eller til deduplikering. En `dict` knytter "hashable" nøgler til værdier og er den normale struktur for indekser, opslag og JSON-lignende data. I moderne Python bevarer en `dict` sin indsættelsesrækkefølge (insertion order), men dens primære formål er stadig nøglebaseret adgang.

items = ["a", "b", "a"]
unique = set(items)
index = {"user_id": 42}
point = (10, 20)
Prøv at besvare dette spørgsmål med en AI-coach

3Hvad er forskellen på `is` og `==`?

`==` undersøger, om to objekter er ens i værdi, typisk via `__eq__`. `is` undersøger, om to referencer peger på præcis det samme objekt i hukommelsen. I produktionskode bør `is` som regel forbeholdes singleton-tjek såsom `value is None`, `value is True` (kun når identitet virkelig er afgørende), eller sentinel-objekter. Stol ikke på implementeringsdetaljer som caching af små heltal eller streng-interning; disse kan få `is` til at se ud til at virke i eksempler, selvom det i virkeligheden er det forkerte semantiske valg.

value = None

if value is None:
    print("missing")
Prøv at besvare dette spørgsmål med en AI-coach

4Hvad er et hashbart (hashable) objekt, og hvorfor er det vigtigt?

Et hashbart objekt har en hashværdi, der forbliver stabil i løbet af dets levetid, samt en logik for lighed, der er konsistent med den pågældende hashværdi. Hashbarhed er vigtigt, fordi `dict`-nøgler og `set`-elementer afhænger af det. De fleste uforanderlige indbyggede typer er hashbare, men det er ikke alle objekter i en uforanderlig container, der automatisk bliver hashbare: En `tuple`, der indeholder en `list`, er ikke hashbar. Til samtaler kan du relatere dette til design: Hvis et objekt bruges som en nøgle, må felter, der indgår i `__eq__` eller `__hash__`, ikke ændres efter indsættelse, da objektet ellers kan blive utilgængeligt i en `dict` eller et `set`.

Prøv at besvare dette spørgsmål med en AI-coach

5Hvordan fungerer slicing, herunder negative indekser og trin (steps)?

En slice `sequence[start:stop:step]` inkluderer `start`, ekskluderer `stop` og bevæger sig i henhold til `step`. Manglende grænser udledes ud fra retningen på `step`. Negative indekser tæller fra slutningen, så `s[-1]` er det sidste element. Et negativt `step` bevæger sig fra højre mod venstre; `s[::-1]` skaber en omvendt sekvens for mange sekvenstyper. Ulempen er, at slicing ofte opretter et nyt objekt, så selvom det er udtryksfuldt, er det ikke altid uden omkostninger for store datamængder.

text = "interview"

print(text[1:5])
print(text[::-1])
Prøv at besvare dette spørgsmål med en AI-coach

Funktioner og synlighedsområde

6Hvordan overfører Python argumenter til funktioner?

Python overfører objektreferencer ved tildeling, hvilket ofte kaldes "call by sharing". Funktionen modtager lokale parameternavne, der er bundet til de samme objekter, som kalderen har angivet. Hvis objektet er foranderligt, kan funktionen ændre det, og kalderen vil kunne se ændringen. Hvis funktionen tildeler en ny værdi til parameternavnet, er det kun den lokale binding, der ændres. Et stærkt svar til en samtale skelner mellem direkte ændring og lokal ombinding: `items.append(...)` eller `items.clear()` ændrer kalderens liste, mens `items = []` inde i funktionen kun får det lokale navn til at pege på en ny liste.

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

def replace_items(items):
    items = []

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

print(values)  # ["new"]
Prøv at besvare dette spørgsmål med en AI-coach

7Hvorfor er foranderlige standardargumenter farlige?

Standardværdier for argumenter evalueres én gang, når funktionen defineres, og ikke hver gang funktionen kaldes. Hvis en standardværdi er en `list` eller `dict`, kan alle kald dele det samme objekt, og man risikerer ved et uheld at lække tilstand. Det sikre mønster er `def f(items=None):`, hvorefter man opretter `items = []` indeni funktionen, når `items` er `None`. Der findes sjældne tilfælde, hvor delt tilstand er tilsigtet, såsom en lille cache, men i så fald bør det gøres eksplicit og dokumenteres, da det ellers kan overraske den, der læser koden.

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
Prøv at besvare dette spørgsmål med en AI-coach

8Hvad bruges *args og **kwargs til?

*args samler ekstra positionelle argumenter i en tuple, mens **kwargs samler ekstra nøgleordsargumenter i en dict. De er nyttige til wrappers, dekoratører (decorators), fleksible API'er (Application Programming Interfaces) og videresendelse af funktionskald. Ved funktionskaldet udpakker *iterable positionelle værdier, og **mapping udpakker nøgleordsværdier. Ulempen er manglende tydelighed: eksplicitte parametre er bedre, når funktionen har en stabil offentlig kontrakt, mens args og kwargs er bedre ved grænseflader, hvor videresendelse eller kompatibilitet er vigtigt.

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)
Prøv at besvare dette spørgsmål med en AI-coach

9Hvordan løser Python variabelnavne med LEGB?

Python leder efter navne i gyldighedsområderne Local, Enclosing, Global og Builtins. Læsning af et navn følger denne rækkefølge. Tildeling af et navn inde i en funktion opretter normalt en lokal binding, hvilket er grunden til, at kode kan udløse `UnboundLocalError`, hvis den læser et navn før lokal tildeling. Nøgleordet `global` omdirigerer tildeling til modulets gyldighedsområde, og `nonlocal` omdirigerer tildeling til en omsluttende funktions gyldighedsområde. I god produktionskode bør begge bruges sparsomt, fordi de gør tilstandsflowet mindre tydeligt.

total = 0

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

def counter():
    count = 0

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

    return increment
Prøv at besvare dette spørgsmål med en AI-coach

10Hvad er en closure, og hvad er late binding?

En closure er en funktion, der bevarer adgangen til variabler fra et omgivende synlighedsområde, selv efter at den ydre funktion har returneret. Dette er nyttigt til dekoratører, callback-fabrikker og konfigurerede funktioner. Late binding betyder, at den frie variabel først slås op, når den indre funktion kaldes, og ikke når den oprettes. Lambda-funktioner oprettet i en løkke vil alle se løkkens endelige værdi. Indfang den aktuelle værdi med et standardargument, for eksempel lambda i=i: i, eller brug en lille fabriksfunktion.

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]
Prøv at besvare dette spørgsmål med en AI-coach

OOP, iteration og ressourcehåndtering

11Hvordan adskiller klassevariabler sig fra instansvariabler?

En klassevariabel gemmes på klasseobjektet og deles af instanser, medmindre en instans overskygger den med sin egen attribut. En instansvariabel gemmes på et specifikt objekt, normalt tildelt som `self.name` i `__init__`. Uforanderlige klassekonstanter er normalt uproblematiske, men foranderlige klasseattributter er en almindelig kilde til fejl, når de ved et uheld bruges som tilstand pr. instans. I en jobsamtale bør du nævne opslagsrækkefølgen: Python tjekker instansen først, derefter klassen og dens overordnede klasser (ancestors).

class Cart:
    items = []

first = Cart()
second = Cart()

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

class SafeCart:
    def __init__(self):
        self.items = []
Prøv at besvare dette spørgsmål med en AI-coach

12Hvordan fungerer nedarvning, MRO (Method Resolution Order) og super()?

Python slår attributter op gennem sin MRO, hvilket kan ses med ClassName.__mro__. Ved enkelt nedarvning er dette ligetil, men ved multipel nedarvning bruger Python C3-linearisering til at skabe en konsistent rækkefølge. super() betyder ikke strengt taget 'kald min forælder'; det betyder 'kald den næste implementering i MRO'en'. Dette muliggør kooperativ multipel nedarvning, når metoder modtager kompatible argumenter, og hver klasse kalder super() korrekt.

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
Prøv at besvare dette spørgsmål med en AI-coach

13Hvad er forskellen på en iterable, en iterator og en generator?

Et itererbart objekt (iterable) er ethvert objekt, der kan returnere en iterator fra `iter()`, såsom en liste eller et filobjekt. En iterator er et tilstandsbaseret objekt, der implementerer `__next__` og kaster `StopIteration`, når det er udtømt. En generator er en praktisk måde at oprette en iterator på ved hjælp af `yield` eller et generatorudtryk. Generatorer er hukommelseseffektive, fordi de producerer værdier efter behov (lazily), men de kan normalt kun gennemløbes én gang og kan ikke indekseres som en liste.

def read_lines(path):
    with open(path, encoding="utf-8") as file:
        for line in file:
            yield line.strip()
Prøv at besvare dette spørgsmål med en AI-coach

14Hvad er en context manager, og hvorfor er `with` nyttig?

En context manager definerer indgangs- og udgangsadfærd med `__enter__` og `__exit__`, eller den kan oprettes med `contextlib.contextmanager`. `with`-sætningen gør ressourcehåndtering eksplicit og pålidelig: filer lukkes, låse frigives, og transaktioner kan godkendes (commit) eller rulles tilbage (roll back), selv når der opstår undtagelser. `__exit__` modtager information om undtagelser og kan undertrykke undtagelsen ved at returnere `True`, selvom man skal være forsigtig med at undertrykke fejl.

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")
Prøv at besvare dette spørgsmål med en AI-coach

15Hvad er forskellen på en overfladisk kopi (shallow copy) og en dyb kopi (deep copy)?

En overfladisk kopi opretter en ny ydre beholder, men beholder referencer til de samme indlejrede objekter. En dyb kopi kopierer indlejrede objekter rekursivt, når det er muligt. For simple, flade lister er en overfladisk kopi ofte tilstrækkelig. For indlejrede, foranderlige strukturer kan en overfladisk kopi dog stadig dele tilstand og føre til uventet adfærd. En dyb kopi er mere ressourcekrævende og kan fejle med `TypeError` eller `PicklingError` for ressourcer som åbne filer, sockets, databasesessioner eller objekter med identitetsfølsom adfærd.

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]]
Prøv at besvare dette spørgsmål med en AI-coach

Kørselsmiljø, asynkron programmering og produktionskode

16Hvad er GIL (Global Interpreter Lock), og hvordan påvirker den backend-kode?

I standard CPython tillader GIL'en kun én tråd at udføre Python-bytecode ad gangen. Dette betyder, at tråde normalt ikke øger hastigheden for CPU-bundet Python-kode. Tråde er stadig nyttige til I/O-bundet arbejde, fordi GIL'en frigives, mens der ventes på mange blokerende operationer. Til CPU-tungt arbejde bør man bruge multiprocessing, native udvidelser, vektoriserede native-biblioteker eller separate tjenester. Til interviews i 2026 kan man også nævne Python 3.13+ free-threaded CPython-builds: De gør GIL valgfri for kompatibel kode, men de kræver stadig test af afhængigheder og ydeevne.

Prøv at besvare dette spørgsmål med en AI-coach

17Hvornår bør man vælge tråde (threads), processer eller `asyncio`?

Brug tråde, når du har blokerende I/O eller biblioteker, der kun udstiller synkrone API'er. Brug processer til CPU-krævende arbejde, isolering eller begrænsning af nedbrud, hvor man accepterer overhead fra serialisering og hukommelsesforbrug. Brug `asyncio`, når du har mange samtidige I/O-operationer og async-kompatible biblioteker, såsom asynkrone databasedrivere eller HTTP-klienter. Det praktiske valg afhænger også af dit framework og dine afhængigheder: blanding af async-kode med blokerende kald kan forringe ydeevnen.

Prøv at besvare dette spørgsmål med en AI-coach

18Hvad er `async`/`await` godt for i Python-webtjenester?

`async` og `await` muliggør kooperativ samtidighed. En coroutine giver kontrollen tilbage til hændelsesløkken, når den afventer I/O, hvilket tillader andre opgaver at køre. Dette er værdifuldt for API'er, der venter på databaser, HTTP-kald, køer eller sockets. Det gør ikke på magisk vis CPU-bundet kode hurtigere; CPU-tungt arbejde blokerer hændelsesløkken, medmindre det flyttes til en worker, en proces eller native kode. I FastAPI køres et normalt `def`-endepunkt i en trådpulje, mens blokerende kald indeni `async def` kan blokere hændelsesløkken; `async` hjælper kun, når udførelsesvejen bruger ikke-blokerende asynkron I/O.

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
Prøv at besvare dette spørgsmål med en AI-coach

19Hvordan bør undtagelser (exceptions) håndteres i Python-produktionskode?

Grib den mest specifikke undtagelse, du kan håndtere, og grib den i et lag, der er i stand til at gendanne (recover), tilføje kontekst eller oversætte den til en domænefejl. Undgå en bar `except`, og undgå at sluge fejl i stilhed, da dette skjuler datatab og driftsproblemer. Brug `raise` til at kaste den samme undtagelse igen, og brug `raise NewError(...) from exc` for at bevare årsagssammenhængen. God produktionskode logger også ved systemgrænser, returnerer passende HTTP-fejl og holder fejlmeddelelser nyttige uden at lække hemmeligheder.

try:
    result = parse_payload(payload)
except ValueError as exc:
    raise InvalidRequest("Invalid payload") from exc
Prøv at besvare dette spørgsmål med en AI-coach

20Hvordan vil du strukturere og teste en Python-tjeneste til produktion?

Adskil domænelogik fra framework- og infrastrukturkode, så kerneadfærden kan testes uden en server eller database. Brug konfiguration fra miljøvariabler eller typede indstillinger, logning i stedet for `print`, dependency injection ved grænseflader og eksplicitte timeouts til netværkskald. Til tests bør du kombinere enhedstests af ren logik, integrationstests af database- eller eksterne grænseflader, og nogle få end-to-end-tests af kritiske forløb. Mock eksterne systemer ved tydelige grænseflader, men test observerbar adfærd i stedet for private implementeringsdetaljer.

Prøv at besvare dette spørgsmål med en AI-coach