This is a build-from-scratch challenge. The candidate reads the assessment brief in
README.md, works out what the rules mean, and implements them. The app runs and the
HTTP contract exists; everything behind it is empty.
Pick 1-2 tasks matching the candidate's level. Let them read the brief and explore the codebase for ~10 minutes before starting.
Sequencing note: Tasks 1-3 are the foundation — later tasks assume a working distance calculation and endpoint. For a mid or senior candidate who should not spend the session on arithmetic, hand them Task 1 as pre-work (or let them stub the distance) and start at Task 5 or 7.
The brief is deliberately ambiguous. Boundary wording, the definition of "travelling west", and the timezone of the take-off time are all unresolved. Do not resolve them for the candidate — the point is to see whether they notice, ask, and commit to a decision. "I chose X because Y" is a full pass; guessing silently is not.
Context: Nothing in the codebase computes distance yet. The brief requires the Haversine formula over the departure and arrival coordinates.
What to do:
- Create a class responsible for calculating great-circle distance in kilometres
- Implement the Haversine formula (no geo library — write it yourself)
- Unit-test it against known distances
Acceptance Criteria:
- Barcelona
41.3851, 2.1734→ London51.5074, -0.1278is about 1,139 km - Barcelona → Sydney
-33.8688, 151.2093is about 17,180 km - The calculation lives in its own class, not in the controller
- At least one test asserts a known distance within a stated tolerance
Hints:
Math.toRadians()already exists — no need to write a degrees-to-radians helper- Use the Earth's mean radius, 6,371 km
- Watch out for integer division and for rounding too early
- Ask them: how accurate is this for a real flight? Is that accuracy good enough here?
Context: The brief defines two passenger bands with different maximum distances. This is the simplest of the three rules — get the shape right and the others follow.
What to do:
- Implement the maximum flight range rule
- Decide what happens when a distance is exactly equal to a limit, and be ready to justify it
- Write unit tests covering both passenger bands
Acceptance Criteria:
- Both passenger bands from the brief are enforced
- The candidate states their reading of "cannot exceed" and the tests pin it
- Tests cover each band, not just one
- No magic numbers in the middle of a condition — named constants at minimum
Hints:
- Re-read the brief's wording for the bands carefully, then say out loud which band a flight with exactly 150 passengers belongs to. It is the most commonly failed detail in this exercise
- Keep the rule out of the controller
Context: FlightPlanController.assess() throws UnsupportedOperationException. The brief asks
for a feasibility verdict plus a list of feedback messages.
What to do:
- Return a real
FeasibilityResponsefrom the endpoint - Report every violated rule, not just the first one
- Keep the controller thin — parse, delegate, respond
Acceptance Criteria:
- A feasible flight returns
200withfeasible: trueand emptyfeedback - An infeasible flight returns
200withfeasible: falseand a message per violation - A flight breaking two rules produces two messages
- The controller contains no distance maths and no rule logic
Hints:
- The brief says "a list of feedback messages" — plural, so do not return early on the first failure
- Discuss: why is an infeasible flight
200and not400? What would422mean here? - The feedback messages are read by an operator who has to fix the plan — include the offending value
Context: FlightPlanController logs with System.out.println(). This is a common code smell.
What to do:
- Add an SLF4J logger to the controller
- Replace the
System.out.println()call with an appropriate log level - Use parameterised messages (e.g.
log.info("Assessing flight {}", flightNumber))
Acceptance Criteria:
- No
System.out.println()anywhere insrc/main - Uses
LoggerFactory.getLogger()(or Lombok's@Slf4jif they add Lombok) - Log level is appropriate —
infofor a business event, noterror
Hints:
- SLF4J comes with Spring Boot — no extra dependency needed
- Ask why string concatenation in a log call is worse than a
{}placeholder - Ask what else is worth logging here, and what must never be logged
Context: The brief restricts take-off times for long flights only.
What to do:
- Implement the rule
- Decide exactly which flights the rule applies to, and which times are allowed
- Test both sides of every boundary the rule has
Acceptance Criteria:
- The rule only affects the flights the brief says it affects
- Both ends of the time window are tested, one minute either side
- A flight of exactly the threshold distance is handled according to a stated decision
- The rule is independently testable — no Spring context needed
Hints:
LocalTime.isBefore()andisAfter()are both strict. The brief says "between 06:00 and 14:00" — does that include 06:00 and 14:00? Make them commit to an answer- Same question for the threshold distance: the brief says "longer than 9,000 km", so what happens at exactly 9,000?
- Ask what timezone 06:00 refers to. There is no right answer in the brief — listen for whether they notice
Context: The brief adds two conditions for flights "travelling west". It never defines what travelling west means.
What to do:
- Decide how to determine that a flight travels west, using only the data available
- Implement both conditions
- Make sure the operator can tell which of the two conditions failed
Acceptance Criteria:
- The direction test is explained and defended, not just written
- Both conditions are enforced, and a flight breaking both says so
- Flights that are not westbound are unaffected by either condition
- Tests cover a westbound flight, a non-westbound flight, and both failure modes
Hints:
- All they have is two longitudes. Ask what the simplest test is, then ask when it is wrong
- Prompt if needed: what does their rule say about a flight from Tokyo
139.65to Los Angeles-118.24? Which way does that flight actually go? (Task 12 fixes this — for now, noticing is enough) - If one rule class reports one message, a flight that is both too late and too far will hide a problem from the operator. Is that acceptable?
Context: The brief says the solution must be "maintainable and easy to extend". If the three
rules are if blocks in one method, that requirement is not met.
What to do:
- Introduce an abstraction so each rule is its own unit
- Let the application discover the rules rather than hard-coding a list
- Refactor the existing rules onto it, keeping all tests green
Acceptance Criteria:
- Adding a fourth rule means one new class and no edits to any existing rule or service
- Each rule can be unit-tested without starting Spring
- The service that runs the rules does not name a single concrete rule
- Distance is calculated once per request, not once per rule
Hints:
- Spring will inject every bean of a type into a
List<T>constructor parameter — that is the whole trick - Ask what happens to the order of the feedback messages, and whether they can rely on it
- Ask them to name the trade-off they just accepted. Every design has one
- Push back with "isn't an interface overkill for three rules?" and see if they can defend it — the answer is in the brief
Context: The endpoint accepts anything. Send an empty body, a blank flight number, zero passengers or a latitude of 500 and it will happily try to assess it.
What to do:
- Add validation for the fields described in the brief
- Return
400with useful, field-level messages - Report all the input problems at once, not just the first
Acceptance Criteria:
- Flight number is required and not blank; passengers must be positive
- Latitude is within ±90 and longitude within ±180
- An invalid request returns
400with a message per problem - A valid-but-infeasible request still returns
200— validation and feasibility stay separate
Hints:
spring-boot-starter-validationis not in thepom.xmlyet — they will need to add it@Validon the controller parameter, constraints on the record components- Handle
MethodArgumentNotValidExceptionin a@RestControllerAdvice - Ask why the fields in
FlightPlanRequestare boxed types (Integer,Double) and what would happen ifdepartureLatitudewere a primitivedoubleand the caller omitted it. The answer is that0.0is a perfectly valid coordinate — a real bug
Context: Two different failures are easy to conflate: a request the API cannot understand, and a flight plan it understands perfectly and rejects.
What to do:
- Make sure a client mistake never surfaces as a
500 - Give the two cases distinct, deliberate responses
- Check what happens today with
"takeOffTime": "25:99"and with a truncated JSON body
Acceptance Criteria:
- Unparseable input returns
400with a helpful body, not a stack trace - An infeasible flight returns
200withfeasible: false - No handler swallows genuine server errors silently
Hints:
ProblemDetail(RFC 9457) is built into Spring — worth discussing even if they roll their own- Ask what is wrong with
@ExceptionHandler(Exception.class). Answer: it converts real bugs into tidy responses with nothing logged, and can shadow Spring's own sensible defaults HttpMessageNotReadableExceptionis the one to catch for malformed JSON
Context: This is the real test of Task 7. The interviewer invents a rule on the spot and the candidate implements it while being watched.
What to do: Give them one of these, and do not let them see it in advance:
- "No take-offs between 23:00 and 05:00."
- "Aircraft with more than 300 passengers may not fly further than 5,000 km."
- "Eastbound flights over 10,000 km must take off before 09:00."
Acceptance Criteria:
- Implemented as one new class with no edits to existing rules or the service
- Comes with its own unit tests, including the boundaries
- Done in under ten minutes
Hints:
- Watch which files they open — that is the actual measurement, not whether it compiles
- Option 1 is the sharpest: the window spans midnight, so the natural
isAfter(23:00) && isBefore(05:00)is always false. It needs||. A test at 02:00 catches it instantly — do they write one? - If they have to modify the service to register the rule, revisit Task 7 with them
Context: Operations wants to change the 8,000 km limit without a code change. Today every threshold is a constant in the source.
What to do:
- Move the thresholds into
application.yml - Bind them type-safely and inject them into the rules
- Keep the current values as defaults so nothing breaks
Acceptance Criteria:
- Changing a limit in
application.ymlchanges the behaviour with no recompile - Configuration is bound to a typed object, not read with raw
@Valuestrings scattered about - Existing tests still pass, and the rules can still be tested without Spring
- Invalid configuration is rejected at startup, not on the first request
Hints:
@ConfigurationPropertiesbound to a record, with@EnableConfigurationPropertiesor@ConfigurationPropertiesScan- Ask what "without a deploy" really means — this is still a restart. What would runtime changes cost?
- Ask whether the rules themselves should be configurable, and where they would stop
Context: The obvious direction test — comparing the two longitudes — is wrong for flights crossing
the antimeridian. Tokyo 139.65 → Los Angeles -118.24 has a smaller arrival longitude, but the
great-circle route heads east across the Pacific.
What to do:
- Implement a correct direction test
- Keep every existing test green
- Explain the trade-off between the simple version and the correct one
Acceptance Criteria:
- Tokyo → Los Angeles is not classified as westbound
- Barcelona → London still is
- Flights on the same meridian are handled deliberately
- Covered by tests that would fail against the naive implementation
Hints:
- Normalise the longitude difference into (−180, 180] — add or subtract 360 until it is in range — then test the sign
- Or compute the initial great-circle bearing and check whether it points west
- Ask what happens at the poles, where longitude is degenerate
- Ask whether they would have shipped the simple version with a comment, and when that is the right call
Context: A client team says they are string-matching on the feedback messages to decide what to show the user, and it keeps breaking when wording changes. Another team wants the messages in Spanish.
What to do:
- Give every violation a stable code alongside its human-readable message
- Discuss what this does to existing clients and how you would roll it out
- Sketch how the same change enables translation
Acceptance Criteria:
- Each violation carries a stable identifier that does not change when wording does
- The response change is versioned or additive — existing clients are considered explicitly
- Ordering of the violations is deterministic
- Translation is possible without touching the rules
Hints:
- A
record Violation(String code, String message)is the obvious shape — thenfeedbackbecomes a list of those MessageSourceplus message keys is how Spring does i18n; the rules should emit keys, not sentences- This is where non-deterministic rule ordering from Task 7 becomes a client-visible problem — do they connect the two?