Interview guides · BNSF Railway
BNSF Railway Software Engineer Interview Guide
BNSF Railway Software Engineer interview guide: Spring Boot, LLD, system design and safety behavioral questions, plus a 7-day plan.
The 60-second brief
BNSF Railway is a freight railroad network in North America. Candidates report that its software engineers build the systems behind train dispatching, asset tracking, inventory processing and supply chain logistics. The reported backend stack is Java and Spring Boot with asynchronous messaging, while front-end and full-stack engineers build live dashboards in Angular. Reported work includes event ingestion pipelines for asset health metrics, REST APIs for shipment routing and database transaction tuning. In this role you write and test services that handle telemetry and operational updates from physical equipment, so correctness and fault tolerance are part of the day job.
This guide is for candidates interviewing for a Software Engineer role at BNSF Railway, on either a backend (Java, Spring Boot) or a front-end and full-stack (Angular) track. It covers the three reported stages, which are the online assessment, the technical interviews and the behavioral interviews. It also covers the five groups of reported questions: Java and Spring Boot mechanics, low-level object-oriented design, system design for telemetry and APIs, coding and algorithms, and behavioral and safety questions. The requirements candidates describe include Spring MVC, Spring Data JPA and Spring Security, SQL on Oracle, PostgreSQL or SQL Server, and JUnit, Mockito or pytest. Angular, Kafka, RabbitMQ, ActiveMQ, Docker, Kubernetes, AWS and Azure are described as extras.
Candidates report three stages over roughly 3-5 weeks: an online assessment, one or more technical interviews, and behavioral interviews. A recruiter call and a loop of several rounds, by video or onsite in Fort Worth, TX, are also reported.
Focus areas
- SQL (coding/queries)
- System Design
- Machine Learning / ML System Design
Overview
This guide lists 23 reported questions in five groups: five on Java and Spring Boot internals, five on low-level design, five on system design, four on coding and four on behavioral and safety topics. The spread tells you where to spend time. Framework recall and runnable object-oriented code get as much weight as architecture, and pure algorithm practice is a smaller share than on many other loops. Candidates describe the process as built around practical engineering rather than puzzles alone.
The Java and Spring Boot group rewards precise recall. Be able to explain REQUIRED against REQUIRES_NEW, which exceptions roll a transaction back by default, and why a call from one method to another in the same bean skips the transaction proxy. Know how an @Aspect with @Around advice wraps controller methods, how a REST controller maps paths, validates payloads and hands errors to a global handler, why a mutable object makes a dangerous HashMap key, and when a stream pipeline holds data in memory. Candidates say the online assessment is typed into a plain editor with limited reference material, so practise this from an empty file with autocomplete switched off.
The low-level design questions ask for working code, not only a diagram. The in-memory key/value store with begin, commit and rollback, the visit-count service built on thread-safe structures, and the unit tests that mock an external dependency all end in something you can run. The train and asset-health models are about entities, relationships and REST endpoints. Reports mention a main method or a test suite as the proof that the design works, so plan to finish each design with executable checks and explicit handling of nulls, missing payload keys and database timeouts.
The system design group centres on telemetry from thousands of moving trains, queues such as Kafka or RabbitMQ, caches in front of a relational store, rate limiting, and a migration from a monolithic dispatching tool. Behavioral rounds are reported to give safety real weight, so every story should show a safeguard: a test, a staged release, a rollback path or a check before acting. Rehearse the outage and the design-disagreement stories with the STAR structure, and prepare a concrete answer on how safety shapes your daily engineering and deployment habits.
The BNSF Railway interview process
Candidates describe three stages, shown below in the order they report them. The technical interviews are reported to cover live coding, class design and architecture, and the loop after the recruiter call is described as three to five rounds.
Online Assessment
Candidates describe a timed online assessment, reported as delivered through Codility, that mixes algorithms and data structures with Java. Other reports say the tasks look like framework work: building a Spring Boot controller, configuring database transactions or writing an Angular component. Code is reported to go straight into the platform editor with few reference materials. Prepare it as a closed-book build test and practise producing working framework code from an empty file.
What to demonstrate
- Producing compiling, working Java code inside a plain platform editor
- Recall of Spring Boot mechanics such as controllers, validation and @Transactional settings without documentation
- Core algorithm and data-structure work, which the round description lists next to Java
- For front-end and full-stack tracks, writing a basic Angular component
How to prepare
- Write a CRUD controller from an empty file: path parameters, request validation, a filter through query parameters and a global exception handler that returns one consistent error body.
- Rehearse an inventory-update service with @Transactional: REQUIRED joins the caller's transaction, REQUIRES_NEW suspends it and commits on its own, and unchecked exceptions roll back by default while checked ones do not.
- Solve two-pointer and sliding-window problems in a plain text editor, with no autocomplete, and run them against edge cases like an empty array and a window larger than the input.
- Test your setup before you open the invitation. Candidates say invitations can mention a 7-day window while recruiters encourage finishing within 3 days.
Technical Interviews
Candidates describe one or more interviews built on scenario-based questions. The loop is reported to cover live coding, low-level class design, high-level architecture and framework depth in Java and Spring Boot, and to run by video or onsite at Fort Worth, TX. Candidates also report being allowed to share their screen and use a local IDE such as IntelliJ IDEA, PyCharm or VS Code. Work through the reported Java, design and coding questions aloud, and finish designs with code or tests that run.
What to demonstrate
- Working code with explicit error handling for nulls, missing payload keys and database timeouts
- Object-oriented design that turns an operational domain such as trains, engines and maintenance status into clear classes, plus unit tests
- Architecture for high-volume telemetry using queues, caches and relational storage
- Depth in Java and Spring Boot, including transactions, AOP, equals and hashCode, and streams
How to prepare
- Build the in-memory key/value store with get, set, count, begin, commit and rollback, including nested transactions, and add a main method or JUnit tests that prove each case.
- Write a visit-count service two ways, with ConcurrentHashMap and merge, and with an atomic counter or LongAdder per key, and explain why a plain HashMap with get-then-put loses updates.
- Write JUnit and Mockito tests, or pytest with a mock, for an ingestion service whose queue client and database repository are replaced by test doubles.
- Talk through a pipeline that ingests health metrics from 5,000 trains and alerts dispatchers: partition key, consumer groups, duplicate and out-of-order events, a cache in front of the database, and the alert path.
Behavioral Interviews
Candidates describe interviews on past experience, teamwork and alignment with company values, and report that safety is discussed explicitly. The reported prompts cover a production outage, a design disagreement, daily safety and risk practices, and owning an ambiguous requirement. Candidates also report STAR as the way to keep answers concise, with a clear statement of your own contribution. Prepare four first-person stories and a short answer on safety habits.
What to demonstrate
- Past experience told through a clear structure that shows your own contribution
- Working through disagreement with colleagues or stakeholders over a design
- Handling production incidents and operational stress methodically
- How you connect testing, reliability and risk management to safe delivery
How to prepare
- Write STAR outlines for the outage story and the design-disagreement story. Each needs a decision you made, how you checked it, and a result you can measure.
- Prepare a safety answer built from real habits: tests before release, staged rollouts, a rollback plan, defensive checks on input and review of risky changes.
- Choose a story where you took charge of a vague requirement: the questions you asked, the assumptions you wrote down, and how you got it delivered.
- Add one story where you pushed back on a deadline because quality or data security was at risk, and what you offered in its place.
Where candidates lose points
Saying @Transactional "rolls back on exception" and stopping there
State the details: unchecked exceptions roll back by default and checked ones do not unless configured, REQUIRES_NEW commits independently of the outer transaction, and a call within the same bean bypasses the proxy so no transaction starts. Rehearse them on an inventory update example.
Handing in a class diagram for the key/value store or the train model with no code that runs
Reports describe executable solutions with a main method or tests. Write the store with get, set, count, begin, commit and rollback, then test rollback with no open transaction, nested transactions and count after a rollback.
Calling the visit-count service thread-safe because it uses a concurrent collection, while the update is a separate read and write
Make the increment atomic with ConcurrentHashMap.merge or compute, or an atomic counter or LongAdder per key. Say which race the choice removes and how you would test it with many threads.
Drawing a Kafka box for the train telemetry design and moving on without partitioning, duplicates, ordering or the alert path
Name the partition key (train or asset id), what a consumer does with a duplicate or late event, where the cache sits, how the database handles writes against reporting reads, and what happens when a consumer falls behind.
Telling outage or disagreement stories in "we" language with no safeguard or result
Say what you decided, how you checked it and what changed afterward. Include one safety element in each story, such as a rollback path, a staged release or a test you added, because candidates report safety is discussed explicitly.
Questions & practice
What is the difference between .equals and == in Java, and how does object immutability affect key hashing
Medium · Java & Spring Boot Development
What is the difference between .equals and == in Java, and how does object immutability affect key hashing in collection types?
Approach
- == compares primitive values, and for objects compares references (same instance); equals() is logical equality, and Object's default equals() is itself an identity check, so a class only gets value equality by overriding it.
- Point out the traps: string literals are interned, so == can appear to work for strings until one is built at runtime; Integer.valueOf caches -128 to 127, so == between boxed Integers works for small values and fails above; BigDecimal.equals compares scale (1.0 and 1.00 are unequal) while compareTo returns 0.
- State the contract: if a.equals(b) then a.hashCode() == b.hashCode(), so override both together from the same fields. Overriding only equals lets a HashSet hold two equal elements, because their different hash codes mean equals is usually never consulted.
- Explain why mutability breaks hashing: HashMap picks the bucket from hashCode at insertion; if a field used in hashCode changes afterwards, get() and containsKey() search the wrong bucket and miss an entry that still occupies memory. Immutable keys (String, records with immutable components, final fields) avoid this and can cache their hash, as String does.
- Mention sorted collections: TreeMap and TreeSet use compareTo or a Comparator instead of equals, so a comparator inconsistent with equals makes the set treat distinct objects as duplicates.
Follow-up
- What happens to a HashMap entry if you mutate its key after put()?
- Is a Java record a safe map key if one of its components is a List?
- How would you write equals() for a class whose subclass adds a field?
How do Java Streams filter and map operations execute under the hood, and how would you optimize stream pipeli
Medium · Java & Spring Boot Development
How do Java Streams filter and map operations execute under the hood, and how would you optimize stream pipelines for memory consumption?
Approach
- Streams are lazy: filter and map only add stages to a pipeline, and nothing runs until a terminal operation (collect, forEach, reduce, findFirst). The terminal operation pulls each element through all the fused stages in turn, so filter followed by map is a single pass with no intermediate collection.
- Separate stateless from stateful operations: filter, map and flatMap handle one element at a time, while sorted() buffers the whole stream and distinct() keeps a set of everything seen, so memory grows there. Short-circuiting operations (findFirst, anyMatch, limit) stop pulling from the source early.
- Memory optimisations: filter before map so fewer objects are created, use IntStream, LongStream or mapToDouble to avoid boxing, read files with Files.lines inside try-with-resources instead of readAllLines, and aggregate with downstream collectors such as groupingBy(key, counting()) rather than grouping into lists first.
- Be precise about parallel streams: they run on the common ForkJoinPool, pay off only for large CPU-bound work on easily split sources such as arrays or ArrayList, do not reduce memory, and order-dependent operations like limit and forEachOrdered can make them slower.
- Note that a stream can be consumed once (reuse throws IllegalStateException), and measure with JMH or a profiler before replacing a stream with a plain loop on a hot path.
Follow-up
- In what order do the side effects print for a stream with peek, filter and findFirst?
- Why can sorted() on a very large stream exhaust the heap, and what would you do instead?
- When is a parallel stream slower than a sequential one?
Build a user activity service in Java or Python that tracks page visits and increments visit counts concurrent
Medium · Low-Level & Object-Oriented Design (LLD)
Build a user activity service in Java or Python that tracks page visits and increments visit counts concurrently with thread-safe data structures.
Approach
- Explain the bug first: map.put(page, map.get(page) + 1) on a HashMap is a read-modify-write race, so concurrent increments are lost, and concurrent writes to a plain HashMap can also corrupt its internal structure.
- In Java use ConcurrentHashMap<String, LongAdder> with counts.computeIfAbsent(page, k -> new LongAdder()).increment(): computeIfAbsent creates the adder atomically, and LongAdder spreads contention across cells, so it scales better than AtomicLong under heavy writes; merge(page, 1L, Long::sum) is a simpler atomic alternative.
- For per-user counts, key on a composite (userId, page) record or nest maps; note that LongAdder.sum() is not an atomic snapshot while increments continue, which is acceptable for visit counts.
- In Python, d[k] += 1 is a separate load, add and store, so it is not atomic even with the GIL; guard a dict or collections.Counter with a threading.Lock, or shard locks by key hash to reduce contention.
- Prove it with a test: an ExecutorService runs N threads that each record M visits, a CountDownLatch releases them together, and the assertion is that the total equals N times M; repeat the run, since a race can pass once by luck.
Follow-up
- The service now runs on several instances. Where do the counts live, and how do you increment them atomically?
- How would you return the ten most visited pages without blocking writers?
- How would you bound memory if the number of distinct pages is unbounded?
Write executable unit tests using pytest or JUnit to mock external dependencies for a backend data ingestion s
Medium · Low-Level & Object-Oriented Design (LLD)
Write executable unit tests using pytest or JUnit to mock external dependencies for a backend data ingestion service.
Approach
- Make the service testable first: inject its dependencies (source client, parser, repository, publisher, Clock) through the constructor so tests can swap in mocks without patching statics or touching a network.
- JUnit 5 with Mockito: @ExtendWith(MockitoExtension.class), @Mock for each dependency and @InjectMocks for the service; stub with when(client.fetch()).thenReturn(batch) or thenThrow(...) for a timeout (Mockito rejects a checked exception the method does not declare), and inspect what was saved with verify(repository).saveAll(captor.capture()) and an ArgumentCaptor.
- pytest: patch the name where the service looks it up, for example patch("ingest.service.fetch_batch") when the service did from client import fetch_batch; use MagicMock with side_effect for a sequence of results or a raised exception, fixtures for shared setup and pytest.mark.parametrize for malformed-record variants.
- Cover the cases that matter for ingestion: happy path, empty batch, a malformed record that is skipped and counted while the rest are saved, a downstream timeout that is retried and then sent to a dead-letter path, and a duplicate record that is not saved twice.
- Keep mocks honest: assert outcomes and key interactions rather than every call, inject a fake clock or retry policy instead of sleeping, and add one integration test against real infrastructure (for example Testcontainers), since mocks cannot catch a wrong SQL statement or serializer.
Follow-up
- How do you test the retry logic without the test waiting through the backoff?
- When would you use a spy instead of a mock?
- What does a mock-heavy suite miss, and how do you cover it?
Given an array of event logs, write an efficient algorithm using a two-pointer technique to find the shortest
Medium · Coding & Algorithms
Given an array of event logs, write an efficient algorithm using a two-pointer technique to find the shortest sub-array containing a specific sequence of status codes.
Approach
- Clarify what containing the sequence means before coding: all target codes in any order (with multiplicity), or the codes in the given order as a subsequence; the two need different algorithms. Also agree what to return when no window exists.
- Any order: the minimum-window technique. Keep a map of required counts and a count of satisfied codes; advance the right pointer to add events, and while every code is satisfied, record the window and advance the left pointer to shrink it. Each pointer moves at most n times, so time is O(n + m) and space is O(k) for k distinct target codes.
- In order: scan forward with one pointer over the logs and one over the pattern until the pattern is fully matched, then scan backward from that end to find the latest start that still matches, record the window and restart just after that start. A DP over (log index, pattern index) solves the same problem in O(n·m) time and O(m) space.
- Edge cases: an empty pattern, a code that never appears, repeated codes in the pattern (counts matter), several minimal windows of equal length (say which you return, usually the earliest) and a log shorter than the pattern.
Follow-up
- The logs arrive as a stream that does not fit in memory. Which version still works, and what state do you keep?
- How would you return every minimal window rather than the first?
- What changes if the codes must be adjacent with nothing between them?
Parse a raw server execution log to extract error frequencies, count unique word patterns, and identify system
Medium · Coding & Algorithms
Parse a raw server execution log to extract error frequencies, count unique word patterns, and identify system performance anomalies.
Approach
- Stream the file line by line (BufferedReader, or iterating the file object in Python) instead of loading it whole, and parse each line with one precompiled regex with named groups for timestamp, level, component, latency and message; count malformed lines instead of crashing, and attach multi-line stack traces to the entry that started them.
- Error frequencies: a hash map keyed by error code or exception class, incremented for each ERROR line; for the top k use a size-k min-heap in O(n log k), or sort the distinct keys once if there are few.
- Word patterns: normalise each message into a template by replacing variable parts (numbers, ids, UUIDs, IP addresses, paths) with fixed tokens, then count templates in a hash map, so timeouts that differ only in milliseconds count as one pattern; for plain word counts, lowercase and split on non-word characters.
- Anomalies: bucket events by minute and flag buckets where error count or p99 latency exceeds the trailing mean plus three standard deviations, or use a rolling median and median absolute deviation, which a single spike distorts less.
- Complexity: parsing is linear in input size, and memory grows with the number of distinct keys and templates rather than lines; handle time zones and clock skew when logs come from several servers.
Follow-up
- The logs total 50 GB across 200 servers. How does the design change?
- Traffic doubles every morning. How do you stop the detector flagging that as an anomaly?
- How would you test the template normaliser?
Implement a sliding window algorithm to calculate rolling average delays across train movement time-series dat
Medium · Coding & Algorithms
Implement a sliding window algorithm to calculate rolling average delays across train movement time-series data.
Approach
- Clarify the window: the last k readings (count-based) or the last W units of time (time-based), per train or across all trains, and what to emit before the window fills.
- Count-based: keep a running sum, add the entering value, subtract values[i - k] as it leaves, and emit sum / k; this is O(n) time and O(1) extra space instead of the O(n·k) cost of re-summing every window.
- Time-based: keep a deque of (timestamp, delay) plus a running sum and count; append each reading, pop from the front while its timestamp falls outside the window, and emit sum / count. Each reading enters and leaves the deque once, so the total is O(n) amortised.
- For many trains, keep a map from train id to its deque and sums; sort each train's readings by timestamp first if order is not guaranteed (O(n log n)), and skip or count readings with missing delays instead of treating them as zero.
- Watch the numerics: use floating-point division (integer division truncates in Java), and on long streams store delays in integer units such as seconds or recompute the sum periodically to avoid floating-point drift from repeated add and subtract.
Follow-up
- How would you compute a rolling median instead of a mean?
- Readings arrive a few minutes out of order. How do you keep the averages correct?
- How much memory does this need for every train at once, and how would you cap it?
Walk through your approach to optimizing an $O(N^2)$ brute-force array search down to $O(N)$ runtime complexit
Medium · Coding & Algorithms
Walk through your approach to optimizing an $O(N^2)$ brute-force array search down to $O(N)$ runtime complexity.
Approach
- Anchor it in a concrete example such as finding a pair that sums to a target: brute force checks all n(n-1)/2 pairs, O(n^2); one pass with a hash map from value to index checks whether target minus x has been seen before inserting x, giving O(n) expected time and O(n) space.
- Name the general levers: a hash set or map replaces an inner lookup loop; prefix sums with a map of prefix counts find subarrays summing to k in O(n); two pointers work on sorted input; a sliding window handles contiguous ranges when the condition is monotonic; a monotonic stack answers next-greater-element in O(n).
- State the trade-offs: hash lookups are O(1) expected, not worst case, and cost O(n) memory; with tight memory, sort and use two pointers for O(n log n) time and O(1) extra space if in-place sorting is allowed, carrying original indices along if they are needed.
- Check the edge cases that break the optimisation: duplicates and reusing the same element in pair problems, negative numbers (they break the sliding-window shrink condition, so use prefix sums instead) and empty or single-element arrays.
- Justify the bound aloud (each element is inserted and looked up once, or each pointer only moves forward), then test the optimised version against the brute force on random inputs.
Follow-up
- Why does a sliding window fail for subarray sum equals k when values can be negative?
- How would you solve it with O(1) extra space?
- What changes if you must return every matching pair rather than one?
Describe a project where you demonstrated leadership by taking ownership of an ambiguous technical requirement
Medium · Behavioral & Safety Culture
Describe a project where you demonstrated leadership by taking ownership of an ambiguous technical requirement and guiding it to delivery.
Approach
- Pick a project where the requirement was genuinely unclear and you closed the gap yourself; open with the situation in two or three sentences and what made it ambiguous (conflicting stakeholders, unknown data, no agreed success criteria).
- Show how you reduced the ambiguity: listed the open questions, interviewed the users, wrote a short design document stating your assumptions and asked stakeholders to confirm or correct it, and built a small prototype to test the riskiest assumption.
- Show the leadership: how you split the work, who you brought in, the scope trade-off you made (what you left out of the first release and why) and how you kept stakeholders informed when the plan changed.
- Close with a measurable result and a lesson: what shipped, the metric or user outcome that moved, and what you would clarify earlier next time; narrate your own actions in the first person singular.
- The mistake that sinks it: a story that is really about executing a clear specification, or one where the ambiguity resolved itself; the story has to contain decisions you made while the requirement was still uncertain.
Follow-up
- Which assumption turned out to be wrong, and how did you find out?
- How did you handle a stakeholder who disagreed with your reading of the requirement?
- What would you have done if the deadline could not move?
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
- 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.
- 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.
- 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.
- 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?
Denormalise tenant onto revisions and backfill it live
Hard · denormalisation · online migration · backfill
resource_revision (revision_id, resource_id, version, actor_user_id, change_kind, patch, request_id, created_at) has 400M rows and no tenant column; tenant_id lives only on resource. Two reads need it: a tenant-scoped audit feed ordered by created_at DESC, and an offboarding purge. Both join back to resource today. Justify adding tenant_id to resource_revision against those two reads, name the anomaly the copy introduces and the constraint that prevents it, then give the ordered migration for a live table taking 1.2k writes/second — the lock each step takes, how the backfill is batched, and where each step stops being reversible. PostgreSQL 16.
Approach
- Justify from the access path rather than from taste. Without the column, the audit feed either scans resource_revision by created_at and discards other tenants' rows, or resolves the tenant's resource_ids first and probes with them — both proportional to the tenant's whole history rather than to one page. With (tenant_id, created_at DESC, revision_id DESC) it is a seek that stops at 50 rows, and the purge becomes a ranged delete instead of a join.
- Name the cost exactly: a second copy of a fact can disagree with the first. Make the disagreement unwritable rather than documented — add UNIQUE (resource_id, tenant_id) on resource so it can serve as a foreign-key target, then FOREIGN KEY (resource_id, tenant_id) REFERENCES resource (resource_id, tenant_id) on the revision table. A revision can then only ever carry its parent's tenant.
- Step one, expand: ALTER TABLE resource_revision ADD COLUMN tenant_id BIGINT NULL, with no default, so it is a catalogue change and no rewrite. It still needs ACCESS EXCLUSIVE for an instant, and that instant queues behind the longest open transaction on the table while every later query queues behind it — set lock_timeout to 2s and retry rather than wait.
- Step two, dual-write: deploy the writer that populates tenant_id on every new revision while reads still use the join. Reversible by redeploying the previous build, because nothing reads the column yet.
Follow-up
- The backfill is half finished and a rollback is required. What state is the table in, and what does the previous build do with a half-populated column?
- How do you verify the backfill actually finished, given rows are still being inserted while it runs?
Explain the propagation options available in Spring Boot's @Transactional annotation and how exception rollb
Medium · Java & Spring Boot Development
Explain the propagation options available in Spring Boot's @Transactional annotation and how exception rollbacks are handled across nested method calls.
Approach
- List the seven Propagation values and what each does with an existing transaction: REQUIRED (default; join it or start one), REQUIRES_NEW (suspend it, start an independent physical transaction), NESTED (savepoint inside it), SUPPORTS (join if present), NOT_SUPPORTED (suspend and run without), MANDATORY (throw if none) and NEVER (throw if one exists).
- State the default rollback rule: an unchecked exception (RuntimeException or Error) marks the transaction for rollback, a checked exception does not unless rollbackFor names it, and noRollbackFor lets a listed exception still commit.
- Walk the nested case: with REQUIRED, inner and outer share one physical transaction, so an inner runtime exception marks it rollback-only, and if the outer method catches it and carries on, its commit throws UnexpectedRollbackException. REQUIRES_NEW commits or rolls back on its own (an audit row written in it survives an outer rollback); NESTED rolls back only to its savepoint.
- Name the proxy pitfalls: the annotation is applied by a Spring proxy, so a call from one method to another in the same class (self-invocation) bypasses it; REQUIRES_NEW holds a second pooled connection while the outer one is suspended, which can exhaust a small pool.
- Say how you would prove the behaviour: an integration test that forces the inner method to throw and asserts which rows exist afterwards; note that NESTED needs a transaction manager with savepoint support, such as DataSourceTransactionManager.
Follow-up
- Your outer method catches the inner method's exception and the commit still fails. Why, and how would you fix it?
- Why does @Transactional on a method called from the same class have no effect, and what are your options?
- When would you pick REQUIRES_NEW over NESTED, and what does each cost in connections and locks?
How do you implement custom aspect-oriented programming (AOP) advice in Spring Boot to handle logging or perfo
Medium · Java & Spring Boot Development
How do you implement custom aspect-oriented programming (AOP) advice in Spring Boot to handle logging or performance tracing across controller endpoints?
Approach
- Add spring-boot-starter-aop so Boot enables auto-proxying, then write a class annotated @Aspect and @Component so it is registered as a bean.
- Define the pointcut narrowly: within(@org.springframework.web.bind.annotation.RestController *) for every controller, an execution(...) expression over the web package, or @annotation(com.example.Timed) for opt-in methods marked with a custom annotation.
- Use @Around with a ProceedingJoinPoint: record System.nanoTime(), call proceed(), and log the method signature and elapsed time in a finally block so failed calls are timed too; rethrow exceptions instead of swallowing them and return proceed()'s result unchanged.
- Know the limits of proxy-based AOP: only calls that go through the Spring proxy are advised, so self-invocation is missed; final methods cannot be advised through CGLIB proxies; when several aspects apply, set their order with @Order.
- Compare alternatives and keep logs safe: a HandlerInterceptor or servlet Filter sees the HTTP status and time spent before the controller; put a correlation id in the SLF4J MDC, never log credentials or personal data from request bodies, and consider Micrometer timers when the goal is metrics rather than log lines.
Follow-up
- Why is a call from one controller method to another in the same class not intercepted?
- When would you choose a servlet Filter or HandlerInterceptor over an aspect for request timing?
- How would you carry the correlation id into work that runs asynchronously on another thread?
Demonstrate how to build a RESTful CRUD API endpoint in Spring Boot with custom request/response mappings, fil
Medium · Java & Spring Boot Development
Demonstrate how to build a RESTful CRUD API endpoint in Spring Boot with custom request/response mappings, filtering, and exception handling.
Approach
- Lay out the layers: a @RestController with @RequestMapping("/api/assets") that delegates to a service holding the @Transactional logic and a Spring Data JPA repository; keep request and response DTOs (Java records work well) separate from the JPA entity so persistence fields do not leak into the API contract.
- Map verbs to status codes: GET /{id} with @PathVariable returns 200 or 404; POST with @Valid @RequestBody returns 201 Created and a Location header via ResponseEntity.created(uri); PUT or PATCH returns 200; DELETE returns 204 No Content.
- Filtering and paging: optional @RequestParam values (status, from, to) plus a Pageable argument, translated into a Specification or derived query; whitelist sortable fields and cap the page size so a caller cannot request an unbounded result set.
- Centralise errors in a @RestControllerAdvice: map a not-found exception to 404, MethodArgumentNotValidException to 400 with per-field messages, unique-key or optimistic-lock conflicts to 409, and return ProblemDetail bodies (Spring 6) so every error has one shape.
- Prove it runs: @WebMvcTest with MockMvc to check status codes, validation and JSON shape with the service mocked (@MockBean, or @MockitoBean on newer Spring versions), plus one repository test against a real database.
Follow-up
- How would you make the POST safe for a client to retry after a timeout?
- PUT or PATCH for a partial update, and how do you stop a stale client overwriting a newer change?
- How would you version this API once a dashboard depends on it?
Design an in-memory key/value store class that supports basic get, set, and count operations along with
Medium · Low-Level & Object-Oriented Design (LLD)
Design an in-memory key/value store class that supports basic get, set, and count operations along with transactional begin, commit, and rollback functionality.
Approach
- Hold two hash maps: data (key to value) and valueCounts (value to number of keys holding it), so get, set, delete and count are all O(1); every write updates both, decrementing the old value's count and removing it at zero.
- Implement transactions as a stack of undo logs: begin pushes an empty map, and a write inside a transaction records the key's previous value (or an absent marker) only the first time that key is touched in the current block.
- rollback pops the top log and restores each recorded key through the normal set and delete path so valueCounts stay correct, returning NO TRANSACTION when the stack is empty; it costs O(k) for k keys changed in that block. commit either clears the whole stack (the usual spec) or merges the top log into its parent, keeping the parent's older prior value when both have one.
- Contrast the alternative: copying the whole map on begin makes rollback trivial but costs O(n) time and memory per begin, which is why the undo log wins when transactions touch few keys.
- Ship it with a main driver or JUnit tests: nested begin and rollback, count after rollback, delete inside a transaction, setting a key to its current value, and rollback or commit with no open transaction; state whether the class is single-threaded or guarded by a lock.
Follow-up
- How would you change commit so it closes only the innermost transaction?
- Two threads use the store at once. What isolation do they get, and how would you add it?
- How would you make committed data survive a process restart?
Model an asset health snapshot tracking system using solid object-oriented principles, defining clear entity r
Medium · Low-Level & Object-Oriented Design (LLD)
Model an asset health snapshot tracking system using solid object-oriented principles, defining clear entity relationships, class structures, and REST endpoints.
Approach
- Model the entities: Asset (id, type, attributes), HealthSnapshot (asset id, capturedAt, receivedAt, a list of readings, derived HealthStatus) and SensorReading as an immutable value object (metric, value, unit). One asset has many snapshots, and snapshots are append-only.
- Keep the classes SOLID: a HealthRule interface (evaluate a snapshot, return a status and a reason) with one class per rule, so new thresholds are added without editing the evaluator (Open/Closed); a SnapshotRepository interface with an in-memory implementation for tests and a database one for production (Dependency Inversion).
- Define the REST endpoints: POST /assets/{assetId}/snapshots returns 201; GET /assets/{assetId}/snapshots?from=&to=&limit= returns a page ordered by capturedAt; GET /assets/{assetId}/snapshots/latest; GET /assets?status=CRITICAL for a dashboard list.
- Handle the data edge cases: snapshots arriving late or out of order (compute latest by capturedAt, not arrival), duplicate submissions (a client-supplied snapshot id as an idempotency key), unknown asset ids (404) and readings with missing or mismatched units (400 naming the field).
- Make the latest-status read cheap: keep a latest-snapshot pointer per asset updated on insert instead of scanning history for every dashboard request, and index snapshots on (asset_id, captured_at).
Follow-up
- Snapshot volume grows to millions per day. How do storage and the history query change?
- How do you add a new asset type with its own health rules without editing existing classes?
- Two snapshots for the same asset carry the same capturedAt. Which one wins?
Design a train tracking and scheduling module using OOD principles, demonstrating how train routes, engine uni
Medium · Low-Level & Object-Oriented Design (LLD)
Design a train tracking and scheduling module using OOD principles, demonstrating how train routes, engine units, and maintenance status interact.
Approach
- Name the classes and relationships: a Train composed of EngineUnit objects and assigned to a Route, which is an ordered list of segments between Stations; a Schedule of ScheduledStop entries (station, planned arrival, planned departure); EngineUnit carries a MaintenanceStatus enum (IN_SERVICE, DUE, OUT_OF_SERVICE).
- Put behaviour with its data: Train.canDepart() checks every engine unit is IN_SERVICE, and a TrainState (SCHEDULED, EN_ROUTE, HELD, ARRIVED) enforces allowed transitions in one place so a train cannot jump from SCHEDULED to ARRIVED.
- Give the scheduler a concrete conflict check: two trains conflict on a single-track segment when their occupancy intervals overlap, start1 < end2 and start2 < end1; keep each segment's reservations in a TreeMap keyed by start so a new reservation checks only its floor and ceiling neighbours in O(log n).
- Wire maintenance changes with an observer: when an EngineUnit moves to OUT_OF_SERVICE, a listener notifies the scheduler, which flags or holds the affected train; also reject assigning one engine unit to two trains whose schedules overlap.
- Prefer composition to inheritance, inject a Clock so delay and ETA logic is testable, and test the boundaries: one train leaving a segment exactly when the next enters, and maintenance status changing mid-route.
Follow-up
- A train is delayed. How does the delay propagate to its later segment reservations?
- How would the model change for double track, where trains can pass each other?
- Where would you add persistence without coupling the domain classes to the database?
How would you design an event ingestion system at BNSF Railway to process telemetry data sent from thousands o
Medium · System Design & High-Level Architecture
How would you design an event ingestion system at BNSF Railway to process telemetry data sent from thousands of moving trains in near real-time?
Approach
- Size it with stated assumptions: for example 5,000 trains each sending a 1 KB reading per second is about 5 MB per second and roughly 432 million messages a day; ask about connectivity too, since moving trains lose signal and upload bursts when they reconnect.
- Ingest path: devices buffer locally and send batches with a per-train sequence number to an authenticated ingestion endpoint, which validates and writes to a Kafka topic partitioned by train id, keeping each train's events ordered and letting consumers scale by partition.
- Processing: stream consumers deduplicate on (train id, sequence number), order by event time rather than arrival time, allow for late events with a watermark or grace period, and evaluate alert rules; alerts go to their own topic so a slow history writer never delays them.
- Store by access pattern: latest state per train in a key-value store such as Redis for dashboards, history in a time-partitioned store (a time-series database or Postgres partitioned by day) for queries, and raw events in object storage for replay.
- Reliability: at-least-once delivery with idempotent consumers, consumer-lag monitoring, a dead-letter topic for unparseable payloads, and enough topic retention to replay after a consumer bug.
Follow-up
- A train is offline for two hours and then uploads everything at once. What happens to ordering and alerts?
- One partition is far busier than the rest. How do you find and fix it?
- How do you avoid alerting a dispatcher twice for the same reading?
Architect a multi-layered distributed data store with caching, asynchronous message queues (such as Kafka or R
Medium · System Design & High-Level Architecture
Architect a multi-layered distributed data store with caching, asynchronous message queues (such as Kafka or RabbitMQ), and relational persistence.
Approach
- Layer by responsibility: a relational database as the source of truth for writes, a cache (Redis or Memcached) for hot reads, a message broker carrying change events, and read models or a reporting store built from those events.
- Avoid the dual-write problem: writing the row and publishing the event separately can diverge after a crash, so write an outbox row in the same database transaction and have a relay (polling, or change data capture such as Debezium) publish it to the broker.
- Reads use cache-aside: check the cache, load from the database on a miss, set with a TTL; on a write, delete the key after the transaction commits instead of updating it, and prevent stampedes with per-key request coalescing and jittered TTLs.
- Choose the broker for the job: Kafka keeps an ordered, replayable log per partition with consumer groups, suited to event streams and rebuilding read models; RabbitMQ routes messages into queues and removes them once acknowledged, suited to task distribution with per-message acks.
- State the consistency model: caches and read models are eventually consistent; for read-your-writes, read from the primary just after a user's write; send historical reporting to a replica or separate store so heavy queries do not compete with operational writes.
Follow-up
- Redis goes down. What happens to latency and to database load?
- A consumer processes the same event twice. How does the read model stay correct?
- How do you change the event schema without breaking older consumers?
Design an API routing service that manages high-volume operational updates, ensuring rate limiting, fault tole
Medium · System Design & High-Level Architecture
Design an API routing service that manages high-volume operational updates, ensuring rate limiting, fault tolerance, and low-latency response times.
Approach
- Clarify traffic and callers: requests per second, payload size, the latency target, and which updates are safety-relevant and must never be dropped versus bulk updates that can be throttled.
- Gateway layer: a load balancer in front of stateless gateway instances that match path and version against a route table (loaded from configuration, reloadable without restart), authenticate the caller and forward over pooled keep-alive connections.
- Rate limiting: a token bucket per client key (capacity sets the burst, refill rate sets the sustained rate) kept in Redis and updated atomically with a Lua script so every instance shares the limit; reject with 429 and a Retry-After header, and decide in advance whether to fail open or closed when Redis is unreachable.
- Fault tolerance: a timeout on every upstream call, retries with exponential backoff and jitter only for idempotent requests or ones carrying an idempotency key, a circuit breaker per upstream (closed, open, half-open) and bulkheads so one slow service cannot take every worker thread.
- Latency and visibility: keep extra network calls off the hot path by caching route config and auth keys locally, and track p50 and p99 latency, 429 counts and 5xx rates per route.
Follow-up
- Should a safety-critical update ever receive a 429? How would you prioritise it?
- How do you enforce a global limit across many gateway instances without a Redis call on every request?
- An upstream starts timing out. Walk through what the circuit breaker does.
How would you structure a backend system to decouple long-running asset health calculations from front-end API
Medium · System Design & High-Level Architecture
How would you structure a backend system to decouple long-running asset health calculations from front-end API polling?
Approach
- Use the asynchronous request-reply pattern: POST /assets/{id}/health-calculations returns 202 Accepted with a Location header for /jobs/{jobId}, and the API stores a job record (PENDING, RUNNING, SUCCEEDED, FAILED) before enqueuing the work.
- Workers consume from a queue, compute, store the result and update the job status; acknowledge the message only after the result is stored, retry with a cap and send repeated failures to a dead-letter queue; a lease or heartbeat timestamp lets a sweeper requeue jobs whose worker died.
- Make polling cheap: GET /jobs/{jobId} reads one indexed row or a cached copy, returns a Retry-After hint and serves the stored result without recomputing it; deduplicate submissions so the same asset and parameters return the existing job.
- Offer push where it helps: server-sent events or WebSockets notify the dashboard when a job finishes (an Angular client can wrap the stream in an RxJS Observable), with polling kept as the fallback.
- If the calculation is periodic rather than on demand, precompute it on a schedule or on new telemetry and store the latest result per asset, so the front end only reads a table.
Follow-up
- A worker dies halfway through a long job. How is it finished exactly once from the user's point of view?
- Hundreds of dashboards poll the same asset. How do you keep that load off the database?
- How would a user cancel a running calculation?
Explain how you would migrate a legacy monolithic dispatching tool to a microservices architecture while maint
Medium · System Design & High-Level Architecture
Explain how you would migrate a legacy monolithic dispatching tool to a microservices architecture while maintaining zero downtime.
Approach
- Use the strangler fig pattern: put a routing layer in front of the monolith, extract one bounded context at a time behind it and shift traffic route by route, so the monolith keeps serving everything not yet moved and each step is reversible.
- Pick the first slice by risk and seams: a read-heavy capability with few dependencies, such as a status lookup, before anything that writes dispatch decisions; map module and table dependencies first to find clean seams.
- Move data without downtime: replicate the relevant tables to the new service's store with change data capture, backfill history, compare continuously, switch reads, then switch writes, then stop replication; use expand-and-contract schema changes so old and new code run side by side.
- Prove equivalence before cutover: run the new service in shadow mode on mirrored traffic and diff its outputs against the monolith, then release behind feature flags or a canary with automatic rollback on error rate or output mismatch.
- Plan for distributed failure: replace cross-module database transactions with sagas or the outbox pattern, add contract tests between the monolith and new services, and keep a rollback path for every stage, including after writes have moved.
Follow-up
- Two modules you are separating share one database transaction today. What replaces it?
- After writes move to the new service, how do you roll back if it misbehaves?
- How do you know the migration is complete and the old code can be deleted?
How do you integrate safety principles and risk mitigation into your day-to-day software engineering and deplo
Medium · Behavioral & Safety Culture
How do you integrate safety principles and risk mitigation into your day-to-day software engineering and deployment practices?
Approach
- Answer with practices you actually use, anchored in one real example from your own work: a risk assessment before a change (what can break, who is affected, how to undo it), tests and code review as merge gates, and a staged release with a rollback plan.
- Defensive coding at boundaries: validate every external input, set explicit timeouts on network and database calls, handle null and missing fields deliberately, and fail safe, so missing or stale sensor data displays as unknown instead of healthy.
- Deployment safeguards: CI that blocks merges on failing unit tests, integration tests or static analysis; feature flags to separate deploy from release; canary or blue-green rollout with automatic rollback on error-rate or latency thresholds; expand-and-contract migrations so rolling back never needs a schema reversal.
- Operational follow-through: alerts on symptoms users feel, runbooks for known failure modes, and blameless post-incident reviews whose action items are tracked to completion.
- Show judgement rather than a checklist: describe a time a safeguard caught a defect, or a release you held back because a check failed, and what that cost and what it prevented.
Follow-up
- Tell me about a time you were pressed to skip a test or review. What did you do?
- How do you decide how much testing a small change needs?
- What would make you roll back a release that is mostly working?
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
- 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.
- 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.
- 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.
- 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 one reported stage or question group, starting with Spring Boot recall in a plain editor and ending with the behavioral stories.
Day 1 — Map the loop and set up a no-autocomplete environment
- Write out the reported sequence (Online Assessment, Technical Interviews, Behavioral Interviews) and mark which question groups sit under each: Spring Boot and coding, class design and architecture, behavioral.
- Set up a plain-text or minimal editor with autocomplete disabled, matching the conditions candidates report for the assessment.
- From memory, write a one-page sheet of what @Transactional, @Aspect, @RestController and @Autowired do, then answer the transaction propagation question aloud and check the sheet against the Spring reference.
Deliverable: A one-page loop map, a working no-autocomplete setup, and a written annotation sheet with propagation and rollback rules checked against the Spring reference.
Day 2 — Spring Boot from an empty file
- Build a CRUD controller with path parameters, bean validation, a filter by query parameter and a global exception handler, then call it with curl.
- Write an @Aspect with @Around advice that logs method name and elapsed time for every controller method, and note why a self-invoked method skips the aspect.
- Add a transactional inventory update that throws a runtime exception and test that the change rolls back, then change the propagation to REQUIRES_NEW on an inner call and observe the difference.
Deliverable: A runnable Spring Boot project with a controller, a timing aspect and two passing tests, one for rollback and one for the REQUIRES_NEW case.
Day 3 — Java internals and SQL
- Write a small program that uses a mutable object as a HashMap key, mutates it and loses the entry, then fix it with an immutable key and a consistent equals and hashCode.
- Write a stream pipeline with filter and map, add peek calls to show element-by-element lazy execution, and explain where a collect or sorted step buffers data.
Deliverable: A Java file showing the lost-key bug and its fix, an annotated stream pipeline, and one solved SQL practice question.
Day 4 — Executable low-level design
- Build the in-memory key/value store with get, set, count, begin, commit and rollback, supporting nested transactions, and write tests for rollback with no transaction, nested commits and count after rollback.
- Write the page-visit service with thread-safe counters and a test that runs many threads against one key.
- Write unit tests for an ingestion service with its queue client and repository mocked, using JUnit with Mockito or pytest.
Deliverable: A key/value store with a passing test suite, a concurrent visit counter with a multi-thread test, and a mocked-dependency test class for an ingestion service.
Day 5 — Domain models and log-window coding
- Model the train tracking and scheduling module in code, with trains, engine units, routes and maintenance status, and write a main class that exercises it.
- Model asset health snapshots with entity classes and the REST endpoints that create and read them.
- Solve the shortest-sub-array two-pointer problem and the rolling-average sliding-window problem, then take the log-parsing problem to error counts and word-pattern counts with hash maps.
- Reduce a quadratic array search to linear time using a hash set or hash map, and state the space cost.
Deliverable: Runnable train and asset-health models with a driver class, plus working solutions to the two-pointer, sliding-window, log-parsing and quadratic-to-linear problems with edge-case tests.
Day 6 — Telemetry and API architecture
- Whiteboard the train telemetry ingestion design for thousands of trains, covering the partition key, consumer groups, duplicate and out-of-order events, a cache, relational storage and the dispatcher alert path.
- Design the API routing service with rate limiting and fault tolerance, then the layered data store with caching, a queue and relational persistence.
- Design the decoupling of long-running asset health calculations from front-end polling, then write a migration plan for moving a monolithic dispatching tool to services without downtime.
Deliverable: Four labelled architecture diagrams with written failure modes (ingestion, API routing, layered store, async calculation), a staged migration plan, and a solved memory-growth debugging exercise.
Day 7 — Behavioral stories and a full mock
- Write STAR outlines for the production outage, the design disagreement, the ambiguous-requirement ownership story and the deadline pushback story.
- Write a safety and risk answer from real habits, covering tests, staged release, rollback and review, and say it aloud.
Deliverable: Four STAR outlines, a rehearsed safety answer, and a recorded or written mock run with a list of gaps to fix.
Safety, outages and ownership stories
Candidates report behavioral interviews that explore past experience, teamwork and alignment with company values, and say safety is discussed explicitly. For a Software Engineer working on dispatching and telemetry software, the reported prompts cover production outages, design disagreements, daily safety and risk habits, and ownership of unclear requirements. Each story should show a decision you made, how you checked it and a safeguard you used or added.
- How do you integrate safety principles and risk mitigation into your day-to-day software engineering and deployment practices?
- Describe a project where you demonstrated leadership by taking ownership of an ambiguous technical requirement and guiding it to delivery.
- Tell the story of a time you pushed back on a delivery date because software quality or data security was at risk, and what you offered instead.
Tell me about a time you experienced significant operational stress or a critical outage in a production syste
Medium · Behavioral & Safety Culture
Tell me about a time you experienced significant operational stress or a critical outage in a production system. How did you resolve it?
Approach
- Choose an incident where you owned a concrete part of the response, such as the diagnosis, the rollback decision or the stakeholder updates. A story where you only watched or relayed messages shows nothing about your judgment.
- Give two sentences of context (system, symptom, who was affected), then walk the sequence: how it was detected, how you sized the impact, what you did first to stop the damage (rollback, feature flag, failover, shedding traffic) before hunting the root cause, and how you confirmed recovery.
- Cover communication explicitly: who you told, how often, and what you said while the cause was still unknown. In a loop where safety comes up, say how you avoided a rushed change that would widen the problem, for example by testing the fix first or applying it to a small slice.
- Close with the follow-through: root cause, the written review and the permanent change, such as an alert, a regression test, a runbook step or a circuit breaker. Give one measurable result, like time to recover or a failure class that can no longer happen.
- Spend one or two sentences on the stress itself: you kept a timeline, split the investigation among people and tested one hypothesis at a time. Do not blame a teammate and do not credit luck for the fix.
Follow-up
- Why did you choose to roll back instead of fixing forward, or the reverse?
- What did you change in the system or the process so this class of outage cannot recur?
- How did you keep non-engineering stakeholders informed while you were still diagnosing?
Describe a situation where you had to work through a disagreement with a team member or stakeholder regarding
Medium · Behavioral & Safety Culture
Describe a situation where you had to work through a disagreement with a team member or stakeholder regarding a software design decision.
Approach
- Pick a genuine technical disagreement over a design choice, such as synchronous calls against a message queue, a shared database against a split service, or whether to add a cache. State both positions fairly, including the best argument for the other side.
- Say what was at stake in terms the other person cared about, such as delivery date, operational risk or maintenance cost, rather than only your preference.
- Show the move from opinion to evidence: a small prototype, a load test, a failure scenario, a written comparison of trade-offs, or a conversation with the owner of the affected system. Name the data point or the person who settled it.
- Give the outcome and the working relationship afterward. If your option lost, show that you committed to the chosen design and helped deliver it. If it won, show what you took from the other side. Winning with no change in your own thinking is the weak version of this story.
- Keep the story in first person singular, with your argument, your action and your result, and avoid presenting the other person as obstinate.
Follow-up
- What evidence would have changed your mind?
- What would you have done if the decision-maker had overruled both of you?
- How did you work with that person on the next project?
Reverse your own decision and price the reversal
Medium · reversibility · measurement · migrations
Describe a technical decision you made and later reversed. Pick one that cost something: a service you split and merged back, a cache you added and removed, an index you created that pushed the planner onto a worse plan, a projection you rebuilt from scratch. State what you believed when you decided, the measurement that changed your mind, how long the wrong version ran in production, and what the reversal cost in migrations, dual writes, and a deprecation window for callers you did not own.
Approach
- State the original rationale without irony, in the version you would still defend given what was known then. If it is not defensible, the story is about carelessness rather than judgement, and a different example serves you better.
- Give the measurement that moved with a before and after: the p99 that did not improve, the cache hit rate that sat at 40%, the plan that flipped to a sequential scan once the table passed a size you can name.
- Cost the reversal in steps, not adjectives: expand-and-contract deploys, the dual-write window, the callers who had to be notified, the rows already written in the wrong shape that had to be backfilled or abandoned.
- Distinguish reversal from rewrite by naming what you kept. Most good reversals preserve the schema or the interface and undo one decision inside it, which is also why they were affordable.
Follow-up
- What in that decision was irreversible, and did you know it was irreversible when you made it?
- How did you tell the people who had already built on top of the original decision?