cswork / libraryOpen practice workspace

Interview guides · Adform

Adform Software Engineer Interview Guide

Adform Software Engineer interview guide: 4 reported rounds, Java, Spring, data structure and system design questions, and a 7-day prep plan.

The 60-second brief

Adform is an advertising technology company, and candidates describe the Software Engineer role as work on its programmatic advertising platform. The reported work covers services that process large volumes of data in real time, microservices, data pipelines, integrations with external APIs and the gradual refactoring of older components. Engineers also write and review code, work with product managers on requirements and work with operations teams on deployments.

This guide is for Software Engineer candidates preparing for Adform. It covers the 4 reported rounds, the reported questions on Java, Spring, OOP, strings, linked lists, arrays, stacks, high-traffic design, microservices, concurrency and JPA, and the behavioral prompts candidates mention. The requirements candidates describe name Java or .NET, data structures and algorithms, and microservices, so most of the preparation below assumes a Java stack and points out where a .NET answer differs.

Candidates report 4 rounds over roughly 3-5 weeks: Baseline Skills Assessment, Deep-Dive Technical Discussions, Behavioral Rounds and Final Panels. Reported questions are not tied to a specific round, so prepare every question type for every stage.

Focus areas

  • Data Structures & Algorithms (DSA)
  • Java
  • OOP Concepts

Overview

The reported question mix is concrete. One group tests language and framework knowledge: Spring versus Spring Boot, the principles of object-oriented programming, multithreading and inter-thread communication in Java, String versus StringBuilder versus StringBuffer, and managed versus unmanaged code. A second group is classic data-structure work: the intersection of two linked lists, a palindrome check, an anagram check, swapping two numbers without a temporary variable, and general array and stack problems. A third group covers design and architecture: a high-traffic system on a whiteboard, microservices experience, optimizing for high concurrency, and JPA.

That mix means two kinds of preparation. For the language and framework questions, a memorised definition is not enough; you need the mechanism behind each answer, for example why String is immutable, what Spring Boot auto-configuration actually removes from a Spring setup, or what happens to a shared counter when two threads increment it. For the data-structure questions, candidates describe implementing solutions by hand, so practise writing them without library shortcuts, stating complexity, and listing edge cases before you run the code.

The role itself is about services that handle heavy, real-time data, so design answers should talk about request rate, latency targets, where state lives and what happens when a dependency is slow. Pick one system you have built and be ready to explain its architecture, the scaling or reliability problem you hit and how you fixed it. The same project feeds the microservices, JPA, concurrency and behavioral questions, so choosing it early saves preparation time.

Prior experience in AdTech or programmatic advertising is described as a differentiator, not a requirement. If you lack it, spend a little time learning the vocabulary and the shape of the problem (high request volume, tight response budgets, large event streams) so that the question about experience with programmatic advertising and your motivation for applying both get specific answers.

The Adform interview process

Candidates describe 4 stages, from a baseline skills check to final panels with senior engineers and managers. Use the descriptions below to decide what to rehearse for each one.

Baseline Skills Assessment

Candidates describe this first stage as an initial evaluation of your fundamental technical skills. The format is not described further, so prepare for both short conceptual answers and writing small pieces of code by hand. Fundamentals here means the language you claim as your main one, object-oriented design and the basic data structures: arrays, linked lists, strings and stacks. Answer out loud, show the mechanism rather than the definition, and state complexity and edge cases whenever you write code.

What to demonstrate

  • Command of the core language and OOP concepts you list on your resume
  • Ability to write correct basic data-structure code and state its complexity
  • Clear explanation of simple concepts without memorised phrasing

How to prepare

  • Rehearse the short conceptual questions in this guide: Spring versus Spring Boot, the principles of OOP, and String versus StringBuilder versus StringBuffer.
  • Hand-write the string and list problems (palindrome, anagram, linked-list intersection) and say the time and space cost of each.
  • Refresh Java basics you may use daily without explaining: equals and hashCode, immutability, pass-by-value for references, and the collections you reach for.

Deep-Dive Technical Discussions

Candidates describe in-depth technical conversations with engineering leads. A conversation is less a single puzzle than a walk through how things work, so expect to go from a concept to its internals and then to a design you would defend. The role requirements and the reported questions point to Java concurrency, framework internals (Spring, Spring Boot, JPA), microservices and high-traffic design. Prepare one system you built so you can move from the whiteboard to concrete experience.

What to demonstrate

  • Depth of knowledge in the primary stack, beyond everyday usage
  • Reasoning about concurrency, throughput and latency in service design
  • Ability to discuss trade-offs in an architecture you chose

How to prepare

  • Build a concurrency cheat sheet: synchronized, volatile, locks, ExecutorService, ConcurrentHashMap, BlockingQueue, wait and notify, and what each protects against.
  • Write a one-page design for a high-frequency, low-latency request service: load balancing, caching, database choice, how services talk to each other, and what happens when one is slow.
  • Prepare to explain the Spring bean lifecycle and what Spring Boot adds to it, and your experience with JPA, including lazy loading and transactions.

Behavioral Rounds

Candidates describe these rounds as discussions of your project experience and how you handle feedback. The reported prompts include why you chose to apply, how you work in a team, your experience with clients in programmatic advertising, and your previous titles and responsibilities. Treat it as a structured conversation about real work: pick projects you can describe in depth, and have one story where feedback changed your approach.

What to demonstrate

  • Ownership of the projects on your resume and the decisions you made in them
  • How you take and act on feedback
  • Fit for collaboration with product managers and operations teams, which the role description includes

How to prepare

  • Write three project stories, each with the situation, your specific decision, a trade-off and a result you can verify.
  • Prepare one feedback story: what you were told, whether you agreed, what you changed and what happened next.
  • Write a short, specific answer to why Adform, built on what you have learned about programmatic advertising and the engineering problems of high-volume systems.

Final Panels

Candidates describe final interviews with panels of senior engineers and managers. A mixed panel means you may explain the same system at two levels: technical depth for the engineers and impact and trade-offs for the managers. Be ready to defend your choices if the panel challenges them, so rehearse staying calm and re-deriving a decision when someone pushes back.

What to demonstrate

  • Clear communication of technical decisions to a mixed audience
  • Explaining your design choices clearly and defending them when challenged
  • How you respond when a design choice is questioned

How to prepare

  • Rehearse a 2-level walkthrough of your best project: a short business summary, then the architecture, the failure modes and the trade-offs.
  • Run a full mock where someone challenges your high-traffic design with a new constraint, such as ten times the traffic or a slow dependency, and practise adjusting aloud.
  • Prepare two questions about the team's current scaling challenge or technical debt to ask at the end.

Where candidates lose points

Reciting definitions of Spring versus Spring Boot or String versus StringBuilder versus StringBuffer without the mechanism

Attach a reason to every point: String is immutable and shared through the string pool, StringBuffer methods are synchronized while StringBuilder's are not, and Spring Boot adds auto-configuration, starters and an embedded server on top of Spring. Then say which one you would choose and why.

Coding the palindrome or anagram check with no stated assumptions or edge cases

Ask about case, spaces, punctuation, Unicode and null or empty input before you write. For anagrams, compare the sorted approach at O(n log n) with a character-count array or map at O(n), and say which you would use.

Drawing boxes for the high-traffic design before fixing the scope

Ask for request rate, latency target and consistency needs first. Separate the read path from the write path, place caching and load balancing deliberately, and name the first bottleneck yourself. Practise adjusting when the constraint changes, such as ten times the traffic.

Claiming experience with microservices, concurrency or JPA and being unable to go one layer deeper

For each claim, prepare one concrete case: a service boundary you drew, a race condition you fixed, an N+1 query you found. If you only used a technology lightly, say so and describe what you know about how it works.

Giving a generic answer to why Adform and to experience in programmatic advertising

Learn the basics of the programmatic space and connect them to your own work. If you have no ad-tech background, say so briefly and bridge with the closest real experience instead of bluffing terms.

Questions & practice

Explain the difference between Spring and SpringBoot.

Medium · Technical Foundations and Coding

Approach

  1. Start with the layering: the Spring Framework is the core (IoC container and ApplicationContext, dependency injection, AOP, transaction management, Spring MVC), and Spring Boot is an opinionated layer on top of it, not a replacement.
  2. Name what Boot adds: auto-configuration driven by @Conditional annotations (@ConditionalOnClass, @ConditionalOnMissingBean), starter POMs with curated dependency versions, an embedded server (Tomcat by default for spring-boot-starter-web), an executable jar, externalised config in application.properties/yml, and Actuator endpoints.
  3. Unpack @SpringBootApplication: it combines @SpringBootConfiguration, @EnableAutoConfiguration and @ComponentScan, so scanning starts from that class's package.
  4. State the trade-off: Boot removes wiring code but hides decisions, so show you can see and override them: run with --debug for the condition evaluation report, define your own bean so @ConditionalOnMissingBean backs off, or exclude an auto-configuration class.
  5. Have the bean lifecycle ready, because it is the same container in both: instantiate, inject, Aware callbacks, BeanPostProcessor before-init, @PostConstruct / afterPropertiesSet / init-method, after-init (where AOP proxies are created), then @PreDestroy on shutdown.

Follow-up

  • Walk through the lifecycle of a Spring bean. At which step does a @Transactional proxy get created?
  • Why does @Transactional have no effect when a method calls another method on the same class?
  • Boot configured a DataSource you did not ask for. How do you find out why, and how do you replace it?

Describe the core principles of OOPs (Object-Oriented Programming).

Medium · Technical Foundations and Coding

Approach

  1. Name the four principles and give a Java example for each: encapsulation (private fields behind methods that guard invariants), abstraction (callers depend on an interface, not the implementation), inheritance (an is-a relationship and code reuse), polymorphism (one call site, behaviour chosen by the runtime type).
  2. Split polymorphism into compile-time overloading (resolved from the static argument types) and runtime overriding (dynamic dispatch). In Java, instance methods that are not static, private or final can be overridden.
  3. Show judgement as well as definitions: prefer composition over inheritance, and use the Liskov substitution principle to explain why Square extends Rectangle breaks callers that set width and height independently.
  4. Compare an interface with an abstract class: since Java 8 interfaces can have default and static methods, but only an abstract class holds instance state and constructors, and a class extends one class while implementing many interfaces.
  5. Point out a common encapsulation leak: a getter that returns an internal mutable List. Fix it with a defensive copy or List.copyOf / Collections.unmodifiableList.

Follow-up

  • Since Java 8, when would you still pick an abstract class over an interface?
  • Is method overloading really polymorphism? Which overload gets called when the argument is null?
  • How would you map SOLID onto a class you wrote recently?

How do you handle multithreading and inter-thread communication in Java?

Medium · Technical Foundations and Coding

Approach

  1. Start with how you run work: an ExecutorService (a ThreadPoolExecutor with a bounded queue and a rejection policy) rather than raw Threads, Callable plus Future for results, and CompletableFuture to compose async steps.
  2. Cover shared-state tools and what each guarantees. synchronized gives mutual exclusion plus a happens-before edge. volatile gives visibility but not atomicity, so count++ still races. AtomicInteger and LongAdder use CAS. ReentrantLock adds tryLock with timeout, fairness and multiple Conditions.
  3. For inter-thread communication, explain wait/notify/notifyAll. They must be called while holding the object's monitor, otherwise IllegalMonitorStateException. Always wait inside a while loop that re-checks the condition, because of spurious wakeups. Then say what you would use in practice: BlockingQueue put/take for producer-consumer, plus CountDownLatch, CyclicBarrier and Semaphore.
  4. Name the hazards and their fixes: race conditions, deadlock (acquire locks in one global order, or use tryLock with a timeout), livelock and starvation. Diagnose with a thread dump (jstack), which lists BLOCKED threads and reports Java-level deadlocks it finds.
  5. Close with cancellation: shutdown() vs shutdownNow() (the latter interrupts workers), awaitTermination, and when you catch InterruptedException without rethrowing it, restoring the flag with Thread.currentThread().interrupt().

Follow-up

  • Write a bounded producer-consumer buffer, first with wait/notifyAll and then with a BlockingQueue.
  • Why is a volatile int not safe as a shared counter, and what would you use instead?
  • Two threads lock the same two accounts in opposite order during transfers. How do you prevent the deadlock?

What are the differences between String, StringBuilder, and StringBuffer?

Medium · Technical Foundations and Coding

Approach

  1. String is immutable, and the class is final. Every apparent modification creates a new object. Immutability is what makes string-pool interning safe, makes strings safe HashMap keys (the hash is cached) and makes them thread-safe without locks.
  2. StringBuilder is a mutable, resizable buffer whose capacity roughly doubles when it fills, so appends are amortised O(1). It is not synchronised, so use it in single-threaded code, which covers almost every case.
  3. StringBuffer has the same API with synchronized methods. It dates from Java 1.0, and StringBuilder was added in Java 5 as its unsynchronised replacement. Per-method locking does not make a check-then-append sequence atomic, so it rarely buys real thread safety.
  4. Give the complexity argument: concatenating with + inside a loop copies the growing string each iteration, which is O(n^2) total, while one StringBuilder is O(n). A single-expression a + b + c is fine: before Java 9 javac compiled it to a StringBuilder chain, and since Java 9 it uses invokedynamic with StringConcatFactory.
  5. Add the equality and memory details: compare with equals, not ==. Literals are pooled, but new String("x") creates a separate object. Since Java 9, compact strings store Latin-1 text as one byte per character.

Follow-up

  • Why did Java make String immutable? What would break if it were mutable?
  • What does the compiler generate for a + b where b is an int, and does that change inside a loop?
  • Give a case where StringBuffer's synchronised methods still leave your code racy.

Explain managed vs. unmanaged code with examples.

Medium · Technical Foundations and Coding

Approach

  1. Define managed code as code that runs under a runtime. In .NET, C# compiles to CIL, the CLR JIT-compiles it to native code, and the runtime provides garbage collection, type safety, bounds checks and structured exceptions. JVM bytecode is the Java equivalent.
  2. Define unmanaged code as code compiled straight to machine code that runs outside the runtime, with memory managed by hand (malloc/free, new/delete). Examples: a C or C++ DLL, Win32 API calls, COM components.
  3. Explain the bridge between them. In .NET: P/Invoke with [DllImport] or [LibraryImport], COM interop, and argument marshaling, which costs something on every call. In Java: JNI, or the Foreign Function & Memory API, finalised in Java 22.
  4. Cover resource ownership: the GC frees managed memory but knows nothing about file handles, sockets or native buffers. So unmanaged resources are released deterministically with IDisposable and using (backed by SafeHandle or a finalizer) in .NET, and with AutoCloseable and try-with-resources in Java.
  5. State the trade-off: managed code buys memory safety and productivity at the cost of GC pauses and less control over layout. Unmanaged code gives full control and predictable latency but brings leaks, dangling pointers and buffer overflows. Note that C# 'unsafe' blocks are still managed code, just unverifiable.

Follow-up

  • Why does .NET need IDisposable if it already has a garbage collector?
  • What does marshaling a string to a native function actually do, and why can it be slow in a hot loop?
  • How is an unsafe C# block different from calling an unmanaged DLL?

Find the intersection of two linked lists.

Medium · Data Structures and Algorithms

Approach

  1. Clarify first: intersection usually means the lists share nodes from some point on (a Y shape), compared by reference, not by value. Also ask whether the lists may be modified, whether they can have no intersection, and whether cycles are possible.
  2. Baseline: put every node of list A in a HashSet, then walk B and return the first node already in the set. That is O(m + n) time and O(m) extra space.
  3. Constant-space version: compute both lengths, advance the longer list's pointer by the difference, then step both pointers together until they point at the same node or both reach null. O(m + n) time, O(1) space.
  4. Alternative O(1) trick: two pointers that jump to the other list's head when they reach the end. Each travels at most m + n steps, so they meet at the intersection or reach null together when there is none.
  5. Edge cases to test: either head null, intersection at the very first node, equal lengths, no intersection, and duplicate values in both lists (the reason you compare references, not values).

Follow-up

  • Prove the pointer-switching version terminates even when the lists do not intersect.
  • What changes if either list may contain a cycle?
  • If the question means common values in two sorted lists, how do you find them in one pass?

Write code to check for a palindrome.

Medium · Data Structures and Algorithms

Approach

  1. Clarify the definition first: is it case-sensitive, should spaces and punctuation be ignored, what about Unicode, and is the input a string or an integer?
  2. Main solution: two indices from both ends. Skip characters that fail Character.isLetterOrDigit, compare with Character.toLowerCase, and move inward. O(n) time, O(1) extra space.
  3. Mention the one-liner new StringBuilder(s).reverse().toString().equals(s) as a correct but O(n)-space baseline that does no normalisation. Then explain why you would write the two-pointer version.
  4. For an integer: negatives are never palindromes, and a number ending in 0 is one only if it is 0. Reverse the lower half of the digits until it is at least the remaining upper half, then compare. This avoids string conversion and overflow.
  5. Edge cases: an empty string and a single character are palindromes, a string of only punctuation is one under the ignore-non-alphanumerics rule, and the middle character of an odd-length string never needs comparing.

Follow-up

  • Check whether a singly linked list is a palindrome with O(1) extra space.
  • Return true if the string can become a palindrome by deleting at most one character.
  • Find the longest palindromic substring. What does expanding around each centre cost?

Identify if two strings are anagrams.

Medium · Data Structures and Algorithms

Approach

  1. Clarify the character set, case sensitivity and whether whitespace counts, then return false straight away if the lengths differ.
  2. For lowercase ASCII, use an int[26]: increment for each character of the first string, decrement for the second, then check that every slot is zero. O(n) time, O(1) space.
  3. For general Unicode, count code points (s.codePoints()) in a HashMap<Integer, Integer> so surrogate pairs are not split. That is O(n) time and O(k) space for k distinct characters.
  4. Sorting alternative: toCharArray, Arrays.sort both, then Arrays.equals. O(n log n) time and O(n) space. It is simple, but slower than counting.
  5. Test empty strings, equal strings, the same letters with different counts ('aab' vs 'abb') and mixed case.

Follow-up

  • Group a list of words into sets of anagrams. What do you use as the map key?
  • Find every start index in s where a substring is an anagram of p (a fixed-size sliding window with counts, O(n)).
  • Two very large strings arrive as streams. How do you decide whether they are anagrams?

Swap two numbers without using a temporary variable.

Medium · Data Structures and Algorithms

Approach

  1. Arithmetic version: a = a + b; b = a - b; a = a - b. In Java, int overflow wraps in two's complement, so the result is still correct even when a + b overflows. In C, signed overflow is undefined behaviour, so this is not safe there.
  2. XOR version: a ^= b; b ^= a; a ^= b. It works for any integral type and cannot overflow.
  3. Name the aliasing trap: if both operands are the same storage (swapping arr[i] and arr[j] with i == j), both tricks zero the value. Guard with if (i != j).
  4. Note the limits: XOR does not apply to double or float, and the arithmetic version loses precision on floating-point values.
  5. Add the Java point: arguments are passed by value, so a method swap(int a, int b) cannot change the caller's variables. Swap array elements in place or return the pair. In production a temporary variable is clearer and no slower, because the JIT allocates registers anyway.

Follow-up

  • Why does a swap(int a, int b) method in Java leave the caller's variables unchanged?
  • What happens with the XOR swap when both indices point at the same array element?
  • Does the addition version break when a + b overflows in Java, and would it in C?

Solve general problems involving arrays and stacks.

Medium · Data Structures and Algorithms

Approach

  1. Name the pattern before coding. For arrays: two pointers on sorted input, a sliding window for contiguous ranges, prefix sums for range queries, and binary search when the data is sorted. For stacks: matching brackets, expression evaluation, and the monotonic stack for 'next greater or smaller' problems.
  2. For searching an array for a target: an unsorted array costs O(n) per lookup, or O(1) average after building a HashSet in O(n). A sorted array allows binary search in O(log n). Compute mid = lo + (hi - lo) / 2 to avoid int overflow.
  3. For a balanced-brackets check: push each opening bracket, and on a closing bracket pop and compare. Fail on an empty stack or a mismatch, and require an empty stack at the end. O(n).
  4. For a monotonic stack (next greater element, daily temperatures, largest rectangle in a histogram): each index is pushed and popped at most once, so the whole pass is O(n) despite the nested loop.
  5. In Java, use ArrayDeque for push/pop/peek, not the legacy Stack class, which extends the synchronised Vector. Test empty and single-element inputs, duplicates and sums that need a long.

Follow-up

  • Design a stack that returns its minimum in O(1).
  • Compute the largest rectangle in a histogram in O(n).
  • Implement a queue using two stacks. What is the amortised cost per operation?

How do you optimize systems for high-concurrency environments?

Medium · System Design and Architecture

Approach

  1. Measure before changing anything. Find whether the limit is CPU, lock contention, I/O wait, connection-pool exhaustion or GC, using a profiler (JFR, async-profiler), thread dumps and p99 latency metrics. Use Little's Law (in-flight requests = arrival rate x time in system) to size concurrency.
  2. Reduce contention: prefer immutable objects and thread confinement, use ConcurrentHashMap instead of a synchronised map, LongAdder for hot counters, lock striping and shorter critical sections, and read-write or stamped locks for read-heavy state.
  3. Size pools for the workload. CPU-bound pools stay near the core count. I/O-bound work needs more threads or a non-blocking model (CompletableFuture, reactive, or Java 21 virtual threads). Keep queues bounded with an explicit rejection policy for back-pressure, and size the database connection pool deliberately.
  4. Fix the data layer: cache hot reads, batch writes, remove N+1 queries, and choose optimistic locking (a JPA @Version column, retry on conflict) when conflicts are rare or pessimistic locking when they are frequent.
  5. Verify with JMH for micro-level changes and a load test (Gatling, k6 or JMeter) for the system. Compare throughput and tail latency against the baseline, not just the average.

Follow-up

  • A service spends about 90% of each request waiting on I/O. How do you size its thread pool, and would virtual threads change that?
  • Two requests update the same row at once. When would you choose optimistic over pessimistic locking?
  • After adding threads, throughput drops. What would you look at first?

Explain why the owner filter ignores the listing index

Medium · composite index · query plans · sort elimination

The only index on resource is (tenant_id, status, updated_at DESC, resource_id DESC). A new endpoint returns one user's resources across all statuses, newest created first: WHERE tenant_id = $1 AND owner_user_id = $2 ORDER BY created_at DESC LIMIT 20. On a tenant with 2M rows it takes 900 ms and EXPLAIN shows a sort above a large scan. Explain precisely why the existing index cannot serve it, give the index that can, and state which of these the new index still will not help: owner_user_id alone across tenants; the same query ordered by updated_at. PostgreSQL 16.

Approach

  1. Separate the two jobs an index does. For filtering, a composite btree is seekable only on a left prefix, so with no predicate on status the scan can at best range over tenant_id and test owner_user_id per row; PostgreSQL 16 has no btree skip scan to jump the unconstrained column.
  2. For ordering, the index is sorted by (status, updated_at) within a tenant and not by created_at, so the LIMIT cannot stop early: every matching row is read and then sorted. That is the 'Sort Method: top-N heapsort' line, and it is why the plan reads 2M rows to answer with 20.
  3. Derive the replacement from the access path — equality, equality, then the ordering column: CREATE INDEX CONCURRENTLY ON resource (tenant_id, owner_user_id, created_at DESC). The scan seeks to the (tenant, owner) range and walks 20 entries in order, so the Sort node disappears along with the row-read.
  4. Treat INCLUDE (title, status) as conditional, not free. An index-only scan still visits the heap for any row whose page is not marked all-visible, so on a table taking 1.2k writes/second the win depends on autovacuum keeping the visibility map current, and the wider index costs more on every insert.

Follow-up

  • 90% of rows are status='active'. Would a partial index WHERE status = 'active' change your answer, and for which of the three queries?
  • A dashboard runs this for 40 owners in one page load. What changes about the design?

Write the update path that detects a concurrent edit

Medium · optimistic concurrency · transactional outbox · audit history

resource carries version INT NOT NULL DEFAULT 1. resource_revision holds revision_id, resource_id, version, actor_user_id, change_kind, patch JSONB, request_id, created_at with UNIQUE (resource_id, version). outbox_event holds aggregate_type, aggregate_id, aggregate_version, event_type, payload, status. A PUT carries the version the client read. Write the exact statements for the single transaction that applies the edit, records the revision and enqueues 'resource.updated', and give the handler's branch on zero affected rows. Then say what PostgreSQL 16 does under READ COMMITTED when two of these updates hit one row at once.

Approach

  1. One transaction, three writes, no network call inside it: UPDATE resource SET title = $3, version = version + 1, updated_at = now() WHERE resource_id = $1 AND tenant_id = $4 AND version = $2; then INSERT the resource_revision row at version $2 + 1; then INSERT the outbox_event row at the same aggregate_version. The event goes to a table rather than a broker because no transaction spans both.
  2. Branch on the affected-row count before doing anything else. Zero has three causes — stale version, wrong tenant, row gone — so re-read once and map to 409 carrying the current version, or 404 for an id outside the caller's tenant, which also stops the endpoint confirming that another tenant's id exists.
  3. State the engine behaviour instead of assuming it. Under READ COMMITTED the second UPDATE blocks on the row lock, and when the first commits PostgreSQL re-evaluates the WHERE clause against the newly committed row, so the version predicate now fails and the statement reports zero rows. Under REPEATABLE READ the identical collision raises SQLSTATE 40001 instead, so the handler must fold both shapes into one conflict response.
  4. Keep UNIQUE (resource_id, version) even though the predicate already serialises writers. It is what makes a lost update unwritable if any other path ever reaches the revision table, and it converts a logic bug into 23505 rather than into a silently missing history row.

Follow-up

  • A client sends the version it read ten minutes ago and the resource has moved three versions. What is in your 409 so it can resolve the conflict without a full re-fetch?
  • Two editors, two disjoint fields, no overlap. Does your answer still refuse the second write, and should it?

Design a high-traffic system (whiteboard exercise).

Medium · System Design and Architecture

Approach

  1. If the prompt is open, pin it to one scenario that fits the programmatic advertising context candidates describe: an ad impression and click tracking endpoint, or a bid-request handler that must answer inside a hard response budget. State that budget as your own assumption (for example tens of milliseconds), along with peak requests per second, read/write ratio, data retained per day and which data must be strongly consistent.
  2. Hot path: stateless application servers behind a load balancer (round robin or least connections) that scale horizontally. A cache (in-process or Redis, cache-aside with TTLs) serves hot lookups such as campaign settings and frequency-cap counters, so the request never waits on the main database.
  3. Write path: for impression and click events, accept the request, append it to a durable log or queue such as Kafka, and acknowledge. Consumers then batch writes to storage. An event id acts as the idempotency key, so a client retry or a redelivered message does not count the same impression twice, and back-pressure protects the store.
  4. Storage: choose the database from the access pattern. Shard by a key that spreads load evenly (say which queries that makes expensive), add read replicas for read-heavy traffic, and plan for the hot key: one popular campaign can receive most of the events, so split its counter across several sub-keys and merge them on read.
  5. Protect latency and availability: timeouts and circuit breakers on every dependency, token-bucket rate limiting, replication across zones, graceful degradation when a dependency is down (for example skip frequency capping rather than miss the response budget), and metrics on rate, errors and p99 to show where the next bottleneck is.

Follow-up

  • Traffic grows ten times. What breaks first, and how would you find out before it happens?
  • One campaign receives most of the traffic. How do you keep its shard from melting?
  • With at-least-once delivery from the queue, how do you keep impression counts from double-counting?

Explain your experience with microservices architecture.

Medium · System Design and Architecture

Approach

  1. Anchor the answer in one system you worked on: the services, how the boundaries were drawn (by business capability rather than by technical layer), who owned each one, and why the split was worth it.
  2. Communication: synchronous REST or gRPC where the caller needs an answer now, asynchronous messaging where it does not. On every remote call: timeouts, retries with exponential backoff and jitter only for idempotent operations, and a circuit breaker (for example Resilience4j in Java).
  3. Data: give each service its own database. Keep cross-service workflows consistent with a saga (choreographed or orchestrated, with compensating actions) and a transactional outbox, not a distributed two-phase commit, and accept eventual consistency where the business allows it.
  4. Operations: service discovery, an API gateway, distributed tracing (OpenTelemetry) with correlation IDs, centralised logs, contract tests, and backward-compatible API versioning so each service deploys independently.
  5. Be honest about the costs: network latency, partial failures, harder debugging and operational overhead. Say when a modular monolith would have been the better choice.

Follow-up

  • How did you keep data consistent when one business operation touched two services?
  • Describe an incident where a slow downstream service affected yours. What did you change afterwards?
  • How do you change an API's contract without breaking consumers that deploy on their own schedule?

Listing latency scales with page size, not with filters

Easy · n+1 · latency · query batching

The tenant listing endpoint reads resource filtered by tenant_id and status, ordered by updated_at DESC, and returns each row plus the owner's display name from app_user and the actor of that resource's latest resource_revision. p99 is 55 ms at 10 rows per page and 1.4 s at 200. Database telemetry shows 401 statements per request, each under 1 ms, and nothing in the slow-query log. Diagnose the cause and give the fix, stating the statement count per request and the p99 you expect afterwards.

Approach

  1. Read the counters before forming a theory. 401 statements for 200 rows is one driver query plus two per row, and sub-millisecond execution with an empty slow-query log rules out a bad plan. The time is round trips, which is why it is invisible in every per-query metric and scales with rows returned rather than with filter selectivity.
  2. Name the two per-row statements from their normalised text: a single-row app_user lookup by user_id, and a resource_revision lookup by resource_id ordered by version DESC LIMIT 1. Confirm by dropping those two response fields and watching the statement count fall to one. That locates the calls in the serialisation layer, not the repository.
  3. Check that the arithmetic accounts for the whole gap. Measure one round trip to the replica in isolation; 400 trips at roughly 3 ms of network plus 0.2 ms of execution is about 1.3 s on top of a 55 ms baseline, which matches. If the multiplication had fallen short, the N+1 would only be part of the story and you would keep looking.
  4. Batch both lookups. Collect owner_user_ids and resource_ids from the driver query, then issue WHERE tenant_id = $1 AND user_id = ANY($2) for the users, and PostgreSQL's SELECT DISTINCT ON (resource_id) ... WHERE resource_id = ANY($2) ORDER BY resource_id, version DESC for the latest revision, which the UNIQUE (resource_id, version) index serves directly. On an engine without DISTINCT ON, use a lateral join or a row_number window. Three statements per request at any page size.

Follow-up

  • The page size is capped at 200 today. What breaks first if it is raised to 2,000, and is it still this bug?
  • How do you stop the next N+1 from reaching production, given that no individual query is slow and the endpoint's tests pass?

Suggested practice checklist

The plan follows the reported question groups: Java and framework internals first, then OOP and Spring, data structures, design, and finally the behavioral and JPA material, with one mock run at the end.

Day 1 — Map the loop and choose your anchor project

  • Write out the 4 reported rounds (Baseline Skills Assessment, Deep-Dive Technical Discussions, Behavioral Rounds, Final Panels) and mark which question types you are least ready for.
  • Choose your primary language, Java or .NET, and one project you can explain in depth.
  • Write the job title and responsibilities narrative for your last two roles, using the first person and verifiable numbers only.

Deliverable: A one-page round map, a named primary language and anchor project, and a written narrative of your previous roles.

Day 2 — Strings and Java concurrency

  • Write a small program that proves String immutability, then compare String, StringBuilder and StringBuffer on repeated concatenation in a loop and note why the results differ.
  • Implement a producer-consumer with a BlockingQueue, then again with wait and notify, and write down what each does when the queue is full.
  • Make a list of Java concurrency tools (synchronized, volatile, ReentrantLock, ExecutorService, ConcurrentHashMap) and the problem each solves.

Deliverable: Two runnable producer-consumer programs, a string comparison program and a one-page concurrency cheat sheet.

Day 3 — OOP, Spring and managed code

  • Explain encapsulation, inheritance, polymorphism and abstraction aloud, each with a short Java example and one design trade-off such as inheritance versus composition.
  • Write a comparison of Spring and Spring Boot (auto-configuration, starters, embedded server) and list the steps in the Spring bean lifecycle.
  • Write a comparison of managed and unmanaged code with examples: garbage-collected runtimes such as the JVM and the .NET CLR versus code with manual memory management, and how Java or .NET interoperate with native code.

Deliverable: Spoken answers to the OOP, Spring versus Spring Boot and managed versus unmanaged questions, plus a one-page bean lifecycle diagram.

Day 4 — Linked lists, strings and swapping

  • Implement the intersection of two linked lists twice: with a hash set, then with two pointers that switch lists at the end, and state the space cost of each.
  • Write the palindrome check with two pointers, then extend it to ignore case and non-alphanumeric characters.
  • Write the anagram check with sorting and with a count array, and write the swap without a temporary variable using arithmetic and XOR, noting overflow for the arithmetic version.

Deliverable: Four working solutions with complexity notes and a list of edge-case tests for each.

Day 5 — Arrays and stacks

  • Solve valid parentheses, next greater element (monotonic stack) and a sorted-array two-sum with two pointers.
  • For each, write the brute-force approach, the improved approach and the time and space complexity.
  • Redo one problem without any library helper, as candidates report that solutions are expected to be implemented by hand unless shortcuts are allowed.

Deliverable: Three solved array and stack problems, each with brute-force and optimized complexity written next to it.

Day 6 — High-traffic design, microservices and concurrency

  • Whiteboard a high-frequency, low-latency request service: clarify rate and latency, split read and write paths, and choose caching, load balancing and a datastore.
  • Write talking points for your microservices experience: service boundaries, communication style, failure handling and a deployment or monitoring lesson.
  • List ways to optimize for high concurrency (connection and thread pools, non-blocking I/O, caching, partitioning, back pressure) and the cost of each.

Deliverable: A design diagram with stated assumptions, a bottleneck list, and written microservices and concurrency talking points.

Day 7 — JPA, behavioral stories, feedback and a full mock

  • Write your JPA answer: mappings, an N+1 fix, transaction boundaries and optimistic locking with a version column.
  • Write answers for why Adform, your teamwork approach and your experience with programmatic advertising clients, using real examples.
  • Write your feedback story: what you were told about code or a design, whether you agreed, what you changed and what happened next. Practise telling it in under two minutes.
  • Run a mock with someone who adds a new constraint to your day 6 design halfway through, then give your 2-level project walkthrough.

Deliverable: Written JPA, motivation, teamwork, programmatic and feedback answers, plus one recorded or peer-reviewed mock session.

Project, teamwork and ad-tech stories

Candidates describe the behavioral rounds as discussions of your project experience and how you handle feedback. The reported prompts mix motivation, teamwork, programmatic advertising experience, previous roles and JPA experience, so prepare real projects you can explain and a couple of stories about feedback and collaboration.

Discuss your experience with JPA (Java Persistence API).

Medium · System Design and Architecture

Approach

  1. Choose a project where you owned the persistence layer and open with specifics: the JPA provider (usually Hibernate), the main entities and relationships, and how repositories were organised. A claim of having used JPA is weak, a description of a mapping you had to change is strong.
  2. Name the problems you actually met. The usual ones are N+1 selects from lazy associations (fixed with a fetch join, an entity graph or batch fetching), LazyInitializationException outside a transaction, and loading whole entities when a DTO projection would do.
  3. Explain transactions and concurrency: @Transactional boundaries at the service layer, the persistence context acting as a first-level cache with dirty checking, and optimistic locking with a @Version column to catch lost updates.
  4. Say when you stepped outside JPA: bulk updates, large batch inserts or reporting queries done with JDBC, a native query or a projection because entity loading was too heavy. Close with a measured result, such as query count per request or latency before and after.

Follow-up

  • How would you detect an N+1 problem in a test or in production?
  • What is the difference between persist and merge, and what is a detached entity?
  • How would you insert several million rows efficiently?

Why did you choose to apply to Adform?

Medium · Behavioral and Situational

Approach

  1. Start from the work, not the brand: connect one thing you have built (high-volume services, data pipelines, microservices) to the engineering problems candidates describe for this role. The answer must not fit any other company unchanged.
  2. Use a simple order: what you have done, what draws you to advertising technology and this platform, and what you want to build next. Keep each part to a couple of sentences.
  3. Do real research beforehand and mention one specific thing you learned about programmatic advertising or the company's public engineering material. State it as something you read, not a claim about values or culture.
  4. Avoid reciting a tagline, praising size or scale generally, or giving a reason that is only about salary or location.

Follow-up

  • What do you already know about how programmatic advertising works?
  • What would you want to work on in your first months?
  • What other roles are you considering and how does this one compare?

What is your approach to teamwork? Are you the analytical contributor or the energetic driver?

Medium · Behavioral and Situational

Approach

  1. Do not simply choose a label. Give one story that shows your default style, and a second short example of when you moved to the other, so the answer is evidence rather than a self-description.
  2. Build the main story with a concrete setting: an ambiguous requirement or a deadline, what you did (for example wrote a benchmark or a short design to settle a debate, or unblocked a decision and assigned work), and the result for the team.
  3. Include the people the role works with: product managers who refine requirements and operations teams who handle deployments. Show how you adapted how you communicate with each.
  4. Include a disagreement that was settled with data or a prototype, and what you would change next time. The mistake that sinks the answer is a story in which teammates were the problem and you were the only competent person.

Follow-up

  • Tell me about a time your working style caused friction and what you did about it.
  • How do you work with someone whose style is the opposite of yours?
  • Describe feedback on how you collaborate, and what you changed.

What is your experience servicing clients within the programmatic advertising landscape?

Medium · Behavioral and Situational

Approach

  1. Answer honestly in the first sentence about whether you have worked in programmatic advertising or with clients at all. Candidates describe prior AdTech experience as a differentiator, not a requirement, so a clear no followed by a strong bridge is better than a vague yes.
  2. If you have experience, tell one concrete case: what the client or internal customer needed (an integration, a reporting discrepancy, an outage), your technical role, what you changed, and the result. Name the systems and keep the ad-tech terms to those you can explain.
  3. If you do not, bridge with the closest real experience: serving external API consumers, supporting a business team, or handling an escalation in a high-volume data system. Add what you have learned about the programmatic space and why the problems interest you.
  4. Show how you handled a client-facing incident: communication while it was happening, root cause, rollback or fix, and what you changed to prevent a repeat. The mistake is bluffing terminology you cannot define.

Follow-up

  • How did you prioritise a client request against planned work?
  • Describe explaining a technical problem to a non-technical stakeholder.
  • Which programmatic advertising terms or concepts have you learned about, and what do they mean?

Describe your job title and core responsibilities in previous roles.

Medium · Behavioral and Situational

Approach

  1. Give a short career narrative, not a list of duties: title, team, the system you worked on, your scope (what you owned and what you contributed to), the stack and the scale, using only numbers you can stand behind.
  2. Lead with the role closest to this job: Java or .NET services, microservices, data-heavy systems. Describe earlier roles in a sentence each.
  3. Turn every responsibility into a decision and a result, and expect to explain the architecture and the challenges of anything you mention, because project ownership is the reported theme of the behavioral rounds.
  4. Keep titles accurate. If your title understated or overstated your actual scope, say what you really did. Speak in the first person singular; a team-level description hides your contribution and is the most common mistake here.

Follow-up

  • Which of those responsibilities did you own end to end?
  • What is the biggest piece of technical debt in the system you worked on, and what would you change?
  • Which role taught you the most and why?