Interview guides · AIA Group
AIA Group Software Engineer Interview Guide
AIA Group Software Engineer interview guide: the three reported rounds, Java, Node.js, SQL, Docker and CI/CD questions, and a seven-day prep plan.
The 60-second brief
AIA Group is a life insurance organization. Candidates describe the Software Engineer role as building and maintaining platforms, APIs and CI/CD pipelines for its insurance and health-protection business across the Asia-Pacific region, working with product managers, infrastructure teams and other engineers in several time zones. The requirements candidates describe list Java or Node.js, SQL and NoSQL databases, Docker, and a cloud platform (Azure or AWS), with Kubernetes, advanced pipeline management and security-minded development as extras. In practice that means services and databases that must stay available and follow security and compliance rules.
This guide is for candidates applying as a Software Engineer at AIA Group. It covers the three reported rounds (HR Screening, Technical Assessment, Leadership Discussion), all fifteen reported questions, and a seven-day plan. The questions fall into three groups: language internals and SQL (Java collections and data types, the Node.js event loop, UNION versus UNION ALL), design and infrastructure (adding a database, SQL versus NoSQL, ECS versus EC2, Docker ENTRYPOINT and multi-stage builds), and project experience (a challenging project, API documentation and testing, technical debt, CI/CD, unit testing).
Candidates report three rounds over roughly three to five weeks: HR Screening, Technical Assessment and Leadership Discussion. The number of rounds and the scheduling can differ by region and team.
Focus areas
- System Design
- CI/CD Pipelines
- Scalability
Overview
The reported questions are mostly fundamentals with a production angle. Some are short factual checks: ArrayList versus LinkedList, Set versus List, the core Java data types, the Node.js event loop, UNION versus UNION ALL. Others ask you to reason about a system: how to add a database to an application from scratch, how to choose between SQL and NoSQL, how ECS differs from EC2, why a Dockerfile uses ENTRYPOINT as well as RUN, and whether multi-stage builds are worth it. The last group is about your own work: a challenging project, how you document and test APIs, how you handle technical debt or conflict in a sprint, your CI/CD experience and your approach to unit testing.
Prepare each fundamentals question in the same four moves: say what the thing is, explain the mechanism underneath it, state the cost or limit, and name the alternative you would pick in a different situation. 'LinkedList inserts faster' is a half answer; the full answer says an insert is cheap only once you hold the node, that finding the position is linear, and that ArrayList's contiguous memory often wins in practice. Candidates describe a preference for explaining why a choice was made, so rehearse the reasoning aloud rather than only recognising the facts.
The requirements candidates describe name either Java or Node.js, so choose the one you have shipped and be able to explain its internals (Java collections and memory, or the Node.js event loop and what blocks it). SQL, Docker and a cloud platform are listed alongside it. Be able to write a small multi-stage Dockerfile from memory, say what ECS manages that a bare EC2 instance does not, and explain how a change travels from a commit through a pipeline to production.
Interviewers are reported to come from different regions, and the final stage is a leadership discussion about fit. Plan to explain technical decisions in plain, structured language, to define terms for a listener outside your specialty, and to reuse two or three well-prepared stories (a challenging project, a technical-debt or sprint-conflict decision, and a deployment-reliability improvement) across all three rounds.
The AIA Group interview process
Candidates describe three stages in this order over roughly three to five weeks; the count and scheduling can shift by region and team.
HR Screening
Candidates report HR Screening as the first stage: an initial conversation about your background and how it fits the role. Pay expectations are also mentioned in connection with this stage. Treat it as the place to make your stack and your strongest project easy to understand, because the later technical rounds build on what you say here.
What to demonstrate
- How your background matches the role
- Fit for the role as a Software Engineer
- How clearly you summarise your recent work
How to prepare
- Write a short spoken walk through your CV that ends on the stack the role lists: Java or Node.js, SQL or NoSQL databases, Docker and Azure or AWS. Say which of these you have run in production.
- Prepare one project in a few sentences: the problem, the technologies you chose and the reason for each, which is the shape of the challenging-project question.
- Have notice period, location constraints and pay expectations ready, and ask the recruiter to confirm the remaining stages and scheduling in writing.
Technical Assessment
Candidates report the Technical Assessment as in-depth technical interviews focused on coding skills and problem-solving. The reported questions give you the types to practise for a stage like this: language internals (Java collections and data types, the Node.js event loop), SQL, database and cloud design, Docker, API testing and unit testing. Treat each as a short design conversation: state the mechanism, the cost, the alternative you rejected and how you would verify it.
What to demonstrate
- Coding skills
- Problem-solving approach
- Clear reasoning about code you write, including how you would test it
How to prepare
- Write a one-page comparison of ArrayList and LinkedList and another of Set and List implementations, with lookup, insertion and ordering costs, then say each aloud without notes.
- Predict the output order of a small Node.js snippet that mixes a timer, setImmediate and a promise, and name what would block the event loop in a real service.
- Write a single-stage and a multi-stage Dockerfile for a service in your language and compare image contents; run UNION and UNION ALL on two small tables with duplicate rows.
Leadership Discussion
Candidates report the Leadership Discussion as the final stage, where leaders look at alignment with the company's culture and values. Candidates also mention interviewers joining from other regions. Approach it with stories rather than facts: decisions you made, trade-offs you accepted and how you worked with people who disagreed or sat in another time zone.
What to demonstrate
- Alignment with the company's culture and values
- Collaboration in cross-border, multi-region teams
- Ability to explain technical decisions to non-specialists
How to prepare
- Prepare a technical-debt or sprint-conflict story in which you made the call, with the numbers or evidence you used and what you gave up.
- Prepare a CI/CD story that shows a reliability improvement you made, including one failure the pipeline missed and what you changed afterwards.
- Practise explaining one architecture choice (for example a database or a deployment approach) to a listener outside engineering, in short sentences with no unexplained jargon.
Where candidates lose points
Calling LinkedList faster for insertion and stopping there
State the full cost: an insert is constant time only once you hold the node, locating the position is linear, each element carries node overhead and poor cache locality, and ArrayList is usually the default. Say which workload would justify LinkedList (a queue or deque, not indexed access).
Describing the Node.js event loop as 'single-threaded, so it is slow'
Explain that it is a good fit for many concurrent I/O waits and a poor fit for long CPU-bound work on the main thread. Name what blocks it (synchronous loops, heavy JSON work) and the remedies: worker threads, a separate process or a queue.
Treating RUN and ENTRYPOINT as interchangeable, or giving a yes/no answer on multi-stage builds
RUN executes during the build and adds a layer; ENTRYPOINT defines the process a container starts, with CMD supplying default arguments. For multi-stage builds, say a single image works but carries compilers and source, while a final stage copies only the built artifact and gives a smaller image.
Walking through a project as a list of technologies with no constraints or alternatives
For each technology in the challenging-project story, say what the constraint was, which option you rejected and what you accepted as a cost. Finish with a reliability or productivity result you can point to.
Answering the CI/CD and technical-debt questions with 'we communicate and automate'
Pick one real incident or decision, state your role, the evidence you used and the trade-off. For CI/CD, name the stages, the rollback path and one failure the pipeline missed.
Questions & practice
What are the differences between ArrayList and LinkedList, and when would you choose one over the other?
Medium · Technical Fundamentals & Language Intern
Approach
- Start from the backing structures: ArrayList wraps a resizable Object[] (it implements the RandomAccess marker), while LinkedList is a doubly linked list of nodes that each hold item, next and prev references.
- Give the costs: ArrayList get(i) is O(1) and add at the end is amortised O(1) (capacity grows by about 1.5x with an array copy). Inserting or removing in the middle is O(n) because System.arraycopy shifts the tail. LinkedList adds or removes at either end in O(1), but get(i) is O(n) because it walks from the nearer end.
- Point out the trap: a LinkedList insert is only O(1) once a ListIterator is already at that position. Reaching the position is O(n), so a middle insert by index is O(n) in both lists.
- Memory and locality: each LinkedList node is a separate heap object, roughly 24 bytes with compressed oops, plus pointer chasing that defeats the CPU cache. ArrayList stores one contiguous reference array plus some spare capacity, so in practice it usually wins even on workloads heavy in inserts.
- Conclusion: default to ArrayList, use ArrayDeque for a stack or FIFO queue, and keep LinkedList for iterator-driven splicing. Neither is thread-safe, so name Collections.synchronizedList or CopyOnWriteArrayList when sharing across threads.
Follow-up
- For a FIFO work queue, would you pick LinkedList, ArrayDeque or a BlockingQueue, and why?
- What do ensureCapacity and trimToSize do, and when would you call them?
- On a List<Integer>, what is the difference between remove(1) and remove(Integer.valueOf(1))?
Explain the event loop in Node.js and its impact on performance.
Medium · Technical Fundamentals & Language Intern
Approach
- Explain the model: JavaScript runs on one thread, and libuv drives a loop with phases: timers, pending callbacks, poll (I/O), check (setImmediate) and close callbacks. Network I/O uses OS readiness APIs (epoll, kqueue, IOCP). File system calls, dns.lookup, some crypto and zlib run on the libuv thread pool, which defaults to 4 threads (UV_THREADPOOL_SIZE).
- Cover microtasks: the process.nextTick queue and then resolved promise callbacks drain after the current operation finishes, before the loop moves on. A recursive nextTick or promise chain can therefore starve I/O.
- Ordering details: setTimeout(fn, 0) versus setImmediate is nondeterministic from the main module, but inside an I/O callback setImmediate always runs first.
- Performance impact: any CPU-bound work blocks every in-flight request, for example a large JSON.parse, a synchronous crypto or fs call, or a catastrophic-backtracking regex. Fixes are worker_threads, a separate service or queue, or breaking the work into chunks yielded with setImmediate. To use more cores, run several processes with cluster or more containers.
- Measure it rather than guess: track event-loop delay with perf_hooks.monitorEventLoopDelay and p99 latency, and profile with --cpu-prof or clinic to find the blocking frame.
Follow-up
- Given a snippet mixing console.log, setTimeout 0, setImmediate, Promise.then and process.nextTick, what order does it print and why?
- An endpoint that generates PDFs makes every other request slow. How do you confirm the cause and fix it?
- Why can raising UV_THREADPOOL_SIZE help with fs- or bcrypt-heavy load but do nothing for slow HTTP calls?
What are the core data types in Java, and how do they differ in memory management?
Medium · Technical Fundamentals & Language Intern
Approach
- List the eight primitives with their sizes: byte 1, short 2, int 4, long 8, float 4, double 8, char 2 (a UTF-16 code unit) and boolean (size is JVM-dependent; boolean[] typically uses 1 byte per element). Everything else is a reference type.
- Explain where values live: primitive locals sit in the stack frame, while objects (arrays and boxed values included) live on the garbage-collected heap. A reference variable holds only a pointer, and primitive fields are stored inside their object on the heap. Java passes everything by value, including copies of references.
- Boxing costs: an Integer is a full object (about 16 bytes versus 4 for an int), so a List<Integer> costs several times more than an int[]. Autoboxing can throw a NullPointerException when unboxing null, and Integer.valueOf caches -128 to 127, which is why == on boxed values works only sometimes.
- Strings are immutable, and literals are interned in the string pool. Repeated concatenation in a loop allocates new objects, so use StringBuilder.
- Tie it to GC: short-lived objects die in the young generation (eden and survivor spaces) and survivors are promoted to the old generation. Mention that escape analysis can remove some allocations, and that money belongs in BigDecimal or long minor units, never double.
Follow-up
- Why does Integer a = 127, b = 127; a == b return true while the same code with 128 returns false?
- Is Java pass-by-reference? Show what happens when a method reassigns a parameter compared with mutating it.
- How would you diagnose an OutOfMemoryError: heap dump, GC logs, which tools?
Explain the difference between Set and List interfaces.
Medium · Technical Fundamentals & Language Intern
Approach
- State the contracts: a List is an ordered sequence with positional access (get(i), add(i, e)) that allows duplicates. A Set holds no two elements that are equal() and has no index; Set.add returns false when the element is already present.
- Map the implementations and their costs: ArrayList and LinkedList for List; HashSet (O(1) average contains, no order), LinkedHashSet (insertion order) and TreeSet (sorted, O(log n), a red-black tree) for Set. List.contains is O(n), which is why a membership-heavy workload belongs in a Set.
- Equality rules: HashSet depends on hashCode and equals being consistent, and mutating a field used in hashCode after insertion leaves the element unreachable. TreeSet uses compareTo or the Comparator, so a comparator that returns 0 for unequal objects silently drops elements.
- Edge cases: HashSet allows one null, while TreeSet with natural ordering throws NullPointerException on null. List.of and Set.of are immutable and reject nulls, and Set.of throws IllegalArgumentException on duplicate arguments.
- Practical idiom: to remove duplicates from a list while keeping order, use new ArrayList<>(new LinkedHashSet<>(list)), or stream().distinct().
Follow-up
- What breaks if a class overrides equals but not hashCode and you put its instances in a HashSet?
- How would you find the elements two large lists have in common, and what is the complexity?
- When would you choose a TreeSet over a HashSet even though it is slower?
Describe a challenging project you have worked on: what technologies did you use, and why?
Medium · Project Experience & Problem Solving
Approach
- Pick a project where you made the technical decisions yourself and can go one level below each tool. Every technology you name is something you may have to defend in depth.
- Structure it as constraints, then options, then the decision: the load, data sensitivity, team skills and deadline, then for each major choice (language, database, hosting such as ECS versus EC2, messaging) the alternative you rejected and the specific criterion that decided it.
- Spend the most time on the hardest technical problem: the symptom, how you narrowed it down (metrics, logs, profiling, a reproduction), the fix and how you confirmed it held.
- Close with measured results, such as latency, error rate, deployment frequency or incident count before and after, and one thing you would design differently now, with the reason.
Follow-up
- Why not the alternative you rejected? What would have had to be true for it to win?
- What failed in production on this project, and how did you find out?
- If load grew tenfold, which part of this design breaks first?
How do you document and test your APIs to ensure they are production-ready?
Medium · Project Experience & Problem Solving
Approach
- Documentation as a contract: an OpenAPI 3 spec, written contract-first or generated (springdoc-openapi for Spring Boot, swagger-jsdoc or a schema-first framework for Node), served through Swagger UI. It covers authentication, request and response examples, a consistent error body (RFC 9457 problem details), pagination, idempotency keys for unsafe retries and the versioning policy.
- Keep the spec honest in CI: lint it (for example with Spectral), fail the build on breaking changes with an OpenAPI diff, and validate real responses against the schema in tests.
- Testing layers: unit tests for validation and business logic, integration tests against a real database through Testcontainers, consumer-driven contract tests (Pact) or schema-based tests for consumers, and a post-deploy smoke test on the live endpoint.
- Production-readiness beyond correctness: load tests (k6, Gatling or JMeter) against a stated latency target, security tests for broken object-level authorization and input validation (the OWASP API Security Top 10), rate limiting, and health, metrics and tracing endpoints.
Follow-up
- How do you stop a change that breaks an existing consumer from reaching production?
- How do you test an endpoint that depends on a slow or flaky third-party API (WireMock, stubs, timeouts)?
- How do you version an API and retire the old version?
How do you approach unit testing in your development workflow?
Medium · Project Experience & Problem Solving
Approach
- Define the unit and the target: test behaviour through the public interface of a class or function, one reason to fail per test, laid out as arrange-act-assert. Name the stack: JUnit 5 with AssertJ and Mockito in Java, Jest, Vitest or Mocha in Node.
- Mock only at boundaries (database, HTTP clients, clock, randomness) and inject them, for example java.time.Clock or fake timers. Over-mocking internals produces tests that break on every refactor without catching bugs.
- Choose cases deliberately: the happy path, boundaries (0, 1, max, empty, null), invalid input and error paths. Use parameterised tests (@ParameterizedTest, test.each) for tables of inputs. That is white-box testing of branches, while API-level tests stay black-box.
- Keep tests deterministic and fast: no shared mutable state, no real network or sleep, seeded randomness. Run them on every pull request, and quarantine and fix flaky tests rather than retrying them.
- Treat coverage as a signal, not a goal: branch coverage from JaCoCo or Istanbul/c8 shows untested paths, and mutation testing (PIT) checks whether the assertions actually catch changes.
Follow-up
- How do you unit-test code that depends on the current time or a random value?
- What is the difference between a mock, a stub and a fake, and when do you use each?
- Is 100% coverage worth requiring? What would you enforce instead?
What is the difference between UNION and UNION ALL in SQL?
Medium · Technical Fundamentals & Language Intern
Approach
- Core difference: UNION returns the distinct rows of the combined result, removing duplicates both across and within the inputs. UNION ALL returns every row from both queries unchanged.
- Cost: UNION needs a sort or hash step to remove duplicates over the whole result, which uses memory and may spill to disk. UNION ALL can stream rows straight through. Default to UNION ALL when the inputs are disjoint (for example, separate tables per year or region) or when duplicates carry meaning, such as counting events.
- Rules for both: the inputs need the same number of columns in the same order with compatible types. Output column names come from the first SELECT, and one ORDER BY at the end applies to the combined result.
- Edge cases: in set operations NULLs compare as equal, so two all-NULL rows collapse under UNION. Adding a literal source column ('a' AS src) makes rows from different sources distinct, so UNION no longer removes cross-source duplicates.
- Verify with an example: two inputs of 3 and 2 rows with one shared row return 4 rows with UNION and 5 with UNION ALL.
Follow-up
- Is SELECT DISTINCT over a UNION ALL equivalent to UNION? When might one plan be faster?
- How would you return the rows in one table but not the other (EXCEPT versus NOT EXISTS versus LEFT JOIN ... IS NULL)?
- After a UNION ALL of two sources, how do you deduplicate on a business key but keep the most recent row?
How do you decide between using an SQL-based database versus a NoSQL database for a new project?
Medium · System Design & Architecture
Approach
- Decide from data shape and access patterns, not fashion: relational when entities are related, integrity matters, multi-row ACID transactions are needed and queries are ad hoc (reporting, joins). NoSQL when access patterns are known and key-based, the schema varies, or write volume needs horizontal partitioning.
- Name the NoSQL families and fit: document stores (MongoDB) for aggregates read as a whole, key-value (DynamoDB, Redis) for lookups by key, wide-column (Cassandra) for high write throughput on time-series data, graph (Neo4j) for relationship traversal.
- State the trade-offs precisely: DynamoDB requires modelling around partition and sort keys up front, and a hot partition key throttles. Cassandra offers tunable consistency. Relational databases scale reads with replicas, but sharding writes is hard. Many NoSQL stores now support transactions (MongoDB multi-document transactions, DynamoDB TransactWriteItems), so the real difference is modelling and query flexibility.
- Give a concrete split: records with relationships and audit needs, such as accounts and transactions, go relational. High-volume session or event data read by key goes to a key-value or document store.
- Acknowledge the cost of using both: two systems to run and back up, and keeping them consistent (outbox pattern or change data capture rather than dual writes).
Follow-up
- In a document store, when do you embed a one-to-many relationship and when do you reference it?
- The business wants ad hoc reports on data held in DynamoDB. How do you serve them?
- What does CAP actually force you to choose during a network partition, and what does PACELC add?
How would you approach adding a database to an application or website from scratch?
Medium · System Design & Architecture
Approach
- Start from requirements: the entities, the read and write paths with their volumes, the consistency each operation needs, data sensitivity (personal or financial data) and retention. Choose the engine from those, defaulting to a managed relational database such as RDS or Azure SQL / Azure Database for PostgreSQL unless the access pattern argues otherwise.
- Design the schema: normalise to roughly third normal form, give every table a primary key, and enforce integrity with foreign keys, NOT NULL, UNIQUE and CHECK constraints. Index the columns the real queries filter and join on, not every column.
- Treat schema as code: versioned migrations (Flyway or Liquibase in Java, Knex or Prisma migrations in Node) run by the CI/CD pipeline, plus seed data for tests.
- Data access layer: a connection pool (HikariCP, or the driver's pool in Node), parameterised queries with bind parameters to block SQL injection, explicit transactions around multi-row changes, and timeouts.
- Operations and security: credentials in AWS Secrets Manager or Azure Key Vault, a least-privilege application user, TLS in transit and encryption at rest, automated backups with point-in-time recovery and a restore that has actually been tested, plus slow-query monitoring.
Follow-up
- How do you rename or retype a column on a live table without downtime (expand and contract)?
- Reads have grown tenfold. What do you add first: indexes, read replicas or a cache, and what does each cost you?
- Where would you run migrations in a deployment so that two app versions can coexist?
Describe the differences between ECS and EC2 in a cloud environment.
Medium · System Design & Architecture
Approach
- Separate the layers: EC2 is virtual machines (IaaS). You pick the instance type and AMI, patch the OS and scale with Auto Scaling groups. ECS is AWS's container orchestrator: task definitions, services and clusters schedule containers and keep the desired count running.
- They are not alternatives. ECS runs tasks either on the EC2 launch type (container instances you manage, running the ECS agent) or on Fargate (AWS manages the hosts and you pay per task for vCPU and memory).
- Trade-offs: the EC2 launch type gives host control (GPU instances, daemons, bin-packing, Reserved or Spot pricing at steady high utilisation) but adds patching and capacity work. Fargate removes host management and isolates each task, at a higher unit cost and with fewer host-level options.
- ECS service features worth naming: ALB target-group integration, service auto scaling, IAM task roles (per-task credentials instead of the instance role), rolling deployments tuned by minimumHealthyPercent and maximumPercent with the deployment circuit breaker, or blue/green through CodeDeploy.
- Place them on the map: plain EC2 suits legacy or stateful software that is not containerised. EKS is the choice when Kubernetes portability or its ecosystem matters. On Azure the rough analogues are Virtual Machines versus Container Apps or AKS.
Follow-up
- How does an ECS rolling deployment avoid dropping traffic, and what triggers a rollback?
- How should a container get AWS credentials, and why not use the instance profile?
- When would you choose EKS over ECS?
Why do we use ENTRYPOINT in a Docker file when we already have RUN?
Medium · System Design & Architecture
Approach
- Build time versus run time: RUN executes while the image is built, and each RUN commits a new layer (installing packages, compiling). ENTRYPOINT runs nothing at build time. It records the process to start each time a container is created from the image.
- ENTRYPOINT and CMD together: ENTRYPOINT fixes the executable, and CMD supplies default arguments. Arguments given to docker run replace CMD, while ENTRYPOINT changes only with --entrypoint.
- Use the exec form, ENTRYPOINT ["java", "-jar", "app.jar"]. The shell form wraps the command in /bin/sh -c, so the app is not PID 1, may never receive SIGTERM and gets SIGKILLed when the docker stop grace period (10 seconds by default) runs out. Shell-form ENTRYPOINT also ignores CMD and run arguments.
- Wrapper script pattern: an entrypoint.sh that does setup (render config, wait for dependencies) and then calls exec "$@" so the real process takes over PID 1. Add tini or docker run --init when the process spawns children that need reaping.
- Keep runtime work out of RUN: anything that depends on the environment, secrets or a live database (for example migrations) cannot happen at build time and must run at start or as a separate job.
Follow-up
- Given ENTRYPOINT ["python", "app.py"] and CMD ["--port", "8080"], what runs for docker run img and for docker run img --port 9090?
- Containers take about 10 seconds to stop on every deploy. What is the likely cause and the fix?
- Should database migrations run in the entrypoint, in a separate task or in the pipeline? What are the trade-offs?
Can an application be built and run using only one base image, or are multi-stage builds preferred?
Medium · System Design & Architecture
Approach
- Answer directly: yes, one image can build and run the app (for example, a Maven JDK image that compiles and then runs java -jar). The cost is that the shipped image carries the compiler, build tools, source and caches, so it is larger and slower to pull and has more packages for CVE scanners to flag.
- Multi-stage fix: FROM maven:... AS build compiles, then FROM eclipse-temurin:<ver>-jre runs and copies only the artifact with COPY --from=build. In Node, the build stage installs devDependencies and builds, and the runtime stage runs npm ci --omit=dev on a slim or distroless base.
- Layer caching: copy pom.xml or package.json with the lockfile and install dependencies before copying source, so a code change reuses the cached dependency layer and CI builds stay fast.
- Hardening: run as a non-root USER, and never pass secrets as ARG or ENV because they persist in image metadata. Use BuildKit RUN --mount=type=secret instead. Pin base image versions or digests.
- Base-image trade-offs: Alpine uses musl libc, which can break native modules. Distroless has no shell, which is good for attack surface but awkward for debugging (use ephemeral debug containers). Use docker build --target to stop at a test stage in CI.
Follow-up
- How do you make sure a private registry token used during the build never ends up in the final image?
- How would you debug a running distroless container that has no shell?
- Your image is 1.2 GB. Walk through how you find what is taking the space and cut it down.
Every query on one table stalls for forty seconds mid-deploy
Medium · lock contention · ddl · migrations
During a release on PostgreSQL, every query touching resource times out for about 40 seconds and then recovers with no intervention. The release ran one migration, ALTER TABLE resource ADD COLUMN archived_reason TEXT, and the migration log shows it completing in 6 ms. Unrelated tables showed no change in error rate. Explain how a 6 ms statement caused a 40-second stall, give the ordered checks you would run on a live system to confirm it, and give the migration procedure that prevents a repeat.
Approach
- Separate the statement's duration from the lock's duration. ADD COLUMN with no default is a catalogue-only change and genuinely runs in milliseconds, but it requires ACCESS EXCLUSIVE, and it cannot acquire that until every transaction already touching the table has finished.
- Account for the queueing, which is the part that surprises people. A lock request that is waiting blocks later requests for conflicting modes behind it rather than letting them overtake, so one long-open transaction holds the DDL and the DDL holds all the traffic. The stall length is set by the longest open transaction, not by the size of the change.
- Confirm on a live system in this order: pg_stat_activity for that table ordered by xact_start, looking for the oldest transaction and specifically for state = idle in transaction; then pg_locks where granted = false to find the waiter; then join them on pid to name blocker and blocked. pg_blocking_pids() does that join for you and is the fastest single call.
- Prevent rather than merely time it better. Set lock_timeout to a second or two on the migration session so the DDL abandons the queue after a bounded wait and is retried, instead of holding it for as long as the oldest transaction lives. Be exact about what that buys: queries arriving during the wait still queue behind the pending ACCESS EXCLUSIVE request, so each attempt costs them up to one lock_timeout of added latency. The outage goes from 40 seconds to about one second per attempt, not to zero. Also run migrations away from deploy-time peaks, and put a statement timeout and an idle-in-transaction timeout on the analytics role that opens the long transactions.
Follow-up
- The same release also wants NOT NULL on that column. What is the sequence that gets there without a long lock?
- Your lock_timeout retry fails ten times in a row because the analytics transaction is always open. What do you change?
Suggested practice checklist
The plan follows the three groups of reported questions: language internals and SQL first, then databases, containers and cloud, then API testing, and finally the delivery stories that the Leadership Discussion draws on.
Day 1 — Java collections and data types
- Write a comparison of ArrayList and LinkedList covering index access, append, middle insertion, iteration and memory per element, and explain why cache locality often beats the textbook complexity.
- Write the same for Set and List with HashSet, LinkedHashSet and TreeSet: duplicates, ordering, lookup cost and the equals/hashCode contract.
- Summarise the Java primitives versus reference types: stack and heap, default values, boxing, the Integer cache, and what garbage collection reclaims.
Deliverable: A one-page cheat sheet on collections and data types, plus spoken answers to the ArrayList, Set versus List and Java data types questions.
Day 2 — Node.js event loop and SQL set operators
- Draw the event loop phases and the microtask queue, then write a snippet mixing setTimeout, setImmediate, process.nextTick and a promise and predict its output order before running it.
- Show how a CPU-heavy loop stalls request handling and list the remedies: worker threads, a child process, or moving the work to a queue.
- Create two small tables with overlapping rows, run UNION and UNION ALL, and explain deduplication cost, column compatibility and when UNION ALL is correct.
Deliverable: A predicted-versus-actual output table for the event-loop snippet, and a short SQL script with the UNION and UNION ALL results explained.
Day 3 — Adding a database and choosing SQL or NoSQL
- Outline how you would add persistence to an application that has none: entities and access patterns, schema, connection pooling, a migration tool, credentials, backups and indexes.
- Build a decision table for SQL versus NoSQL (relationships and joins, transactions, schema flexibility, query patterns, scale, team skills) and apply it to one project of each kind.
- Take one join-heavy query, say which indexes support it, and explain how you would check the plan.
Deliverable: A written outline for adding a database to a new application and a one-page SQL versus NoSQL decision table with two worked examples.
Day 4 — Docker, ECS and EC2
- Write a single-stage and a multi-stage Dockerfile for a service in your language and compare image size and what each image contains.
- Explain RUN, CMD and ENTRYPOINT, including exec form versus shell form and why it affects signal handling at shutdown.
- Make an ECS versus EC2 table: what you manage, how scaling works, cost model and when plain EC2 is enough.
Deliverable: Two working Dockerfiles with a size comparison, and a half-page ECS versus EC2 table.
Day 5 — API documentation and unit testing
- Write an OpenAPI description for a small REST endpoint with request and response examples and error codes.
- Write unit tests for its service layer using mocks, and one integration test against a real database in a container; label each as white-box or black-box.
- Prepare a spoken test-strategy answer: what you unit test, what you refuse to mock, how you cover edge cases and what coverage does not tell you.
Deliverable: An OpenAPI file, a unit-test file, one integration test and a written test-strategy answer.
Day 6 — CI/CD reliability and technical-debt stories
- Write out your CI/CD pipeline stage by stage with the rollback path and one incident, then outline the technical-debt or sprint-conflict story with its evidence and trade-off.
Deliverable: Two story outlines (CI/CD reliability and technical debt) and notes from both practice questions.
Day 7 — Project story and full run-through
- Write your challenging-project answer with constraints, options considered, the trade-off you accepted and a reliability or productivity result.
- Say your weakest five answers from days 1 to 6 aloud without notes and fix any that need a mechanism or a trade-off.
- Explain one technical decision to a non-engineer in a few short sentences, and list your questions for the recruiter about the remaining stages.
Deliverable: A finished project answer, a list of your five weakest questions with fixes, and a plain-language explanation of one decision.
Project, technical debt and delivery stories
Candidates report that the final stage is a leadership discussion about fit with the company's culture, with interviewers who may be in other regions, and that questions about past projects ask for the reasoning behind your choices. For this role, prepare stories about technical decisions, sprint trade-offs, delivery reliability and cross-team work, each with your own role and a concrete result.
- Describe a challenging project you have worked on: what technologies did you use, and why?
- Tell me about a time you explained a technical decision to someone outside engineering and how you checked they understood.
- Describe working with colleagues in another region or time zone and what you changed to make it work.
- Tell me about feedback on your code or design that you disagreed with and what you did next.
How do you handle technical debt or conflicts within a sprint?
Medium · Project Experience & Problem Solving
Approach
- Choose one concrete case with a nameable debt item: a duplicated validation module, a flaky test suite, a hand-rolled deploy script. Say what it cost in measurable terms, such as extra hours per ticket, repeat bugs or blocked releases. One number from your own team makes the story checkable.
- Show how you decided between fixing now, scheduling it and living with it. Typical mechanisms are a fixed share of each sprint for cleanup, attaching the cleanup to a feature that touches the same code, or a ticket with an owner and a trigger condition. Say which you chose and why.
- For the conflict half, state both positions fairly (scope against quality, or two engineers' designs) and show how you settled it with evidence, not seniority: a benchmark, a failing test, an incident count or a short spike. Name what each side gave up and who signed off, including the product manager.
- Protect the sprint while you pay down debt: characterisation tests before the refactor, small reviewable pull requests, a feature flag if behaviour could shift. Close with the outcome and one thing you would change. The mistake that sinks this answer is a story where the team decided and you watched, or a vague line about communicating well with no decision in it.
Follow-up
- How did you persuade a product manager to give sprint capacity to work with no visible feature?
- What would you have done if the deadline had not moved?
- How did you know the refactor had not changed behaviour?
Explain your experience with CI/CD pipelines; how do you ensure deployment reliability?
Medium · Project Experience & Problem Solving
Approach
- Describe a pipeline you actually ran, stage by stage: commit, build, unit tests, static analysis and dependency scan, container image build, integration tests, deploy to a test environment, promotion, post-deploy smoke check. Name your tool and say what each stage is allowed to block.
- Explain the reliability mechanisms: one immutable, versioned artifact (an image tagged by commit) promoted unchanged between environments, configuration kept outside the image, health checks that gate traffic, and a rollback you have rehearsed. Say when you used rolling, blue-green or canary releases, and how feature flags separate deploying code from releasing it.
- Cover database changes, since they are the usual reason a rollback fails: backward-compatible migrations done in steps (add the column, deploy, backfill, remove the old one later) so the previous version still runs against the new schema.
- Back the story with evidence: failed-deploy rate, time to restore service, flaky-test count. Include one incident where the pipeline caught a problem or missed one and what you changed. If your build used Docker or a cloud target such as Azure or AWS, say how (for example multi-stage builds to keep images small). Avoid a bare list of tool names; the story must show your decisions.
Follow-up
- A deploy passes every check and still breaks production: what do you do in the first minutes?
- How do you handle a database migration that cannot be rolled back?
- How would you make a flaky integration stage trustworthy again?
Ship under a deadline and bound the debt you chose
Medium · pagination · technical debt · tradeoffs
You have four days to ship a tenant-facing listing endpoint. The version you would defend uses keyset pagination over (tenant_id, status, updated_at DESC, resource_id DESC); the version you can finish uses LIMIT/OFFSET with no matching index. Describe a deadline call you actually made of this shape: what you shipped, what you knowingly deferred, how you bounded the damage with a mechanism rather than an intention, and the specific numeric condition that would force the follow-up. Name who you told and where you wrote it down.
Approach
- Name the deferred failure precisely instead of calling it slow. OFFSET n makes the database produce and discard n rows, so cost grows with page depth; without an index matching the sort, every matching row is read and sorted before the limit applies; and rows inserted between two page fetches shift across the boundary so items are skipped or repeated with nothing in the response to signal it.
- Bound the blast radius with something mechanical rather than a promise: cap maximum page depth, cap page size, restrict the endpoint to one internal caller, or keep it behind a flag. State which failure each cap removes and which it leaves standing.
- Attach a number to the trigger and wire it to an alarm: the first tenant crossing N resources, or the endpoint's p99 crossing its share of the 400 ms budget, so the debt announces itself instead of waiting to be remembered.
- Write it where the next engineer looks, which is the code and the ticket, not a chat message: what was deferred, why, the cap, and the trigger.
Follow-up
- At what page depth does the offset version breach your latency budget, given your page size and row counts?
- What breaks first when you switch to keyset pagination later, and what does a client holding an old page token see?