Online assessment (two medium DSA problems: HashMap and 2D DP), HackerRank-style coding, then Java/Spring Boot, system design and team-fit rounds. Core Java is probed broadly: collections internals, concurrency, Java 8/11/17/21 features, plus Docker, CI/CD, Kafka and SQL. Reported from candidate write-ups; locations vary.
Track
Q105
Can `System.exit` take a negative value, and what exactly happens when it is called?
Yes, the argument is any int; by convention 0 means success and non-zero means failure. The operating system keeps only the low 8 bits on Linux and macOS, so System.exit(-1) is seen by the shell as 255.
System.exit starts the shutdown sequence: registered shutdown hooks run (concurrently, in unspecified order), then the JVM halts. It never returns normally.
finally blocks and try-with-resources closing on the calling thread are skipped; use hooks or a graceful-shutdown path instead.
Runtime.halt skips hooks entirely and should be reserved for unrecoverable states.
In Spring Boot prefer SpringApplication.exit(context, () -> code) so beans are closed before the exit code is returned.
Group by LTS release: Java 8 brought the functional style, 11 modernised the standard library, 17 added data-modelling features, and 21 added virtual threads and pattern matching for switch.
9 to 11: modules, List.of/Map.of, var (10), HttpClient, new String methods (isBlank, strip, lines, repeat), single-file source launch.
12 to 17: switch expressions (14), text blocks (15), records and instanceof patterns (16), sealed classes (17), strong encapsulation of JDK internals.
18 to 21: virtual threads, record patterns and pattern matching for switch, sequenced collections.
Backend use: records for DTOs, text blocks for SQL/JSON, sealed types plus pattern switch for exhaustive domain results, virtual threads for blocking I/O services. Spring Boot 3 requires Java 17.
⚠ Follow-up traps
Are records a drop-in replacement for JPA entities? No: entities need a no-arg constructor, mutability for dirty checking and proxying.
Which versions are LTS? 8, 11, 17, 21 (and 25 later); others are six-month releases.
Make the class final (or constructors private), all fields private final, no setters, defensively copy mutable inputs in the constructor and outputs in getters, and don't leak this during construction.
public final class Order { private final List<String> items; public Order(List<String> items) { this.items = List.copyOf(items); } public List<String> items() { return items; }}
⚠ Follow-up traps
Is Collections.unmodifiableList(list) enough? No; it's a view, mutations of the original show through. Use List.copyOf.
Why final class? To stop a subclass from adding mutability.
Comparable defines a class's single natural order (compareTo); Comparator is an external, pluggable ordering (Comparator.comparing(...).thenComparing(...)), allowing multiple orders without modifying the class.
⚠ Follow-up traps
Null handling? Use Comparator.nullsFirst/nullsLast.
More threads than 20 gain nothing, since they wait for connections. Size the worker pool at about 20 (or slightly above with a short connection timeout) and bound the queue. CPU count (8) is irrelevant for IO waits, but too many threads add contention and memory. Verify with load tests and queue-wait metrics.
⚠ Follow-up traps
What if requests also call a 3rd party API? Separate pool for it with its own limit.
Runnable.run() returns nothing and cannot throw checked exceptions; Callable<V>.call() returns a value and may throw Exception. A Future is the handle you get back from ExecutorService.submit, used to wait for the result, cancel or inspect the task.
An exception thrown by the task is captured in the Future and rethrown from get() wrapped in ExecutionException; execute(Runnable) instead lets it reach the thread's uncaught-exception handler.
A Thread only accepts a Runnable; to run a Callable outside an executor wrap it in a FutureTask (which implements both).
ExecutorService ex = Executors.newSingleThreadExecutor();Future<Integer> f = ex.submit(() -> { if (true) throw new Exception("boom"); return 1; }); // Callabletry { f.get(); } catch (ExecutionException e) { System.out.println(e.getCause().getMessage()); } // boomex.shutdown();
⚠ Follow-up traps
Which overload does a lambda that throws a checked exception pick?Callable, because Runnable cannot throw it; the compiler infers from the body.
Does Future.get() block forever? Yes unless you use get(timeout, unit); prefer timeouts or CompletableFuture composition.
Does cancel(true) stop the task? It only interrupts the thread; code that ignores interruption keeps running.
It declares resources in the try header; each is closed automatically in reverse order of declaration when the block exits, normally or exceptionally. Resources must implement AutoCloseable.
Closeable extends AutoCloseable; its close() throws IOException and is idempotent by contract.
Java 9+ allows an effectively final existing variable: try (r) { ... }.
Null resources are skipped without NPE.
⚠ Follow-up traps
Order of closing? Reverse of declaration.
Can you use a non-final variable in the header? No, it must be final or effectively final (Java 9+).
HashMap keeps an array of buckets (Node<K,V>[] table). The key's hashCode() is spread, masked to an index, and the entry is stored in that bucket as a linked list (or a red-black tree when large). Lookup compares hash then equals.
Index = (n - 1) & hash where n is a power of two.
Spread function: h ^ (h >>> 16) mixes high bits into low bits.
Default capacity 16, load factor 0.75.
Allows one null key and many null values; not thread-safe.
⚠ Follow-up traps
Why is capacity always a power of two? So the index is a cheap bitmask instead of a modulo, and resize can split buckets without rehashing.
Which is compared first, equals or hash? Stored hash first (cheap), then reference ==, then equals.
Java 7 used 16 lock-striped Segments (each a ReentrantLock over a mini hash table). Java 8+ removed segments: an empty bucket is filled with a lock-free CAS; otherwise it uses synchronized on the bucket's first node. Resizing is cooperative (multiple threads migrate bins), and bins treeify like HashMap.
Reads are lock-free (volatile fields); size() uses striped CounterCells (like LongAdder), so it is an estimate under concurrency.
No null keys or values.
⚠ Follow-up traps
Is if (!m.containsKey(k)) m.put(k, v) safe? No, use putIfAbsent/computeIfAbsent.
Can computeIfAbsent call other map operations in its function? No; it can deadlock or throw IllegalStateException (recursive update).
A Map has no order of its own. Stream the entries, sort them with Map.Entry.comparingByValue(), and collect into a LinkedHashMap, which preserves insertion order. TreeMap orders by key only, because its comparator receives keys.
The JVM splits memory into the heap (objects, shared), per-thread stacks, Metaspace (class metadata, native memory), the code cache (JIT output) and other native areas (direct buffers, thread structures, GC data).
Heap: young generation (Eden + two survivors) and old generation in generational collectors.
Each thread has a stack of frames holding locals, operand stack and return info; -Xss sets its size.
Metaspace replaced PermGen in Java 8 and lives in native memory.
⚠ Follow-up traps
Is -Xmx the total process memory? No. Metaspace, stacks, code cache, direct buffers and GC structures come on top of it.
Are strings in PermGen? Not since Java 7; the string pool lives in the heap.
-D sets a system property for the application, -X options are non-standard but widely supported JVM settings (-Xms, -Xmx, -Xss), and -XX options are advanced, implementation-specific flags that may change or disappear between releases.
Boolean -XX flags: -XX:+UseG1GC enables, -XX:-UseG1GC disables. Value flags: -XX:MaxMetaspaceSize=512m.
Some need unlocking: -XX:+UnlockExperimentalVMOptions or -XX:+UnlockDiagnosticVMOptions.
Inspect effective values with java -XX:+PrintFlagsFinal -version; in containers prefer -XX:MaxRAMPercentage over a fixed -Xmx.
Since Java 9 logging uses -Xlog (e.g. -Xlog:gc*) instead of old -XX:+PrintGC flags.
Future only supports blocking get(). CompletableFuture is a composable promise: chain stages with thenApply, thenCompose, thenCombine, handle errors with exceptionally/handle, and complete manually with complete.
supplyAsync/runAsync run on ForkJoinPool.commonPool() by default; pass an Executor for IO-bound work.
allOf/anyOf coordinate several futures.
⚠ Follow-up traps
thenApply or thenCompose for a function returning a future?thenCompose, else you get CompletableFuture<CompletableFuture<T>>.
Does supplyAsync use a dedicated pool? No, the shared common pool unless given an executor.
Make the constructor private and expose one instance created safely. The common options are eager initialization, double-checked locking with a volatile field, the initialization-on-demand holder, or a single-element enum.
public final class Config { private static volatile Config instance; private Config() {} public static Config get() { Config c = instance; if (c == null) { synchronized (Config.class) { c = instance; if (c == null) instance = c = new Config(); } } return c; }}
Without volatile, another thread may see a non-null but partially constructed object because of instruction reordering.
Eager static final initialization is simplest when construction is cheap.
⚠ Follow-up traps
Is synchronized on get() wrong? Correct but takes a lock on every call; double-checked locking avoids that.
Does volatile matter in Java 5+? Yes, it prevents unsafe publication in double-checked locking.
Spring Framework is the core programming model (dependency injection, AOP, transactions, MVC, data access) that you configure yourself. Spring Boot is an opinionated layer on top that removes the configuration: starters, auto-configuration, an embedded server and production tooling.
Starters bundle compatible dependencies (spring-boot-starter-web), with versions managed by the parent/BOM.
Auto-configuration registers beans conditionally (@ConditionalOnClass, @ConditionalOnMissingBean) based on the classpath and properties; your own beans back it off.
Embedded Tomcat/Jetty/Netty produces a runnable fat jar instead of a WAR deployed to an external server.
Actuator, externalized configuration, profiles and graceful shutdown are Boot features.
Boot 3 builds on Spring 6: Java 17 minimum and the jakarta.* namespace.
⚠ Follow-up traps
Is Boot a replacement for Spring? No, a Boot app is a Spring app; remove Boot and the same beans still work with manual configuration.
Does Boot slow startup? Classpath scanning and auto-configuration add cost; measure, and consider lazy initialization or AOT/native.
Use RestClient (Spring 6.1 / Boot 3.2+) for blocking calls in new code; RestTemplate still works but is in maintenance mode, and WebClient is for reactive or non-blocking composition.
RestClient client = RestClient.builder().baseUrl("https://api.example.com").build();User u = client.get().uri("/users/{id}", 42).retrieve().body(User.class);User legacy = new RestTemplate().getForObject("https://api.example.com/users/{id}", User.class, 42);
4xx/5xx responses raise HttpClientErrorException / HttpServerErrorException unless you handle status explicitly via onStatus.
Neither client sets sensible timeouts for you; configure connect and read timeouts on the request factory.
Inject the auto-configured builder (RestClient.Builder) so metrics, tracing and message converters apply.
⚠ Follow-up traps
Is RestTemplate thread-safe? Yes, create one and reuse it.
Where do you add retries and circuit breaking? Around the call with Resilience4j, not inside the client defaults.
Group by the month extracted from the joining date and count rows. Include the year (or use date_trunc) unless you really want all years merged.
-- PostgreSQLSELECT EXTRACT(MONTH FROM doj) AS month, COUNT(*) AS employeesFROM employeeGROUP BY EXTRACT(MONTH FROM doj)ORDER BY month;-- per calendar monthSELECT date_trunc('month', doj) AS month, COUNT(*) FROM employee GROUP BY 1 ORDER BY 1;
MySQL uses MONTH(doj). Months with no joiners are absent from the result; to list them, left join against generate_series (PostgreSQL) or a calendar table.
⚠ Follow-up traps
Can a function on doj use an index? Not a plain index on doj; use a range on doj for filtering or an expression index on the extracted value.
COUNT(*) or COUNT(doj)?COUNT(doj) skips rows with a null date.
Order is guaranteed only within a partition. The producer picks the partition by key hash, so all events for a key stay ordered. Within a consumer group, each partition is consumed by exactly one member.
Max parallelism of a group = number of partitions; extra consumers sit idle.
Rebalances reassign partitions on member join/leave; cooperative-sticky assignor reduces stop-the-world.
Offsets committed per group/partition; commit after processing for at-least-once.
Increasing partition count later changes key-to-partition mapping and breaks per-key ordering for new data.
⚠ Follow-up traps
Is there global ordering in a topic? No, only per partition; one partition gives total order but no parallelism.
What breaks ordering on producer retries? With max.in.flight.requests.per.connection > 5, or without idempotence, retries can reorder; idempotent producer preserves order up to 5 in flight.
A @KafkaListener method runs inside a listener container that polls a KafkaConsumer. Each partition of a topic is assigned to exactly one consumer in a group, so messages are load-balanced across the group and ordered per partition.
concurrency above the partition count leaves threads idle.
Different group ids each receive every message (pub/sub); the same group id shares the load.
Offsets are committed after processing (manual or batch ack mode) for at-least-once delivery, so handlers must be idempotent.
Configure DefaultErrorHandler with a backoff and DeadLetterPublishingRecoverer, and wrap deserializers in ErrorHandlingDeserializer so a poison message does not loop forever.
⚠ Follow-up traps
Why is ordering lost across partitions? Order is guaranteed only within a partition; key by the entity id to keep related events together.
What if processing exceeds max.poll.interval.ms? The consumer is evicted and a rebalance starts.
Partition keys across nodes with consistent hashing (virtual nodes), replicate for availability, evict with LRU/LFU, and let clients or a proxy route requests.