Tillbaka till Utvecklare

Backend-utveckling

Backend-intervjufrågor — Java, Spring, samtidighet och tjänstedesign.

Java- och OOP-grunder

Vilka är de fyra pelarna i objektorienterad programmering i Java? Ge ett exempel på varje.
1) Inkapsling: dölja interna data och exponera bara det nödvändiga via getters/setters (t.ex. en BankAccount-klass med privat saldo-fält). 2) Abstraktion: representera verkliga koncept med fokus på det väsentliga (t.ex. ett Payment-gränssnitt med metoden processPayment()). 3) Arv: en barnklass ärver attribut och metoder från en förälder (t.ex. class Dog extends Animal). 4) Polymorfism: samma metod beter sig olika beroende på objektet (t.ex. animal.makeSound() skäller för hund, jamar för katt).
Förklara SOLID-principerna med ett kort exempel för varje.
S (Single Responsibility): varje klass har ett enda skäl att ändras — t.ex. InvoicePrinter skriver bara ut, InvoiceCalculator beräknar. O (Open/Closed): klasser är öppna för utökning men stängda för modifiering — t.ex. abstrakt Discount med subklasser PercentageDiscount och FixedDiscount. L (Liskov Substitution): subklasser måste kunna ersätta basklassen utan att bryta beteendet. I (Interface Segregation): ingen klass ska tvingas implementera metoder den inte använder — föredra små gränssnitt som Flyable, Swimmable. D (Dependency Inversion): högnivåmoduler beror på abstraktioner — t.ex. UserNotifier beror på ett Notifier-gränssnitt istället för en konkret EmailService.
Vad är skillnaden mellan ett interface och en abstrakt klass i Java? När skulle du använda respektive?
Ett interface definierar ett kontrakt som klasser måste uppfylla — det anger vad som ska göras men inte hur. En abstrakt klass kan tillhandahålla delvis implementation tillsammans med kontraktet. Använd interface för att definiera gemensamt beteende mellan orelaterade klasser (t.ex. Serializable). Använd abstrakta klasser när du vill dela logik eller tillstånd bland närbesläktade klasser i en arvshierarki.
Vilka viktiga funktioner introducerades i Java 8, Java 11 och Java 17?
Java 8 introducerade funktionell programmering: lambdas, Streams API, Optional och det nya Date/Time-API:et. Java 11 förde med sig produktivitetsförbättringar som lokal typinferens med var och ett modernt asynkront HTTP-klient-API. Java 17 lade till records, sealed classes och pattern matching, vilket förbättrar läsbarhet och typsäkerhet.
Förklara skillnaden mellan == och equals() i Java.
== jämför objektreferenser — det kontrollerar om två variabler pekar på exakt samma objekt i minnet. equals() jämför det logiska innehållet eller värdet hos objekt, beroende på hur metoden är överskriven i klassen. Till exempel returnerar två olika String-objekt med samma text false med == men true med equals().
Vad är Garbage Collector i Java och vilka typer av GC finns i JVM?
Garbage Collector frigör automatiskt minne som upptas av objekt utan referenser, vilket förhindrar minnesläckor. Viktiga GC-implementationer: Serial GC (entrådad, för små appar), Parallel GC (flertrådad insamling), G1 GC (regionbaserad, standard sedan Java 11, balanserar genomströmning och latens), och ZGC/Shenandoah (designade för ultralåg latens i stora heapar).
Nämn några vanliga designmönster och förklara ett med ett Java-exempel.
Vanliga mönster: Singleton (säkerställer en enda instans — används för connection pools, loggers), Factory Method (delegerar objektskapande utan att exponera instansieringslogik), Observer (prenumerationsmodell där observatörer reagerar på förändringar — används i eventsystem som Kafka), Strategy (utbytbara algoritmer vid körning) och Saga (koordinerar lokala transaktioner mellan mikrotjänster utan distribuerade transaktioner). Exempel — Singleton: privat konstruktor, statiskt instansfält och en publik static getInstance()-metod som garanterar en enda instans genom hela applikationen.
Vad är Liskov Substitution-principen? Ge ett klassiskt exempel på ett brott mot den.
Liskov Substitution-principen (LSP) säger att subklasser måste kunna ersätta sina basklasser utan att ändra programmets korrekthet. Ett klassiskt brott är att låta Square ärva Rectangle: om Rectangle har oberoende setWidth/setHeight-metoder bryter en Square-override som tvingar båda dimensionerna att vara lika kod som förväntar sig oberoende dimensioner. Lösningen är att omdesigna hierarkin så att invarianter bevaras.
Vad är Interface Segregation-principen? Hur förbättrar den designen?
Interface Segregation-principen (ISP) säger att ingen klass ska tvingas implementera metoder den inte använder. Istället för ett stort gränssnitt med många metoder bör man skapa mindre, fokuserade gränssnitt. Till exempel, istället för ett enda Animal-gränssnitt med fly(), swim() och run(), skapa separata Flyable, Swimmable och Runnable-gränssnitt. Detta minskar koppling och gör klasser enklare att implementera och underhålla.
Vad är Strategy-mönstret och när skulle du använda det?
Strategy-mönstret definierar en familj av utbytbara algoritmer inkapslade bakom ett gemensamt gränssnitt, vilket gör det möjligt att välja en klasss beteende vid körning. Använd det när du har flera sätt att utföra en operation och vill undvika villkorssatser. Till exempel kan en PaymentProcessor ta emot olika strategier (CreditCardStrategy, PayPalStrategy) och delegera betalningslogiken till den injicerade strategin utan att ändra sin egen kod.
Vilken algoritm skulle du använda för att effektivt söka efter ord horisontellt och vertikalt i en teckenmatris?
Iterera över varje rad för horisontella träffar och varje kolumn för vertikala träffar genom att läsa tecken uppifrån och ned. För varje startposition kontrollerar du om de återstående tecknen matchar målorden. Att använda ett HashSet för ordlistan ger O(1)-uppslag per kandidatsträng, vilket håller den totala komplexiteten nära O(R × C × L) där L är maximal ordlängd.
Hur avduplicerar du ord i ordlistan innan bearbetning, och varför spelar det roll för resultatet?
Konvertera ordlistan till ett Set innan sökning så att varje unikt ord söks exakt en gång. Utan avduplicering skulle ett ord som förekommer flera gånger i indata kunna blåsa upp sitt antal förekomster, vilket bryter mot kravet att upprepade poster i ordlistan bara räknas en gång.
Hur validerar du en teckenmatris på backend-sidan innan du bearbetar den?
Kontrollera att matrisen inte är null eller tom, att den innehåller högst 64 rader och att varje rad innehåller högst 64 tecken. Verifiera också att alla rader har samma längd för att säkerställa att matrisen är rektangulär. Returnera ett 400-fel med ett beskrivande meddelande om något krav bryts.
Vilka grundläggande Git-kommandon använder du regelbundet, och vad är en branch, merge och pull request?
Vanliga kommandon: git add . (stagea ändringar), git commit -m "meddelande" (committa), git push (skicka till remote), git checkout -b <branch> (skapa och byt gren), git pull (hämta fjärrändringar), git merge <branch> (sammanfoga grenar). En branch är en oberoende utvecklingslinje som undviker att påverka huvudkoden. En merge integrerar en grens ändringar i en annan. En pull request är en formell merge-begäran som används för kodgranskning innan integration, vanlig i Git Flow och Feature Branching-arbetsflöden.
Vad bör en README-fil innehålla för en take-home kodningsutmaning?
Den bör täcka: projektets syfte och översikt, förutsättningar och miljöinställning, steg för att bygga och köra både backend och frontend, hur man kör tester, samt eventuella kända begränsningar eller designbeslut. Tydliga, minimala instruktioner säkerställer att granskarna kan utvärdera arbetet utan att behöva kontakta kandidaten.
Vad är Dependency Injection och varför är det användbart?
Dependency Injection (DI) är ett designmönster där ett objekt eller en funktion tar emot sina beroenden från en extern källa istället för att skapa dem själv. Det frikopplar komponenter från sina konkreta beroenden, vilket gör dem enklare att testa (injicera mocks) och enklare att ändra (byt implementering utan att röra konsumentkoden). I React är proppar och Context Provider vardagliga DI-exempel.
Jämför objektorienterad programmering (OOP) med funktionell programmering (FP). Vilka är de viktigaste avvägningarna?
OOP modellerar ett system som interagerande objekt som kapslar in muterbart tillstånd med klasser, arv och polymorfism. FP modellerar beräkningar som en pipeline av rena funktioner över oföränderlig data med hjälp av högre ordningens funktioner och utan sidoeffekter. OOP är intuitivt för domänmodellering; FP utmärker sig för datatransformation, testbarhet (rena funktioner är trivialt enhetstestbara) och säkerhet i concurrency tack vare immutabilitet.
Vilket problem löser Business Delegate-mönstret och vad är dess huvudsakliga fördel?
Presentationslagrets komponenter interagerar ofta direkt med fjärrtjänster, vilket exponerar implementationsdetaljer och skapar tätt koppling. En Business Delegate fungerar som en klientsidesproxy som kapslar in tjänstuppsökning, åtkomst och undantagsöversättning, döljer implementationsdetaljer och minskar påverkan av tjänsteförändringar på klientkoden.
Vad är Session Facade-mönstret och varför bör man undvika att skapa en fasad per användningsfall?
Session Facade är ett JEE-mönster där en session bean fungerar som den enda ingångspunkten för en grupp relaterade affärsobjektsinteraktioner, hanterar deras livscykel och döljer komplexitet från klienten. Att skapa en fasad per användningsfall leder till alltför fin granularitet och komplexitet; det är bättre att gruppera relaterade användningsfall i en enda Session Facade.
Vad är Composite Entity-mönstret och vilka roller har ett grovkornigt objekt respektive ett beroende objekt?
Composite Entity är ett JEE-mönster som modellerar och hanterar en uppsättning relaterade objekt som en enhet för att minska koppling och förbättra underhållbarheten. Ett grovkornigt objekt innehåller beroende objekt, hanterar deras livscykel och har sin egen; beroende objekt (ofta finkorniga) förlitar sig på det grovkorniga objektet och kan bilda en trädstruktur.
Vad är Transfer Object Assembler-mönstret och när är det användbart?
Transfer Object Assembler bygger ett sammansatt transferobjekt genom att aggregera data från flera tjänster och komponenter och returnerar det till klienten i en enda operation. Det är användbart när man vill centralisera affärslogik, skapa en komplex presentationsmodell utan att belasta klienten, eller minska kopplingen mellan klienten och företagskomponenter.
Vad är Service Locator-mönstret i JEE och vilka är dess huvudansvar?
Service Locator är ett JEE-mönster som tillhandahåller en enhetlig och transparent mekanism för att lokalisera tjänster och komponenter som JDBC-datakällor eller asynkrona tjänster. Det centraliserar och kapslar in uppsökningsmekanismen – inklusive skapandet av den initiala kontexten – så att klienter inte behöver känna till detaljerna om hur tjänster hittas eller nås.

Spring och Spring Boot

Vilka vanliga Spring-annotationer används för att definiera beans, och vad är dependency injection?
I Spring markerar @RestController REST-endpoint-klasser, @Service markerar affärslogikkomponenter och @Repository markerar dataåtkomstkomponenter. Dessa annotationer registrerar klasserna som beans som hanteras av Springs IoC-container. Dependency injection låter containern automatiskt koppla ihop beans — vanligtvis via konstruktorinjektion eller @Autowired — vilket eliminerar behovet av att manuellt instansiera beroenden med new.
Hur implementerar man in-memory-cache med TTL i Spring Boot?
Aktivera caching med @EnableCaching och annotera metoden som hämtar värdet med @Cacheable. För TTL-kontroll konfigureras en CacheManager (t.ex. CaffeineCacheManager) med expireAfterWrite. Exempel: Caffeine.newBuilder().expireAfterWrite(30, TimeUnit.MINUTES).build() kopplad till managern. Spring serverar sedan cachade värden automatiskt tills posten löper ut.
Hur implementerar man ett cache-fallback-mönster när en extern tjänst är otillgänglig?
Fånga undantaget som kastas av det externa anropet och försök sedan läsa det senast kända värdet från cachen. Om ett cachat värde finns returneras det och en varning loggas. Om inget cachat värde finns propageras ett lämpligt HTTP-fel (t.ex. 503 Service Unavailable). Detta frikopplar anroparen från tillfälliga fel i nedströmstjänster.
Hur konfigurerar man automatisk retry-logik för ett externt tjänstanrop i Spring Boot?
Lägg till spring-retry i klassvägen och annotera konfigurationsklassen med @EnableRetry. Annotera sedan metoden med @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 500)). Efter att försöken är slut kan en @Recover-metod returnera ett fallback-värde eller kasta ett specifikt undantag. Detta håller retry-logiken separerad från affärslogiken.
Hur implementerar man rate limiting (t.ex. 3 requests per minut) i ett Spring Boot REST API?
Ett vanligt tillvägagångssätt är att använda biblioteket Bucket4j, som implementerar token-bucket-algoritmen. Skapa en Bucket med en påfyllning på 3 tokens per minut och applicera den i ett servlet-filter eller Spring-interceptor. När bucketen är tom returneras HTTP 429 Too Many Requests med ett beskrivande meddelande. För multi-replika-driftsättningar kan bucketen backas av ett distribuerat lager som Redis.
Hur implementerar man global HTTP-felhantering i Spring Boot?
Skapa en klass annoterad med @RestControllerAdvice och definiera @ExceptionHandler-metoder för specifika undantagstyper. Varje hanterare returnerar en ResponseEntity med lämplig HTTP-status (4XX eller 5XX) och en strukturerad felfältskropp med meddelande, tidsstämpel och sökväg. Detta centraliserar felformatering och håller kontrollerna rena.
Hur implementerar man paginerade frågor i Spring Data JPA?
Repository-metoder tar emot en Pageable-parameter och returnerar Page<T>. Kontrollern accepterar frågeparametrarna page och size, konstruerar en PageRequest och skickar den till repositoryt. Det returnerade Page-objektet innehåller innehållslistan, totalt antal element och totalt antal sidor, som kan mappas till en DTO för API-svaret. Exempel: repository.findAll(PageRequest.of(page, size, Sort.by("createdAt").descending())).
Vad är skillnaden mellan Spring WebFlux och Spring MVC, och när väljer man WebFlux?
Spring MVC använder en tråd-per-request-modell och blockerar vid I/O; Spring WebFlux använder en icke-blockerande event-loop-modell (Project Reactor) och hanterar många samtidiga requests med färre trådar. WebFlux är fördelaktigt när tjänsten gör många nedströms HTTP- eller databasanrop med hög concurrency och låga latensskrav. För enkla CRUD-API:er eller CPU-intensiva operationer är Spring MVC vanligtvis enklare och tillräckligt.
Hur genererar man OpenAPI/Swagger-dokumentation för ett Spring Boot REST API?
Lägg till beroendet springdoc-openapi-starter-webmvc-ui. Spring Boot auto-konfigurerar /v3/api-docs-endpointen och Swagger UI på /swagger-ui.html. Berika dokumentationen med @Operation-, @Parameter- och @ApiResponse-annotationer på kontrollerna. Detta producerar interaktiv, alltid aktuell dokumentation som klienter kan använda för att testa endpoints direkt i webbläsaren.
Hur testar man scenarier för externt tjänstefel i ett Spring Boot-enhetstest?
Använd Mockito för att mocka den externa tjänsteklienten och konfigurera den att kasta ett undantag med when(...).thenThrow(...). Verifiera att retry-mekanismen aktiveras det förväntade antalet gånger och att fallback-värdet (cache eller felsvar) returneras. För integrationstester kan WireMock stubba HTTP-endpoints för att returnera felsvar och ge en mer realistisk felsimuling.

Samtidighet och JVM

Vad är en tråd i Java, vilka samtidighetsproblem kan uppstå och hur hanterar du dem?
En tråd är en oberoende exekveringsenhet inom en process. Javas flertrådningsmodell förbättrar effektiviteten men introducerar risker som race conditions — när flera trådar modifierar delad data samtidigt och ger oförutsägbara resultat. Lösningar inkluderar synchronized-block, explicita lås (ReentrantLock) och samtidiga datastrukturer från java.util.concurrent (t.ex. ConcurrentHashMap, AtomicInteger).
Vad är CompletableFuture i Java och hur kedjar man ihop flera asynkrona operationer?
CompletableFuture är en klass som möjliggör asynkron, icke-blockerande exekvering och komposition av uppgifter. Man kan kedja operationer med metoder som thenApply() (transformera resultat), thenCompose() (flat-map en annan future) och thenCombine() (kombinera två futures). Den integreras med lambdas och erbjuder felhantering via exceptionally() eller handle(), idealisk för I/O-tunga arbetsflöden.
Hur implementerar man asynkron anropsloggning i Spring Boot utan att påverka huvudsvarstiden?
Annotera loggningsmetoden med @Async och aktivera det med @EnableAsync på en konfigurationsklass. Anroparen skjuter och glömmer — huvudtråden returnerar omedelbart medan loggningen körs i en separat trådpool. Omslut den asynkrona metoden i ett try-catch så att eventuella persistensfel tystas ned och aldrig propageras till anroparen.
Vad är Service Activator-mönstret och vilket problem löser det?
Service Activator är ett JEE-mönster som möjliggör asynkront anrop av en eller flera tjänster, vanligtvis implementerat via meddelandeinfrastruktur som JMS. Det adresserar behovet hos applikationer som inte kan vänta på ett synkront svar – till exempel när en affärsuppgift består av flera deltask – genom att tillhandahålla en modell för att skicka en förfrågan och hantera svaret när det är tillgängligt.