Pre-Interview Questionnaire - Complete Solutions

This document provides my comprehensive solutions and explanations for all tasks in the Aon Developer Pre-Interview Questionnaire.

Task 1: Timing a Method

From the Questionnaire:

public interface IncDec
{
    void increment();
    void decrement();
}
public class MyIncDec implements IncDec
{
    private int x;
    public MyIncDec(int x) {
        this.x = x;
    }
    public void increment() {
        this.x++;
    }
    public void decrement() {

My Reasoning Message:

If I act as the compiler, the given code above from the interview-questionaire will not compile successfully. The problem is that the decrement() method in the MyIncDec class is incomplete. Since the class implements the IncDec interface, it is required to provide full implementations of both increment() and decrement(). Because the method body is missing and the braces are unbalanced, the compiler would throw errors such as: '}' expected or reached end of file while parsing (because the method is not closed properly), and MyIncDec is not abstract and does not override abstract method decrement() in IncDec (because the method is not implemented correctly).

However, logically, I can identify the missing part. To make the solution correct, I would complete the decrement() method, provide the proper implementation, and create the necessary Java files for the solution.

Implementation Files:

Part 1: Modify the existing IncDec

Modify the existing IncDec interface implementation to measure and log the execution time (in milliseconds) for each call/invocation to the increment() and decrement() methods. Your solution should not require any code change in current classes that implement IncDec interface.

Solution Analysis

The key constraint is that we cannot modify existing IncDec implementations. This is a classic use case for the Proxy Design Pattern.

Why Proxy Pattern?

  • Non-intrusive: No changes required to existing classes
  • Transparent: Maintains the same interface
  • Composable: Works with any IncDec implementation
  • Single Responsibility: Separates timing concerns from business logic

Implementation Details

Key Design Decisions:

1. High Precision Timing:

long startTime = System.nanoTime(); // Nanosecond precision
// ... execute method ...
long endTime = System.nanoTime();
double executionTimeMs = (endTime - startTime) / 1_000_000.0; // Convert to milliseconds

Rationale: System.nanoTime() provides the highest precision available in Java, more accurate than System.currentTimeMillis() for measuring short execution times.

2. Exception Safety with try-finally:

try {
 delegate.increment();
} finally {
 // Timing logic always executes, even if delegate throws exception
 long endTime = System.nanoTime();
 // ... logging ...
}

Rationale: Ensures timing measurements are always logged, even if the wrapped method throws an exception.

3. Instance Identification:

LOGGER.info(String.format("[%s] increment() executed in %.3f milliseconds", instanceIdentifier, executionTimeMs));

Rationale: Distinguishes between different proxy instances in logs, essential for debugging in multi-instance scenarios.

Usage Example

// Original implementation (no changes required)
IncDec original = new MyIncDec(10);

// Enhanced with timing using proxy
IncDec timed = TimingIncDecProxy.withTiming(original, "MyInstance");

// All method calls now automatically logged with timing
timed.increment(); // Logs: [MyInstance] increment() executed in 0.023 milliseconds
timed.decrement(); // Logs: [MyInstance] decrement() executed in 0.018 milliseconds

Part 2: Imagine you need to do something similar

Imagine you need to do something similar (timing of method calls) for many places in an application. What other ideas come to mind, e.g. could you propose other ideas for getting timing information more conveniently?

1. Aspect-Oriented Programming (AOP)

In my Spring Boot projects, I use AOP with annotations (e.g., @Timed) to capture method execution times. This keeps timing logic centralized and avoids scattering code across services, especially useful in large enterprise applications with many APIs.

2. Bytecode Instrumentation

While less common in my daily work, Java agents and tools like Javassist can help monitor legacy or third-party libraries without code changes. This is valuable in enterprise systems that I’ve integrated with where direct modification wasn’t possible.

3. Annotation-Based Decorators

On the backend, I’ve built custom annotations to measure execution times at compile time. On the frontend, Angular also supports decorators, which I can extend to log performance in components or services. This keeps monitoring type-safe and IDE-friendly.

4. Method Interceptors

In Spring Boot, I’ve used interceptors and dynamic proxies to add cross-cutting concerns like logging and timing at runtime. In Angular, I use HTTP Interceptors to capture request/response times, giving clear visibility into API performance end-to-end.

5. Profiler Integration

For backend analysis, I’ve worked with JVM profilers and monitoring tools to track memory and execution bottlenecks. On the frontend, I rely on Angular DevTools and Chrome Lighthouse for runtime profiling of UI rendering and API call performance.

6. Metrics Libraries

I’ve used Micrometer in Spring Boot microservices to collect metrics and expose them to Prometheus and Grafana dashboards. This aligns with my experience in CI/CD pipelines and cloud deployments, ensuring that both backend and frontend services are observable in production.

Task 2: Find Arrays

Problem Statement

Implement a method that given two arrays as parameters will find the starting index where the second parameter occurs as a sub-array in the array given as the first parameter. Then implement unit tests for this method.

Example: [4,9,3,7,8], [3,7] should return 2.

Solution Analysis

This is a classic sub-array (or substring) matching problem. I implemented two approaches:

When we see O(n * m) in complexity analysis:
n = length of the main array (the bigger dataset).
m = length of the sub-array (the smaller dataset we’re comparing, searching, or iterating over).
O(n * m) means the runtime grows in proportion to both n and m multiplied together.

Implementation Files:

  • SubArrayFinder.java - Main implementation with both basic and KMP(Knuth-Morris-Pratt) algorithms
  • SubArrayFinderTest.java - Comprehensive unit tests

1. Basic Brute Force Algorithm

Time Complexity: O(n*m) where n = main array length, m = sub-array length

Space Complexity: O(1)

Approach: Check each possible starting position in the main array

2. Knuth-Morris-Pratt (KMP) Algorithm

Time Complexity: O(n+m) - more efficient for large arrays

Space Complexity: O(m) for the partial match table

Approach: Uses a partial match table to skip unnecessary comparisons

Key Implementation Features

Input Validation

  • Null checks with descriptive error messages
  • Edge case handling (empty arrays, sub-array longer than main array)
  • Proper exception throwing with IllegalArgumentException

Algorithm Correctness

  • Handles all edge cases correctly
  • Works with negative numbers
  • Handles duplicate patterns correctly
  • Returns -1 when no match is found
  • Returns 0 for empty sub-array (mathematical correctness)

Code Quality

  • Comprehensive line-by-line comments
  • Helper methods for better readability
  • Clear variable names and logic flow
  • Separate implementation for advanced algorithm (KMP)

Test Coverage

Comprehensive unit tests covering:

  • Basic Functionality: Problem example, beginning/end matches, single elements
  • Edge Cases: Empty arrays, identical arrays, negative numbers, duplicate patterns
  • Error Cases: Null input validation
  • Algorithm Comparison: Verifies both implementations give same results
  • Performance Testing: Compares execution times of both approaches

Task 3: Review a Method

Code Under Review

01 boolean checkStudy(Student student){
02    if(student.isLoggedIn()){
03        if(student.getTeacherName() == "Lee") {
04            if(student.isEnrolledInMaths()){
05                if(student.hasDistinction())
06                    return true;
07            }
08        }
09    }
10    return false;
11 }

My Code Review Feedback

Critical Issues:

1. String Comparison Bug (Line 3):

Problem: Using == for string comparison instead of .equals()

Issue: == compares object references, not string content

Fix: Change to student.getTeacherName().equals("Lee")

Additional consideration: Add null safety: "Lee".equals(student.getTeacherName())

2. Null Pointer Risk (Line 2, 3, 4, 5):

Problem: No null checks for student parameter or method return values

Risk: NullPointerException if student is null or if any getter returns null

Fix: Add proper null validation at the beginning of the method

Code Quality Issues:

3. Deep Nesting (Lines 2-9):

Problem: Four levels of nested if statements reduce readability

Impact: Difficult to understand, maintain, and test

Fix: Use guard clauses or combine conditions with && operator

4. Missing Braces (Line 5-6):

Problem: if statement without braces is error-prone

Risk: Future modifications might introduce bugs

Fix: Always use braces for control structures

5. Unclear Method Purpose:

Problem: Method name "checkStudy" doesn't clearly indicate what it returns

Impact: Other developers need to read implementation to understand behavior

Suggestion: Rename to something like "isEligibleForAdvancedStudy" or add comprehensive JavaDoc

My Recommended Refactored Version:

/**
* Checks if a student is eligible for advanced study based on:
* - Student is logged in
* - Student's teacher is "Lee"
* - Student is enrolled in Mathematics
* - Student has distinction
*
* @param student the student to check (must not be null)
* @return true if student meets all eligibility criteria, false otherwise
* @throws IllegalArgumentException if student is null
*/
boolean isEligibleForAdvancedStudy(Student student) {
  if (student == null) {
  throw new IllegalArgumentException("Student cannot be null");
}

return student.isLoggedIn()
&& "Lee".equals(student.getTeacherName())
&& student.isEnrolledInMaths()
&& student.hasDistinction();
}

Task 4: Trends

Question

Describe a trend which has happened with enterprise applications over the last two or three years. How do you see this trend influencing corporate application development? What are you doing to prepare for this trend?

My Answer

(~1,460 characters)

Trend: Cloud-Native Microservices and API-Driven Frontends

In recent years, enterprise applications have moved from monolithic systems to microservices with containerization and API-first designs. Java Spring Boot is widely used for backend services, while Angular and other modern frameworks consume APIs to deliver responsive, scalable user experiences. This shift is driven by the need for faster delivery, high availability, and distributed teams working in Agile environments.

Influence on Corporate Application Development:

  • Architecture: Backends are decomposed into Spring Boot microservices, frontends into modular Angular components.
  • DevOps: CI/CD pipelines with tools like TeamCity, Jenkins, and AWS enable frequent, reliable releases.
  • Scalability: APIs and microservices allow independent scaling of frontend and backend workloads.
  • Security: Greater focus on authentication, authorization, and secure API gateways.

My Preparation for This Trend:

  • Built REST APIs with Java Spring Boot and integrated them into Angular frontends.
  • Implemented CI/CD pipelines and multi-stage deployments across dev, staging, and prod.
  • Applied SOLID principles, design patterns, and modular Angular architecture for maintainability.
  • Worked in Agile Scrum with daily standups, sprints, and code reviews to deliver incrementally.

This trend reshapes enterprise delivery by requiring developers to master both backend microservices and frontend integration, ensuring applications remain scalable, secure, and user-focused.

Task 5: Describe Software that Delivers User Value

Question

Describe a product or project you worked on that delivered high value to the user. Which specific aspects did you think were critical in successfully creating value for the user?

Answer

Project: RelayDoc – Automated Railway Relay Testing System

I contributed to RelayDoc, a Java-based system that automated testing of railway relays. It measured coil resistance, contact setup, and other parameters—ensuring safety, accuracy, and compliance while eliminating manual errors.

High Value Delivered:

  • Boosted safety and reliability by reducing human error
  • Cut testing time through automation
  • Provided real-time logs for quick decisions
  • Met compliance with standardized results

Critical Success Aspects:

1. Spring Boot Backend: Multithreaded design for fast, accurate processing.

2. API & Database Integration: MongoDB storage with Angular dashboards.

3. Agile Collaboration: Delivered iteratively with QA and frontend teams.

4. Scalable Design: Applied SOLID principles and clean architecture.

5. User Feedback: Engineer input shaped dashboards and recovery features.

By combining Java, Angular, and Agile, RelayDoc delivered safer, faster, and more reliable railway operations.

Task 6: Your Values

Question

What are your core values/principals as a software engineering professional working in a team environment and why are they important to you?

Answer

My Core Software Engineering Values:

1. Clean and Readable Code

Whether I’m writing Java Spring Boot APIs or Angular components, I treat code as communication. Clear naming, modular design, and consistent patterns make the system easy for teammates to understand, maintain, and extend.

2. Test Early, Fix Early

I believe in failing fast by using unit tests, integration tests, and code reviews. In both backend APIs and Angular frontends, catching issues early keeps delivery reliable and builds team trust.

3. User-First Decisions

Every feature should serve the end user. I balance technical design with business value, ensuring APIs are performant and Angular UIs are intuitive and responsive.

4. Shared Ownership

I see the entire system—backend and frontend—as the team’s responsibility. I actively review pull requests, share knowledge, and support teammates so no part of the system becomes a silo.

5. Continuous Learning

Technology evolves quickly, so I stay current with Java, Angular, DevOps tools, and design patterns. I also mentor juniors, which strengthens both my growth and the team’s capabilities.

These values shape how I collaborate and deliver. They ensure that our software is not only technically sound but also valuable, scalable, and sustainable for the business and end users.

Compilation and Execution Instructions

To compile all Java files:

javac -d out src/*.java

To run Task 1 demonstration:

java -cp out TimingDemoMain

To run Task 2 tests:

java -cp out SubArrayFinderTest

Note:
All solutions are my preferred approaches, implemented with comprehensive error handling, detailed comments, and thorough testing to demonstrate professional software development practices. -- Christopher Natan