cswork / libraryOpen practice workspace

Interview guides · Apptio

Apptio Software Engineer Interview Guide

Apptio Software Engineer interview guide: reported rounds, Java concurrency, coding and design questions, behavioral prep and a 7-day plan.

The 60-second brief

Apptio makes SaaS applications for CIOs and IT leaders who need to track and manage what their organisations spend on technology. Candidates describe the engineering work behind it as building data-heavy platforms that process large datasets reliably, with Java as the main backend language and enterprise users who read the output as financial reports. For you, that means the loop mixes general computer science with questions about aggregating financial data and structuring it for pivot-style reporting.

This guide is for engineers preparing for a Software Engineer loop at Apptio, from a mid-level candidate to a senior one. It covers the reported rounds in order, the coding, Java concurrency, Spring and design questions candidates mention, and the behavioral prompts about executives and senior colleagues. The requirements candidates describe are Java and object-oriented programming, data structures and algorithms, system design, multithreading and REST API development. Spring Boot, React or Angular for full-stack roles, AWS and distributed systems are described as extras.

Candidates report four rounds over about three to five weeks: a cognitive aptitude test, a behavioral phone screen, a technical phone screen and an onsite loop of four to five sessions.

Focus areas

  • Data Structures
  • Algorithms
  • Coding Interviews (Whiteboard Coding)

Overview

The reported loop starts with a hurdle that has nothing to do with Java. Candidates describe a timed cognitive test (CCAT or UCAT are the names that come up) plus a logic assessment before any engineer speaks to you, and they say it works as a strict filter. Treat it as its own preparation track: timed practice on pattern recognition, mental arithmetic and short reading passages, because strong engineers are reported to fail it for lack of practice with the format.

After that, the questions candidates list split into four groups. Coding questions are classic structure problems: a zigzag tree print, reversing a linked list in groups of k, a two-sum check in strictly O(n) time, the minimum of a rotated sorted array and the nth highest number. The Java group is concurrency-heavy: producer-consumer from scratch, wait versus sleep, volatile and atomics, a thread-safe Singleton, and an iterative inorder traversal. The design group covers a WhatsApp-style messenger, aggregating large financial files, Spring dependency injection and inversion of control, and the Factory pattern. The last group is behavioral and domain: pivot tables, a demanding executive, and disagreeing with a senior engineer.

The business context is the reason the financial-file and pivot-table questions appear. The product is spend reporting for IT leaders, so a candidate who can talk about exact decimal arithmetic, idempotent file ingestion, additive measures and roll-ups is answering in the product's own terms. Candidates are also told to read up on Technology Business Management before the loop.

Prepare by writing code without an IDE, since the phone screen is reported to run in a shared editor such as CoderPad or CodeSignal and the onsite is reported to include whiteboard-style coding. For every coding answer, state the brute-force version, name the target complexity, and dry-run on a small input including the empty and one-element cases. For every concurrency answer, name the lock, the condition and the invariant it protects.

The Apptio interview process

These are the four stages candidates describe, in the order they report them; the onsite is said to contain four to five sessions of its own.

Cognitive Aptitude Test

Candidates report a timed cognitive aptitude test as the first gate, taken before any conversation with the engineering team, with a logic assessment alongside it. The names CCAT and UCAT come up. The reported content is logical reasoning (patterns, spatial puzzles, deductive logic), numerical questions solved quickly without a calculator (mental math, fractions, basic algebra) and verbal comprehension (short passages and vocabulary). The per-question time is tight, so the preparation is speed and a habit of guessing and moving on, not engineering knowledge.

What to demonstrate

  • Logical reasoning: pattern recognition, spatial reasoning and deductive puzzles
  • Numerical agility: mental math, fractions and basic algebra without a calculator
  • Verbal comprehension of short passages and vocabulary

How to prepare

  • Take full timed practice tests in the CCAT style at least three times and review which question types cost you the most time.
  • Drill fractions, percentages and simple algebra by hand until the common cases are automatic.
  • Set a skip rule, for example one quick guess and move on, so one hard puzzle cannot eat the clock.

Behavioral Phone Screen

Candidates describe a brief phone conversation with HR about behavioral aspects and fit. Nothing technical is reported for this stage, so it is a short chance to show you can explain your own work, your reasons for this employer and the way you handle working relationships. Prepare a clear two-minute walk through your CV, plus a few stories you can tell quickly.

What to demonstrate

  • Clarity when summarising your own background and decisions
  • Fit and motivation, as covered in a behavioral conversation
  • Communication that a non-engineer can follow

How to prepare

  • Write a two-minute CV walk-through that ends with why spend management software for IT leaders interests you.
  • Read up on Technology Business Management so you can say in one sentence why IT cost reporting is hard.
  • Outline two stories in situation, action, result form: one about a demanding stakeholder and one about disagreeing with a senior colleague.
  • Prepare your questions about the remaining stages and ask what format the technical screen uses.

Technical Phone Screen

Candidates report a technical screen in a shared coding environment such as CoderPad or CodeSignal, with algorithm challenges and deeper questions about your primary programming language. For this role the language is Java. The coding question types candidates list are tree traversal, linked-list manipulation and array search with a stated complexity target, so this is where those drills pay off. Expect to type working code in a plain editor and explain complexity out loud.

What to demonstrate

  • Working code for an algorithm problem written in a shared editor
  • Ability to hit a stated time or space target, such as strictly O(n)
  • Depth in the primary language, as the screen is reported to include language questions

How to prepare

  • Write the zigzag tree print, k-group list reversal, two-sum in O(n) and rotated-array minimum in plain Java with no autocomplete.
  • For each solution say the brute-force cost, the final time and space, and run a dry test with an empty and a single-element input.
  • Review Java basics you can be asked about live: wait versus sleep, volatile, and how a HashMap and a PriorityQueue behave.

Onsite Interview Loop

Candidates report an onsite or virtual loop of four to five sessions. They describe it as covering algorithmic coding, system design, technical questions on concurrency and frameworks, and a managerial round on behavioral questions and business alignment. In some loops, candidates also report presenting results from a take-home data assignment. This is the stage where the Java concurrency, Spring, Factory pattern, messenger design and financial-file questions are most relevant, along with the stakeholder and senior-engineer stories.

What to demonstrate

  • Algorithmic coding with complexity reasoning
  • System design: breaking a large problem into services, storage and caches, and justifying the trade-offs
  • Java concurrency and framework knowledge, including Spring dependency injection
  • Behavioral and business alignment in a managerial conversation

How to prepare

  • Code producer-consumer with a bounded buffer twice: once with wait and notifyAll, once with BlockingQueue, and explain which you would ship.
  • Practise two designs aloud: a WhatsApp-style messenger and a pipeline that aggregates large financial files, each with a stated data model and failure story.
  • Write a one-page explanation of dependency injection and inversion of control using a Spring constructor-injected service and a Factory use case.
  • Build a spoken answer for pivot tables, then rehearse the executive and senior-engineer stories to one tight minute each.

Where candidates lose points

Treating the cognitive test as a formality and arriving without timed practice

Candidates report the aptitude test as a strict filter that comes before any engineer speaks to you. Put three timed full-length CCAT-style sessions in the first days of the week, practise mental arithmetic with no calculator, and decide in advance when you guess and move on.

Answering the two-sum or rotated-array question with a nested loop or a sort when the question names a complexity

Say the brute-force cost, then use a hash set of seen values for the O(n) two-sum, and a binary search comparing mid with the right end for the rotated minimum. Dry-run each on an empty array, one element and duplicates before saying it is done.

Linked-list and tree code that works on the happy path but fails on the leftover group or the null child

For reversing every k nodes, decide up front what happens to a final group shorter than k and test with six nodes at k=3 and five at k=2. For zigzag print, flip direction per level, and for an iterative inorder, test a left-only and a right-only tree.

Concurrency answers that name the keyword but miss what it guarantees, such as wait inside an if, a Singleton without volatile, or volatile described as making count++ atomic

Write producer-consumer with a while loop around wait and notifyAll under the same lock. Explain that wait releases the monitor and sleep does not, that volatile gives visibility and ordering but not atomic compound updates, and why double-checked locking needs it.

Designing the messenger or the financial-file pipeline by listing components without requirements, data model or failure handling

Open with users, message or file volume and delivery guarantees. For financial files add idempotent ingestion, exact decimal arithmetic instead of floating point, partial-failure restart and a way to reconcile totals. For the messenger, cover connection handling, storage, offline delivery and ordering.

Questions & practice

Print a binary tree in a zigzag manner.

Medium · Data Structures & Algorithms

Approach

  1. Run a level-order BFS with a queue, reading the queue size at the start of each level so you know exactly which nodes belong to it.
  2. Keep a leftToRight flag that flips after every level. Build each level in a deque: add values at the tail when going left to right and at the head when going right to left. That avoids reversing a list on every other level.
  3. The alternative is two stacks: pop from the current one and push children onto the next. Push left then right on one pass and right then left on the next.
  4. Every node is enqueued and dequeued once, so it runs in O(n) time with O(w) extra space, where w is the widest level (up to about n/2 in a complete tree).
  5. Edge cases: a null root returns an empty result, a single node gives one level, and a skewed tree has one node per level, so the direction flag changes nothing visible.

Follow-up

  • Can you do it with DFS, passing the depth down and inserting at the front or back of that depth's list?
  • How would you print the levels as you go, without keeping the whole result in memory?
  • How does the two-stack version compare with the deque version in space and readability?

Write a program to reverse every k-nodes in a linked list.

Medium · Data Structures & Algorithms

Approach

  1. Ask first what happens to a final group shorter than k: leave it in its original order (the common variant) or reverse it too. The answer changes the loop condition.
  2. Iteratively: put a dummy node before the head and keep groupPrev. Walk k nodes ahead to check a full group exists, reverse those k nodes in place with prev/curr/next pointers, then point groupPrev.next at the new group head and connect the old head (now the tail) to the rest of the list.
  3. Each node is visited a constant number of times, so time is O(n) and extra space is O(1). A recursive version is shorter but uses O(n/k) stack frames.
  4. Edge cases: an empty list, k <= 1 (return the list unchanged), k greater than the length, and a length that is an exact multiple of k.
  5. Dry-run a 5-node list with k = 2 out loud. Losing the connection between groups is the bug that shows up most often.

Follow-up

  • How would you reverse alternate groups of k, reversing one group and skipping the next?
  • What changes for a doubly linked list?
  • Why might the recursive version overflow the stack on a very long list, and how does the iterative one avoid it?

Given an array, find if the sum of two elements equals a target value k in strictly O(n) time.

Medium · Data Structures & Algorithms

Approach

  1. Make one pass with a HashSet: for each x, check whether k - x is already in the set, and only then insert x. Checking before inserting means x is never paired with itself unless it appears twice.
  2. This is O(n) expected time and O(n) space. Hash lookups are O(1) on average, not in the worst case. Java's HashMap turns heavily colliding buckets into trees, which caps a lookup at O(log n).
  3. If 'strictly O(n)' has to mean worst case, ask about the value range. A bounded range lets you use a boolean or count array indexed by value, which is O(n + range) with no hashing.
  4. If the array is already sorted, two pointers moving inward give O(n) time and O(1) space. Sorting first costs O(n log n), which breaks the stated bound.
  5. Edge cases: fewer than two elements, duplicates such as [3, 3] with k = 6, negative numbers, and int overflow in k - x (compute it as a long).

Follow-up

  • Return the indices of the pair, or count every pair that sums to k, instead of a boolean.
  • How would you solve three-sum, and what is the best complexity you can get?
  • What if the numbers arrive as a stream and the question is asked after every new element?

Find the smallest element in a sorted and rotated array.

Medium · Data Structures & Algorithms

Approach

  1. Use binary search with lo = 0 and hi = n - 1. Compare a[mid] with a[hi]: if a[mid] > a[hi], the minimum is to the right, so set lo = mid + 1; otherwise set hi = mid. Stop when lo == hi and return a[lo].
  2. Compare with a[hi], not a[lo]. A comparison against a[lo] cannot tell a rotated array from one that was never rotated, and on a non-rotated array it sends the search the wrong way.
  3. This runs in O(log n) time with O(1) space. Compute mid as lo + (hi - lo) / 2 so the sum cannot overflow.
  4. With duplicates, a[mid] == a[hi] gives no information. Decrement hi by one in that case, and say that the worst case becomes O(n) (for example, all elements equal except one).
  5. Edge cases: one element, an array rotated 0 or n times, and a two-element array.

Follow-up

  • Return how many times the array was rotated (the index of the minimum).
  • Search for a target value in the rotated array in O(log n).
  • Why can't duplicates be handled in O(log n) in the worst case?

Find the nth highest number in an array.

Medium · Data Structures & Algorithms

Approach

  1. Clarify whether 'nth highest' counts duplicates: in [5, 5, 4] the 2nd highest is 5 or 4 depending on the answer. Also ask what to return when n exceeds the number of (distinct) values or n <= 0.
  2. Baseline: sort descending and index into it, O(m log m) for m elements. For distinct values, deduplicate first with a set or skip repeats while scanning the sorted array.
  3. For small n, keep a min-heap of size n (Java's PriorityQueue is a min-heap by default). Push each element, poll when the size exceeds n, and the heap's top is the answer. That is O(m log n) time and O(n) space, and it works on a stream.
  4. Quickselect with a random pivot partitions in place and recurses into one side only. It averages O(m) and is O(m^2) in the worst case. Median-of-medians guarantees O(m) but is rarely worth writing in an interview.
  5. For a small fixed n such as 2 or 3, a single pass that tracks the top values in variables is O(m) and O(1). Watch the ordering of updates and the duplicate rule.

Follow-up

  • How does the answer change if the data is a stream too large to store?
  • Write the SQL version: the nth highest salary in a table, with and without counting ties.
  • Why is quickselect's worst case quadratic, and how does a random pivot help?

Implement the Producer-Consumer problem from scratch.

Medium · Java and Concurrency

Approach

  1. Write a bounded buffer: a fixed-size array or queue plus a count, shared by producer and consumer threads. A producer must block when the buffer is full and a consumer when it is empty.
  2. With intrinsic locks: put() and take() are synchronized on the buffer. Each waits in a while loop (while full, wait(); while empty, wait()) to cover spurious wakeups and conditions that changed before the thread reacquired the lock, and calls notifyAll() after changing the state.
  3. Use notifyAll rather than notify when producers and consumers share one wait set. notify can wake a thread of the wrong kind, for example a producer when only a consumer can make progress, and the system can stall.
  4. A cleaner version uses a ReentrantLock with two Conditions, notFull and notEmpty, so each side signals only the threads that can proceed. Mention that java.util.concurrent's ArrayBlockingQueue already does this with put() and take(), but the question asks for it from scratch.
  5. For shutdown, use a poison-pill item or interrupt the threads. Catch InterruptedException and either propagate it or restore the flag with Thread.currentThread().interrupt(); never swallow it.

Follow-up

  • What breaks if you replace the while loop around wait() with an if?
  • How do you shut down several consumers cleanly once the producers finish?
  • How would you measure whether lock contention limits throughput, and what would you change?

Explain the difference between wait and sleep in Java.

Medium · Java and Concurrency

Approach

  1. wait() is an instance method on Object. The calling thread must hold that object's monitor (otherwise it gets an IllegalMonitorStateException), and wait() releases that monitor while the thread waits.
  2. Thread.sleep() is a static method that pauses the current thread for at least the given time. It releases no locks, so sleeping inside a synchronized block keeps every other thread out of it.
  3. A waiting thread returns after notify()/notifyAll() on the same object, a timeout if one was given, an interrupt, or a spurious wakeup, and it must reacquire the monitor before continuing. That is why wait() always sits in a while loop that rechecks the condition.
  4. Both throw InterruptedException when interrupted, which clears the interrupt flag. Use wait() for coordination between threads on a condition, and sleep() only for pacing or backoff, never to wait for another thread's state change.

Follow-up

  • How do join(), yield() and LockSupport.park() differ from these two?
  • What does Condition.await() give you that wait() does not?
  • Why is calling sleep() inside a synchronized method usually a bug?

How does the volatile keyword work, and how does it interact with atomic types?

Medium · Java and Concurrency

Approach

  1. volatile guarantees visibility and ordering. A write to a volatile field happens-before every later read of that field, so readers see the latest value and the writes the writer made before it. The compiler and CPU cannot move ordinary accesses across it in ways that break this.
  2. It does not make compound actions atomic. count++ on a volatile int is still read-modify-write, and two threads can lose an update. Reads and writes of a volatile long or double are atomic, unlike plain ones.
  3. Atomic classes such as AtomicInteger hold their value in a volatile field. get() and set() have volatile semantics, and incrementAndGet() and compareAndSet() are atomic read-modify-write operations built on CAS, retrying on contention instead of blocking.
  4. The typical uses: a volatile boolean stop flag read by a worker loop, the instance field in double-checked locking, and AtomicInteger or LongAdder for counters (LongAdder spreads writes across cells and scales better under heavy contention).
  5. Declaring an AtomicInteger field itself volatile adds nothing if the field is final, because the reference never changes. Elements of a volatile array are not volatile, so use AtomicIntegerArray when the elements need those semantics.

Follow-up

  • When would you use synchronized instead of volatile or an atomic?
  • What is the ABA problem with compareAndSet, and how does AtomicStampedReference address it?
  • Why does double-checked locking need the field to be volatile?

Write a thread-safe implementation of the Singleton design pattern and explain the synchronization issues invo

Medium · Java and Concurrency

Write a thread-safe implementation of the Singleton design pattern and explain the synchronization issues involved.

Approach

  1. Start with the simplest correct option: a private constructor plus a static final instance initialised eagerly. The JVM runs class initialisation once under a lock, so it is thread-safe, but it is not lazy.
  2. A synchronized getInstance() is lazy and correct, but every call takes the lock. Double-checked locking (check null, synchronize, check null again, assign) avoids that cost only if the field is volatile.
  3. Explain why volatile is required. instance = new Singleton() can be reordered so the reference is published before the constructor finishes, and a second thread that skips the lock can see a non-null but partly built object.
  4. Two better choices: the initialization-on-demand holder idiom (a private static nested class holding the instance, loaded only on first access, lazy without any locking) and a single-element enum, which also survives serialization and resists reflective construction.
  5. Other issues to name: reflection can call the private constructor, deserialization creates a new object unless readResolve() returns the instance, and separate classloaders each get their own copy.

Follow-up

  • How do you unit-test code that depends on a singleton?
  • How does a Spring singleton-scoped bean differ from this pattern?
  • What happens to your singleton if the class is Serializable?

Print a binary tree's inorder traversal without using recursion.

Medium · Java and Concurrency

Approach

  1. Use an explicit stack in place of the call stack. Set curr = root. While curr != null or the stack is non-empty: push curr and move left until curr is null, then pop a node, print it, and set curr to its right child.
  2. Every node is pushed and popped once, so the traversal is O(n) time. The stack holds at most one root-to-leaf path, so space is O(h): O(log n) for a balanced tree and O(n) for a skewed one.
  3. To get O(1) extra space, describe Morris traversal. For each node with a left child, find its in-order predecessor and set a temporary right link back to the node. Follow that link to return, then remove it. The tree is restored and time stays O(n).
  4. Edge cases: an empty tree, a single node, and left-only or right-only chains. Trace a 3-node tree aloud to show the push, pop and move-right order.

Follow-up

  • Write iterative preorder and postorder. Postorder is the tricky one: use two stacks or track the last visited node.
  • Turn this into a BST iterator with next() and hasNext() in amortized O(1) time and O(h) space.
  • Use the traversal to find the kth smallest element of a BST without visiting the whole tree.

Keep soft-deleted accounts from blocking re-registration

Easy · soft delete · unique constraints · engine differences

app_user holds user_id, tenant_id, email CITEXT, password_hash (NULL for SSO principals), email_verified_at, auth_version, status ('invited','active','suspended','deactivated'), created_at, updated_at, deleted_at. Two live accounts for one address inside a tenant must be impossible, but an address freed by a soft delete must be reusable, and the same tenant may delete and re-register it repeatedly. Write the uniqueness DDL for PostgreSQL 16, then the equivalent for MySQL 8 where partial indexes do not exist, and say what each permits once three deleted rows already hold that address.

Approach

  1. Start from what is actually unique: not (tenant_id, email), but (tenant_id, email) among live rows. PostgreSQL says that directly — CREATE UNIQUE INDEX app_user_live_email ON app_user (tenant_id, email) WHERE deleted_at IS NULL. A full constraint over the same two columns burns the address permanently the first time someone deletes an account.
  2. Keep case-insensitivity in the type or the index, never in the application: CITEXT as given, or UNIQUE (tenant_id, lower(email)) as an expression index where the extension is unavailable. A case-sensitive unique column is exactly how two accounts for one human appear.
  3. For MySQL 8 the predicate has to move inside the key: add a discriminator column that is a constant 0 while the row is live and is set to user_id on delete, with UNIQUE (tenant_id, email, deleted_marker). Live rows share the constant and still collide; deleted rows differ from each other and stop colliding.
  4. State the NULL variant and its dependency: leaving the marker NULL for deleted rows also works, because a unique index treats NULLs as distinct — true in MySQL, and true in PostgreSQL only under the default NULLS DISTINCT, which PostgreSQL 15 lets you reverse. Check the polarity against the three existing deleted rows: constant-on-live is what preserves the collision you want, and reversing it silently admits duplicate live accounts.

Follow-up

  • A deleted account re-registers with the same address the next day. Do the old resource rows follow the new user_id, and how does the API keep the two principals apart?
  • How do you honour an erasure request while resource_revision.actor_user_id still references this table?

Stop tag and share joins from fanning out a page

Medium · join fan-out · semi-joins · many-to-many

resource_tag is (resource_id, tag_id) with PK (resource_id, tag_id); resource_share is (resource_id, shared_with_user_id, permission). The tagged-and-shared listing inner-joins resource to both, filters tenant_id, tag_id = ANY($2) and shared_with_user_id = $3, orders by updated_at DESC and takes 50. Pages come back with fewer than 50 distinct resources and the total in the header is far too high. Explain the row multiplication, rewrite both the page query and the count query so each is correct, and name the index each one needs. PostgreSQL 16.

Approach

  1. Do the arithmetic against the predicates that are actually there. An inner join emits one row per matching child row, and both joins are filtered: tag_id = ANY($2) admits only the requested tags, shared_with_user_id = $3 admits one user's share rows. So a resource holding three of the requested tags and shared with $3 once yields three rows, not one — the multiplier is its count of matching tags times its share rows for that single user, and that second factor is 1 unless the table admits duplicate (resource_id, shared_with_user_id) pairs. LIMIT 50 then limits rows rather than resources, and COUNT(*) counts pairs — the header is the product, not the population.
  2. Reject DISTINCT as the fix. It deduplicates after the product has been built, so the planner must materialise and sort the fanned-out set before the LIMIT can apply, and it leaves any SUM or AVG in the same select list wrong.
  3. Rewrite both filters as semi-joins, keeping resource as the only row source: AND EXISTS (SELECT 1 FROM resource_tag rt WHERE rt.resource_id = r.resource_id AND rt.tag_id = ANY($2)) and the same shape against resource_share. A semi-join stops at the first match per resource and preserves the driving index order, so ORDER BY updated_at DESC, resource_id DESC LIMIT 50 still stops after 50 rows.
  4. Count with the same predicates and no join at all: SELECT count(*) FROM resource r WHERE r.tenant_id = $1 AND r.status = 'active' AND EXISTS (...) AND EXISTS (...). Nothing multiplies a resource, so the number is the population.

Follow-up

  • The filter changes from 'any of these tags' to 'all of these tags'. Rewrite it and state what it costs relative to the ANY form.
  • A resource can be shared with the same user twice under different permissions. Does your count change, and should it?

Design a messaging application like WhatsApp.

Medium · System Design & Architecture

Approach

  1. Scope the requirements: 1:1 and group chat, delivery and read receipts, offline delivery, online status and media. Then the non-functional ones: low latency, ordering within a conversation, durability, and roughly how many users and messages per day you are designing for.
  2. Clients keep a persistent connection (WebSocket or a long-lived TCP connection) to stateless gateway servers. A session registry, for example in Redis, maps each user ID to the gateway holding that user's connection, so the message service can route to the recipient.
  3. Write path: the message service gives each message a per-conversation sequence number, stores it, acknowledges the sender, then pushes to recipients. Offline recipients get the message from an undelivered queue when they reconnect, plus a push notification through APNs or FCM.
  4. Storage is write-heavy and read by recency, so a wide-column store partitioned by conversation ID and clustered by sequence number fits. Media goes to object storage behind a CDN, and the message carries only a reference.
  5. Delivery is at-least-once with a client-generated message ID, so retries are deduplicated. Small groups fan out on write to each member; very large groups need fan-out on read or batching. With end-to-end encryption, the server stores and relays only ciphertext.

Follow-up

  • A gateway server crashes while holding thousands of connections. What happens to in-flight messages?
  • How do you keep message order and read state in sync across several devices for one user?
  • How would you serve 'last seen' and online status without flooding every contact with updates?

Explain Dependency Injection (DI) and Inversion of Control (IOC) in the context of the Spring framework.

Medium · System Design & Architecture

Approach

  1. Inversion of Control means the framework, not your code, creates objects and manages their lifecycle. In Spring that is the ApplicationContext, which builds beans from component scanning (@Component, @Service) and from @Bean methods in @Configuration classes.
  2. Dependency Injection is the form of IoC Spring uses: a bean declares what it needs, and the container supplies it through the constructor, a setter or a field, so classes depend on interfaces rather than constructing their collaborators.
  3. Prefer constructor injection. Dependencies can be final, required collaborators are explicit, and the class can be unit-tested with plain new and mocks, no container needed. Since Spring 4.3, a class with a single constructor does not need @Autowired.
  4. Injection resolves by type. When several beans match, @Qualifier or @Primary settles it. The default scope is singleton, meaning one instance per container; prototype, request and session scopes also exist.
  5. Know the pitfalls. Circular constructor dependencies fail at startup, and Spring Boot 2.6+ rejects circular references by default. Features such as @Transactional run through proxies, so a call from one method to another on the same bean skips the proxy and the annotation has no effect.

Follow-up

  • How do you inject a prototype-scoped bean into a singleton and get a fresh instance each time?
  • Walk through the bean lifecycle: when do @PostConstruct and @PreDestroy run?
  • Why does @Transactional not apply when one method calls another on the same class?

How would you design a system to process and aggregate large files of financial data?

Medium · System Design & Architecture

Approach

  1. Clarify first: file sizes and format (CSV, Excel), how often files arrive, how fresh the aggregates must be, the grouping dimensions (for example cost by department and month), and whether results must reconcile exactly to the cent.
  2. Ingest: files land in object storage, and an event creates a job. Split large files into chunks on line boundaries and parse them in parallel with streaming readers, so no worker loads a whole file into memory. Validate each row and send bad rows, with file and line number, to a quarantine table instead of failing the whole job.
  3. Aggregate in two phases: each worker builds partial sums and counts per key, and a reduce step merges them. Sums and counts combine safely; a median or distinct count needs a different approach. Hold money as BigDecimal or integer minor units, never double.
  4. Make reruns safe. Key each job on a file checksum, write results to a staging table and swap or merge them in one transaction, so a retried or duplicate upload never double-counts. Track job state so a failed chunk can be retried on its own.
  5. Store raw data in a columnar format such as Parquet and serve queries from precomputed rollups. Plan for skew: one very large key can stall the reduce step unless you salt or pre-split it.

Follow-up

  • A corrected version of last month's file arrives. How do you replace its numbers without touching the other files?
  • One cost center accounts for most of the rows. What slows down, and how do you fix it?
  • How would you handle amounts in several currencies with exchange rates that change over time?

Explain the Factory design pattern and provide a real-world use case where you would apply it.

Medium · System Design & Architecture

Approach

  1. Define it: Factory is a creational pattern that puts object creation behind a method, so callers depend on an interface and never name a concrete class. Separate the three variants: a simple static factory, Factory Method (a subclass decides which class to create) and Abstract Factory (creates families of related objects).
  2. Give a concrete use case and code it. For example, an importer needs a FileParser for CSV, XLSX or JSON uploads. ParserFactory.forFormat(format) returns the right implementation, and the import code calls only parse().
  3. Avoid a growing switch. Keep a registry Map<String, Supplier<FileParser>>, or in Spring inject a List<FileParser> and index it by each parser's supported format, so adding a format means adding a class (the open/closed principle).
  4. Name JDK examples, such as Calendar.getInstance() and NumberFormat.getInstance(), and the trade-off: an extra layer of indirection that is not worth it when only one implementation exists or will exist. Say how an unknown format is handled: throw a clear exception, don't return null.

Follow-up

  • How does Factory differ from Builder, and when would you combine them?
  • When is Abstract Factory worth it over a single Factory Method?
  • How does the factory make the importer easier to unit-test?

Edge instances grow 400 MB per hour until the nightly restart

Hard · memory leak · heap analysis · metric cardinality

Edge API instances start at 700 MB resident and grow about 400 MB/hour; a nightly rolling restart has hidden it for weeks. Growth continues unchanged when request rate halves overnight, p99 degrades in the last hours before an instance is recycled, and heap used immediately after a forced full GC rises monotonically. The service holds no product state. Name the discriminating measurement that separates the plausible causes, give the most likely cause, and give the fix and how you would verify it.

Approach

  1. Separate resident memory from live heap first, because they fail differently. Resident size can grow from fragmentation, native buffers or thread stacks while the heap is flat; heap used after a full GC rising monotonically is the measurement that says objects are reachable and not being released. You already have it, so this is retention, not fragmentation, and that closes off half the candidate list.
  2. Use the rate's independence from traffic as the discriminator. Growth that continues at half the request rate rules out per-request objects that are merely slow to collect and points at a structure that grows with distinct values observed rather than with call volume. Write the candidates that have that property: a metrics registry keyed on a high-cardinality label, an unevicted cache, an interner, a per-key lock map.
  3. Take two heap snapshots an hour apart and diff by retained size, reading the dominator tree, not by allocation count or instance count. Expect one root holding a map with millions of entries, then follow the reference chain to the code that inserts and never removes. Allocation profilers point at churn, which is the wrong signal here.
  4. The candidate that fits this service is an observability label carrying an identifier, such as a request path recorded before templating so that /v1/resources/48213 becomes its own metric series. That grows with distinct ids seen, is independent of rate, and explains the late p99 degradation, since GC cost rises with the size of the live set.

Follow-up

  • Post-GC heap is now flat but resident size still creeps. What are you looking at, and does it matter?
  • How would you have detected this before an OOM, given the nightly restart masked the trend?

Suggested practice checklist

Each day targets a group of reported question types or a stage of the loop, and ends with something you can show: solved problems, a coded class, a design outline or a set of stories. Practice-library questions are added where they strengthen the same skill.

Day 1 — Aptitude test format and the loop map

  • Take one full timed CCAT-style practice test and mark the question types that cost you the most time.
  • Write a one-page list of the four reported rounds and which of your skills each one needs, marking the weakest.
  • Do twenty minutes of calculator-free arithmetic on fractions, percentages and simple algebra.

Deliverable: A scored practice test with a list of slow question types, and a one-page map of the four rounds with your weakest stage marked.

Day 2 — Array problems with a complexity target

  • Solve the two-sum-equals-k problem in strictly O(n) with a hash set, then write the sorted-array two-pointer variant and compare their space.
  • Find the smallest element of a sorted and rotated array with binary search, and test it with duplicates and an array that is not rotated.
  • Find the nth highest number with a size-n min-heap and again with quickselect, and state the complexity of each.

Deliverable: Three Java solutions with a one-line complexity note and a test list for each, including empty and single-element input.

Day 3 — Trees and linked lists in plain Java

  • Write the zigzag tree print with a queue and a direction flag per level.
  • Reverse a linked list in groups of k, leaving a short last group unchanged, and dry-run it on paper with six and five nodes.
  • Write an iterative inorder traversal with an explicit stack and test it on skewed trees.

Deliverable: Three working Java methods typed without autocomplete, each with a hand dry-run trace.

Day 4 — Java concurrency from scratch

  • Implement a bounded producer-consumer buffer with synchronized, wait and notifyAll, then redo it with a BlockingQueue.
  • Write a thread-safe Singleton in two forms, double-checked locking with volatile and the holder idiom, and note what breaks without volatile.
  • Prepare spoken answers on wait versus sleep, and on volatile versus AtomicInteger, with one counter example each.

Deliverable: Two coded concurrency classes and a half-page of spoken-answer notes covering the lock owned, the condition and the invariant.

Day 5 — Design: messenger and financial file aggregation

  • Outline a WhatsApp-style messenger: client connections, message storage, delivery to offline users, ordering within a chat and group fan-out.
  • Outline a pipeline that ingests and aggregates large financial files: chunked parsing, idempotent loads, decimal math, restart after partial failure and reconciliation of totals.
  • For each design, write your database choice and the trade-off you accept, such as SQL versus NoSQL and the consistency you need.

Deliverable: Two one-page design outlines, each with requirements, data model, bottleneck and failure handling, and a stated storage choice.

Day 6 — Spring, patterns and data modelling for pivot tables

  • Explain dependency injection and inversion of control with a Spring service whose dependency arrives through its constructor, and show how it simplifies a test.
  • Write a Factory with a real use case, for example choosing a report exporter by format, and say when a plain constructor is better.
  • Sketch a fact table and dimension tables for IT spend and write the GROUP BY query behind a pivot of cost by month and team.

Deliverable: A short DI and Factory write-up with code, a schema and query for the pivot, and notes from the two practice questions.

Day 7 — Behavioral stories and a full dry run

  • Write the demanding-stakeholder story and the senior-engineer disagreement story in situation, action, result form, each to about one minute spoken.
  • Say both aloud to a friend or a recording, and cut anything that explains the person instead of your actions.
  • Run one mixed session: one coding problem in a plain editor, one concurrency explanation and one short design, then note what you would fix.

Deliverable: Two rehearsed stories, one recorded mixed practice session, and a short fix list for the remaining gaps.

Stakeholders, pushback and pivot tables

Candidates report a behavioral phone screen with HR and a managerial onsite session focused on behavioral questions and business alignment. The prompts below come from reported questions: dealing with a demanding executive, disagreeing with a senior engineer, and pivot tables, which is a domain question dressed as a conversation. Prepare stories where you made a decision and can describe the result, and keep each to about a minute.

  1. Tell me about a time you explained a technical trade-off to someone who was not technical.
  2. Describe a time a teammate pushed back hard on your solution and what you did next.
  3. Why do you want to build software for IT leaders who manage technology spend?

Tell me what you know about pivot tables and how you would structure data to support them.

Medium · Behavioral and Domain Knowledge

Approach

  1. Define it first: a pivot table summarises a flat table by letting the user pick fields for rows, columns and filters and an aggregate (sum, count, average) for the values. Underneath it is a GROUP BY across the chosen dimensions, displayed as a cross-tab.
  2. Say the grain out loud, then structure the data around it: one row per event at that grain (for example one cost line per account per day), dimension columns such as date, team, vendor and category, and numeric measure columns such as amount. Keep it in long form, not one column per month, so a new month is a new row.
  3. Put dimensions in their own tables in a star schema with hierarchies (day to month to year, category to subcategory) so the user can roll up and drill down. Add a proper date table with fiscal periods.
  4. Keep the numbers correct and fast: store additive measures, or the parts of a non-additive one (sum and count, not a stored average); use ROLLUP or GROUPING SETS for subtotals; pre-aggregate the common groupings; partition by date; avoid joins that fan out and double count.
  5. Cover edge cases: null dimensions shown as an 'unassigned' member, multiple currencies, late corrections to past periods, and a user dragging a very high-cardinality field onto the columns.

Follow-up

  • The fact table has hundreds of millions of rows and the pivot must still respond quickly. What do you precompute, and what do you compute on demand?
  • How should a grand total behave for an average or a ratio column?
  • A user puts a high-cardinality field on the column axis. How do you stop the query and the page from collapsing?

Describe a time you had to deal with a demanding executive or stakeholder.

Medium · Behavioral and Domain Knowledge

Approach

  1. Choose a story where you were the engineer responsible for the outcome and the stakeholder had real leverage, such as a deadline, a budget or the ability to escalate. A story where you only complied, or where the person was simply rude, shows nothing.
  2. Open with the situation in two sentences, then spend most of the time on what you did: you found the need behind the demand, wrote it back in concrete terms, and showed the cost of each option so the executive could choose with the trade-off visible.
  3. Show the working mechanics: a short written agreement on scope and dates, a regular update with a few numbers, and a clear point where you said no or asked what to drop, with an alternative offered.
  4. Finish with the result and what changed in the relationship or the process, for example earlier involvement in planning or a shared dashboard. Name one thing you would do differently.
  5. Avoid describing the person as unreasonable. Describe their pressure and what they were accountable for, and keep the tone you would use if they were in the room.

Follow-up

  • What did you do when the request conflicted with something already on the roadmap?
  • How did you explain that a date was not achievable to someone who is not technical?
  • Did the stakeholder's behaviour change afterwards, and what would you do earlier next time?

How do you handle situations where you disagree with a senior engineer's architectural decision?

Medium · Behavioral and Domain Knowledge

Approach

  1. Pick a real architectural decision, such as a data store, a service boundary or a concurrency approach, where you disagreed and the stakes were clear. Start by stating the senior engineer's reasoning fairly. If you cannot, say what you did to find out.
  2. Separate taste from risk. Say which concrete failure you were worried about, for example a hot partition, a lock contention path or a migration that cannot be reversed, and how you tested it with a benchmark, prototype or load estimate instead of arguing from opinion.
  3. Describe how you raised it: a private conversation first, then a short written comparison of the options with costs, brought to a design review. Do not go around the person to their manager.
  4. State the outcome either way. If your option won, say what you conceded. If it did not, say you committed fully, and that you agreed on a metric or date at which the decision would be revisited.
  5. The mistake that sinks this answer is a story in which you were right and the other person was careless. Include something you learned from their view or a point where you changed your mind.

Follow-up

  • The decision was made and you still believed it was wrong. What did you do?
  • What if the choice was effectively irreversible and you thought it would cause an outage?
  • When would you escalate beyond the senior engineer, and how would you do it?