cswork / libraryOpen practice workspace

Interview guides · EOS IT Solutions

EOS IT Solutions Software Engineer Interview Questions & Guide 2026

Concurrency drills for EOS IT Solutions software engineer prep: races, lock ordering, and what a check-then-act sequence assumes.

The 60-second brief

The Software Engineer role at EOS IT Solutions—often aligned with specialized functions such as Mobility and Optical Consulting or Network Implementation—is a pivotal position that bridges the gap between high-level architectural design and hands-on technical execution. You are not just writing code; you are building the infrastructure and software solutions that power global communication networks and complex enterprise mobility environments.

The behavioural round is a technical round in narrative form. Prepare it by collecting specifics you actually owned, such as a design you argued against, an incident you diagnosed, or a decision you later reversed, rather than by rehearsing phrasing.

Focus areas

  • Diagnose failures inside environments you cannot instrument
  • Scope every query by tenant below application code
  • Bound credential lifetime by the engagement end date

Overview

Your work directly impacts the stability, scalability, and performance of critical client systems. Whether you are optimizing optical transport layers or deploying large-scale network implementations, your output ensures that EOS IT Solutions remains at the forefront of the technology services industry. This role requires a blend of rigorous technical precision and a consultative mindset, as you will often be tasked with solving unique, high-stakes problems for a diverse range of enterprise clients.

The EOS IT Solutions interview process

EOS IT Solutions candidates report 3 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.

Initial Screening

The person on this call usually cannot evaluate your code and does not need to. They write a short paragraph, and that paragraph is what a hiring manager skims when deciding who to put on your loop. So the test is not whether your work was hard, it is whether a non-engineer can repeat it correctly. Name systems by what they did rather than by their internal codename, give each project a shape (what was breaking, what you changed, what happened after), and keep the whole walkthrough near ninety seconds. Depth that cannot survive a paraphrase reads as vagueness.

What to demonstrate

  • Whether a non-engineer can restate your projects without distorting them, since their paraphrase is what travels to the hiring manager, not your sentences
  • Whether each project has a shape rather than a stack list: the failure or constraint, the change you made, the result and how it was measured
  • Whether you can say what was yours inside a team project without either inflating it or disappearing into the plural

How to prepare

  • Rewrite each headline project as two sentences with no internal system names and no acronyms outside your company, then say them to someone outside engineering and have them repeat them back. Fix whatever came back wrong
  • Attach one measured number to each project: the baseline, the change, and the window it was measured over. Where nothing was ever measured, say that plainly rather than reaching for a plausible percentage
  • Time the background walkthrough against a clock. If it runs past two minutes, compress the earliest role to a single clause and spend the recovered time on the most recent one

Technical Evaluations

What this round decides is narrow: whether you can produce code that runs and is correct on inputs nobody showed you. An elegant solution that does not compile scores below a plain one that does, so write a correct brute force first, say out loud that you know its cost, and improve it with the working version still on screen. What separates strong answers is who finds the broken case. Trace your own code against an empty input, a single element, and duplicate keys before you say you are finished, because being told is far more expensive than noticing.

What to demonstrate

  • Whether degenerate inputs get checked without being asked for: an empty collection, one element, every element equal, and the extreme value the input type allows
  • Whether the complexity you state matches the code you actually wrote, including a sort or a copy sitting inside a loop
  • Whether the finished answer is verified against the worked examples before you call it done, rather than assumed correct because the code reads correctly

How to prepare

  • Take five problems you have already solved and, without running anything, write down what each returns for empty input, a single element, and all-duplicates. Then run them and count how many you predicted wrong.
  • Drill the brute force as its own skill: on ten problems, write only the obviously-correct slow version and time how long it takes to get it passing. If that is more than a few minutes, that is what to practise, not the optimal version.
  • Add a fixed last step before you submit anything, reading only the loop bounds and the initial value of each accumulator, which is where most off-by-one errors live

Final Assessments

Coding rounds mostly set a floor. They decide whether you clear the bar, not where you land on the ladder. Level tends to come out of the design discussion and the ownership stories, so the question worth auditing beforehand is whether the scope you describe matches the scope of the job. Work that stops at your own service, or a story whose hard part was writing the code rather than getting several people to agree on an interface, reads a level below where you think you are interviewing, and that gap is usually resolved downwards.

What to demonstrate

  • Whether the largest thing you describe owning ran end to end — the decision, the migration path, the rollout, and what you did when it went wrong — or stopped at the change you merged
  • Whether design answers include what you would not build, what you would defer, and what you would measure before committing, rather than only what the boxes are
  • Whether a disagreement in a story was settled with something checkable — a benchmark, a prototype, a written proposal — instead of by seniority or by waiting it out
  • Whether you can say which calls you made alone and which you escalated, and why the line sat where it did

How to prepare

  • Write your largest piece of owned work as a timeline of decisions — who decided what, when, and what you did when the plan broke — then delete every sentence whose subject is "we" and see how much survives
  • Take one system you know well and drill the migration answer: how old and new paths run side by side under live traffic, how you compare their outputs, what the rollback is once writes are going to both, and which step you would not automate
  • Map each line of the ladder in the job posting to a specific thing you have done, find the line you cannot support, and prepare the closest evidence you have plus an honest account of the gap

Where candidates lose points

Validating against a client sandbox and assuming production parity

Sandboxes typically carry smaller data, looser or absent rate limits, a schema version behind production, and sometimes synchronous behaviour where production is asynchronous. Code that passes there fails first in the client's production, during a change window you do not control and often cannot get a second one of. The mitigations are specific: assert the observed schema version at the boundary of every run, measure the production rate limit empirically rather than reading it from a document, and design the first production run to be a bounded, reversible slice rather than a full backfill.

Depending on a client field because it is always populated

It is populated by their operational process, not by a constraint in their database, and their process changes without telling you. When it stops being populated, a permissive pipeline writes nulls or defaults into derived data and the damage spreads for weeks before anyone notices. Validate at the boundary and fail the run with failure_class='schema_mismatch' plus a redacted sample, so the failure is loud, attributable to the client's change, and cheap to diagnose. A run that fails clearly is far cheaper than a backfill that has to reconstruct which rows were silently wrong.

Answering a debugging question with a guess instead of a bisection

Give a procedure that halves the search space at each step: confirm the symptom reproduces, establish the last known-good version, input or timestamp, then bisect over commits, over the data, or over the layers of the request path. A plausible cause with no way to confirm it is the same move whether it happens to be right or wrong, which is why it scores nothing.

Arguing past a hint

When the interviewer asks what happens for a particular input or floats a different data structure, stop and take it seriously; it is almost always a correction rather than idle curiosity. Talking over it converts a recoverable wrong turn into a data point about how you handle review.

Questions & practice

Measure credential exposure past engagement close without double counting

Medium · interval merge · sweep line · credential ttl

For one engagement you have credential_grant rows: (grant_id, principal_id, target_system_id, issued_at, not_after, revoked_at which may be NULL). The cutoff instant T is the engagement's ends_on plus access_grace_days. A grant is live over the half-open interval [issued_at, min(not_after, revoked_at)). There are up to 200,000 grants across target systems. For each target_system_id, return the total wall-clock time after T during which at least one grant was live, plus the disjoint segments that make it up. Grants that overlap must be counted once.

Approach

  1. Clip each grant to [max(issued_at, T), min(not_after, coalesce(revoked_at, +inf))) and discard any interval whose start is not strictly before its end. O(n), and it removes every grant that already expired inside the engagement window.
  2. Bucket the survivors by target_system_id and sort each bucket by start. O(n log n) overall, and sorting dominates the whole algorithm.
  3. Sweep each bucket once, holding one open segment [s,e): if the next start is greater than e, emit [s,e) and open a new segment, otherwise set e = max(e, next_end). O(n) after the sort, O(1) working state, O(number of emitted segments) output.
  4. Total exposure is the sum of emitted segment lengths, which is the measure of the union. Summing per-grant durations instead answers a different question and is unbounded above by the elapsed wall clock.
  5. Produce a second, conservative figure that ignores revoked_at and uses not_after alone. revoked_at records that you asked for revocation; whether the client's identity provider honoured it is not something this table knows, and a self-contained token validated offline stays valid to its own expiry regardless.
  6. If a bucket does not fit in memory, sort externally and sweep the stream, or push end timestamps into a min-heap keyed by end and pop those below the current start. Same O(n log n), memory proportional to the maximum number of concurrently live grants.

Worked solution (25 min)

  1. Set T = 2026-06-30T00:00Z for one target system with four grants: g1 not_after 2026-06-29T12:00Z; g2 not_after 2026-07-01T00:00Z, revoked_at NULL; g3 not_after 2026-07-01T06:00Z, revoked_at 2026-06-30T18:00Z; g4 issued 2026-07-01T06:00Z, not_after 2026-07-02T06:00Z.
  2. Clip: g1 becomes empty and drops out. g2 -> [Jun30 00:00, Jul1 00:00). g3 -> [Jun30 00:00, Jun30 18:00). g4 -> [Jul1 06:00, Jul2 06:00).
  3. Sort by start and sweep: g2 and g3 merge into [Jun30 00:00, Jul1 00:00); g4 starts after that end, so it is emitted separately.
  4. Union = 24h + 24h = 48h = 2,880 minutes, in two segments with a 6-hour uncovered gap between them. Summing the same three clipped intervals per grant instead gives 24 + 18 + 24 = 66h; the 18h excess is exactly the [Jun30 00:00, Jun30 18:00) stretch where g2 and g3 overlap.
  5. Recompute ignoring revoked_at: g3 now runs to Jul1 06:00, which bridges the gap, and the union becomes the single segment [Jun30 00:00, Jul2 06:00) = 54h.

Expected result: Exposure after T = 2,880 minutes in segments [Jun30 00:00, Jul1 00:00) and [Jul1 06:00, Jul2 06:00). The conservative figure that ignores revoked_at is 3,240 minutes in one segment. Naively summing the clipped per-grant durations (24 + 18 + 24) would have reported 66 hours, more than the 54 hours that elapsed between T and the last expiry.

Verify it yourself

  • Union (48h) <= the clipped per-grant sum (66h) always, with equality only when no two clipped intervals overlap; here the 18h difference is the single g2/g3 overlap.
  • Union (48h) <= last expiry minus T (54h), since the union cannot exceed the elapsed span it lives in.
  • The emitted segments are strictly increasing and pairwise disjoint, and their count is 2, less than the 3 surviving grants.
  • g1 contributes nothing, confirming the clip ran before the sort rather than after it.

Follow-up

  • Three grants overlap and one of them belongs to an offboarded contractor. What must the sweep emit so exposure can be attributed per principal?
  • Which of your two numbers is the real bound on access if the client system validates tokens offline, and what does that imply about issuing TTLs in the first place?
  • Run this across 20,000 engagements as a nightly job with a fixed memory budget. What changes in the shape of the computation?

Assign connector bindings to rollout waves and name the cycle

Medium · topological sort · cycle detection · dependency graph

Connector bindings declare ordering dependencies: an edge u -> v means v must not run in a wave earlier than one after u has succeeded. You have up to 50,000 bindings and 200,000 edges, and a misconfigured dependency may have created a cycle. Assign each binding the earliest wave number such that every predecessor sits in a strictly earlier wave, return the total wave count, and if a cycle exists return the bindings on one concrete cycle rather than a boolean. The implementation must be iterative: a dependency chain can be 50,000 deep.

Approach

  1. Build an adjacency list and an indegree array, then run Kahn's algorithm from every indegree-zero node. Bindings with no predecessors get wave 0. O(V+E) time, O(V+E) space.
  2. Relax waves as you decrement: when popping u, set wave[v] = max(wave[v], wave[u]+1) for each successor before enqueueing v at indegree zero. Because v is only enqueued after its last predecessor is popped, wave[v] is 1 + max over predecessors, which is the earliest legal wave. Total wave count is max(wave)+1, the longest path in nodes.
  3. If Kahn emits fewer than V nodes, the residual subgraph is exactly the nodes lying on or downstream of a cycle. Every residual node still has an in-edge inside the residual, otherwise it would have been popped, so walking backwards along in-edges cannot dead-end and must revisit a node within |residual| steps; the segment between the two visits is a concrete cycle. O(V) with a visited-position map.
  4. Use an explicit stack or a queue rather than recursion. Recursive DFS on a 50,000-node chain exceeds the default stack in most runtimes, and the failure looks like a crash rather than a cycle.
  5. At this size, recompute the whole plan whenever an edge changes. Incremental topological order maintenance costs far more code than the O(V+E) rebuild it saves.

Follow-up

  • Two bindings land in the same wave but hit the same client endpoint, whose rate budget is shared. How does the wave plan have to change, and what problem class does that turn into?
  • One dependency holds only for overlapping watermark ranges rather than globally. Is the dependency graph still a DAG over bindings, and what is the right vertex if not?
  • Report the critical path so an operator can see which chain sets the wave count, not just that it is five.

Collapse connector retry attempts into logical jobs and failure counts

Easy · hash aggregation · idempotency · retries

You are given one UTC day of connector_run rows: (connector_id, idempotency_key, attempt_no, status, failure_class, records_read, records_applied), unique on (connector_id, idempotency_key, attempt_no), up to 5,000,000 rows in arbitrary order. Retries of one logical job share the idempotency_key; the job's outcome is the status of its highest attempt_no. Return, per connector_id: the number of logical jobs, a count of terminally failed jobs by failure_class, and the dedupe hit total, meaning sum(records_read - records_applied) over terminal-succeeded attempts only. One pass over the input; state your memory bound.

Approach

  1. Key a hash map on (connector_id, idempotency_key) and keep only the highest attempt_no seen so far plus that row's status, failure_class and record counts. One pass, O(n) time, O(d) space where d is the number of distinct logical jobs, not O(n).
  2. Treat a repeated (key, attempt_no) as corrupt input and raise, since the table's uniqueness constraint says it cannot happen; silently overwriting hides a double-insert in the runtime.
  3. Fold the d surviving entries into per-connector counters in a second pass over the map, not over the rows. The failure_class histogram is a small fixed-width map per connector because failure_class is an enum.
  4. Compute dedupe hits only from terminal-succeeded attempts. A failed attempt's records_read is work attempted, not work deduplicated, and adding it counts the same source records once per retry.
  5. If d does not fit in memory, partition the input by hash(connector_id, idempotency_key) into p files and aggregate each partition independently. Same O(n) total work, p sequential passes, memory traded for I/O; the partition function must use the full key or a job's attempts split across files.

Follow-up

  • A job's highest attempt is 'cancelled' but an earlier attempt succeeded. What is the job's outcome, and what does that say about who writes the cancel?
  • Produce this incrementally as runs land instead of as a daily batch: what state do you keep per key, and what happens when an attempt arrives out of order?
  • records_applied is written by your runtime, not by the client. What would you reconcile it against before anyone trusts the dedupe-hit number?

Return the latest attempt per connector idempotency key

Easy · distinct-on · indexing · idempotency

connector_run stores one row per attempt: run_id, connector_id, idempotency_key, attempt_no SMALLINT, status (queued, running, succeeded, failed, ambiguous, cancelled, abandoned), failure_class, watermark_from, watermark_to, started_at, finished_at, unique on (connector_id, idempotency_key, attempt_no). For one connector_id, write the query returning the highest-numbered attempt per idempotency_key, keeping only those keys whose latest attempt is not succeeded, ordered by watermark_from. Give the index that serves it, and say what the plan does without that index.

Approach

  1. Use DISTINCT ON (idempotency_key) with ORDER BY idempotency_key, attempt_no DESC. PostgreSQL requires the ORDER BY to lead with exactly the DISTINCT ON expressions, and that requirement is what makes the row picked deterministic rather than whichever the scan happened to reach first.
  2. Apply the not-succeeded filter outside, in a wrapping SELECT. Putting it in the inner WHERE changes the question from 'keys whose latest attempt failed' to 'keys that have any non-succeeded attempt', which reports a key that failed twice and then succeeded, and is the single most common wrong answer here.
  3. Know the portable alternative: row_number() OVER (PARTITION BY idempotency_key ORDER BY attempt_no DESC) in a subquery, filtered = 1 outside. A window function cannot be filtered in WHERE because WHERE is evaluated before the window, and PostgreSQL has no QUALIFY clause, so the subquery is mandatory rather than stylistic.
  4. Index (connector_id, idempotency_key, attempt_no DESC). With connector_id fixed by equality, the index supplies the exact order DISTINCT ON needs and the Sort node disappears. It does not reduce rows read: through PostgreSQL 17 there is no loose index scan, so the executor still walks every attempt in range rather than skipping to each next distinct key.
  5. Give the complexity honestly: without the index the plan is a scan plus an O(n log n) sort over the connector's attempts; with it, O(n) ordered reads and constant working memory, where n is attempts in range, not distinct keys. If the distinct keys are a tiny fraction of attempts, a recursive skip-scan is the next step.

Worked solution (15 min)

  1. Create the table with a handful of rows across three idempotency keys, including one key with attempts 1 failed, 2 ambiguous, 3 succeeded.
  2. Write the inner DISTINCT ON query alone and confirm it returns one row per key.
  3. Wrap it and add WHERE status <> 'succeeded' plus ORDER BY watermark_from in the outer query.
  4. Create the index and compare EXPLAIN output before and after.

Expected result: SELECT * FROM (SELECT DISTINCT ON (idempotency_key) idempotency_key, attempt_no, status, failure_class, watermark_from, watermark_to FROM connector_run WHERE connector_id = $1 ORDER BY idempotency_key, attempt_no DESC) latest WHERE status <> 'succeeded' ORDER BY watermark_from;

Verify it yourself

  • The key whose attempts run failed, ambiguous, succeeded is absent from the result, proving the filter runs after the pick.
  • Move the filter into the inner WHERE and watch that key reappear as attempt 2 — the wrong answer, visible on a four-row fixture.
  • EXPLAIN shows an Index Scan with no Sort node once (connector_id, idempotency_key, attempt_no DESC) exists.

Follow-up

  • attempt_no is not monotonic in time because a retry was queued out of order. Does your query still pick the right row, and which column should it actually order by?
  • How would you return the latest attempt per key across all connectors in one environment without the query degrading to a full sort?
  • An ambiguous latest attempt means the target may or may not have been written. What does this query feed, and should ambiguous be treated as failed here?

Soft-delete environments without breaking uniqueness or audit history

Easy · soft deletes · partial indexes · audit history

environment holds one row per provisioned deployment: environment_id, client_id, engagement_id, env_name, region, status, destroyed_at TIMESTAMPTZ NULL. Decommissioned environments stay readable for seven years, and env_name must be unique per client among live environments only, because a client may reuse a retired name. Design the constraint set and the history table, write the query that lists a client's live environments, and say what your design does about connector_run rows whose foreign key points at a destroyed environment.

Approach

  1. Keep the tombstone as a nullable timestamp rather than a boolean: destroyed_at carries when as well as whether, and enforce live-only uniqueness with a partial index, CREATE UNIQUE INDEX env_live_name ON environment (client_id, env_name) WHERE destroyed_at IS NULL, which indexes only the live subset and so stays small.
  2. Get the NULL semantics right before you pick a spelling. In a B-tree unique index two NULLs compare distinct by default, so plain UNIQUE (client_id, env_name, destroyed_at) enforces nothing at all among live rows — every live row is unique on the third column purely by virtue of being NULL. PostgreSQL 15's UNIQUE NULLS NOT DISTINCT (client_id, env_name, destroyed_at) does satisfy the requirement: the NULL live rows now compare equal and collide with each other, while retired rows carry distinct destroyed_at timestamps and stay distinct, so a name can be retired and reused any number of times.
  3. Prefer the partial index anyway, and be able to say why rather than asserting it. It indexes only the live subset — tens of rows per client — instead of seven years of tombstones; it needs no version floor; and it keeps destroyed_at out of the key, so it cannot be defeated by two rows retired at the same instant. That last case is reachable: now() is transaction-start time, so retiring a name, recreating it and retiring it again inside one transaction writes two identical destroyed_at values and raises a duplicate-key error under the constraint form. clock_timestamp() avoids it if you take that route.
  4. Put history in a separate append-only table written by an AFTER INSERT OR UPDATE OR DELETE trigger holding environment_id, changed_at, changed_by, op and the before and after row images. PostgreSQL has no system-versioned tables, so this is a trigger or a logical-decoding consumer, not a DDL flag, and the soft delete arrives at that trigger as an UPDATE rather than a DELETE.
  5. State the foreign-key consequence rather than leaving it implicit: a soft delete leaves every referencing connector_run row resolvable, which is exactly what the audit requirement wants, but it means no read is protected by the database. Give the predicate one home, a view or an RLS-backed view that every caller uses, instead of repeating destroyed_at IS NULL across hundreds of call sites.
  6. Size the retention: seven years of history plus tombstoned rows in the hot table. Partition the history table by month so expiry is a DETACH PARTITION rather than a DELETE that has to be vacuumed, and decide separately whether a residency-driven erasure request can be honoured against an append-only store at all.

Follow-up

  • What stops the next endpoint from forgetting the destroyed_at IS NULL predicate, and does that mechanism survive a developer writing raw SQL?
  • The history table stores row images as JSONB and the base table gains a column. What do old rows now mean, and how do you read them six years later?
  • A residency erasure request requires a genuine hard delete for one client. What breaks, and what do you keep?

If a client reports intermittent connectivity issues across a global network, what is your systematic approach to identifying the root cause?

Medium

Approach

  1. Work from the requirement backwards to the design.
  2. Clarify what is being asked and what a complete answer contains.
  3. Say what you would check first and why it is the highest-information step.

Follow-up

  • How would you know your answer was wrong?
  • What assumption would you test first?

How do you approach the integration of new software components into an existing, legacy network environment?

Medium

Approach

  1. Say what you would check first and why it is the highest-information step.
  2. State your assumptions explicitly before working the problem.
  3. Clarify what is being asked and what a complete answer contains.

Follow-up

  • How would you know your answer was wrong?
  • What assumption would you test first?

Split a field in an API twelve live versions cannot upgrade

Hard · versioning · backward-compatibility · deprecation · expand-contract

Your public API returns a work-record representation carrying a single decimal hours field. You need to move to integer minutes, add an activity field older responses do not carry, and add two values to the status enum. A dozen release versions are live at once: shared-pool tenants upgrade weekly, dedicated deployments quarterly, and client-managed installs on their own schedule, currently two quarters behind. The callers are client-written integrations you cannot see or compel. Design the evolution: what you version, the sequence, the signal that removal is safe, and what breaks a client that does nothing.

Approach

  1. Separate the three changes, because their compatibility properties are different. Adding minutes beside hours is additive and safe for any reader that ignores unknown fields. Adding activity is additive. Adding enum values is not: a client that switches exhaustively on status and throws on the default branch breaks, so whether this is safe depends entirely on whether 'unknown values map to a documented fallback' was published before those clients were written. If it was not, the addition belongs behind a new major version, not in this one.
  2. Choose the versioning unit deliberately. A major version in the path is coarse and drags every endpoint along at once; a version header or per-representation media types are finer but multiply the test matrix by the number of live combinations. With a two-quarter tail, the cheapest correct answer is additive-only evolution inside the current major, with a new major held in reserve for changes that cannot be expressed additively — and removing hours is exactly one of those.
  3. Sequence it expand, dual-write, backfill, move reads, contract. Emit both fields for the whole window with hours derived from minutes, so there is one source of truth and the two cannot disagree. The mistake is storing both: some path then writes one and not the other, and a client reading the stale one produces a billing discrepancy nobody can attribute. On the write path accept either field, and reject a request carrying both with inconsistent values rather than quietly preferring one.
  4. Respect the precision problem that motivates the change, because it also constrains the transition. A value like 7.4 hours is not exactly representable in binary floating point, so hours round-trips inexactly and rounding to minute granularity can differ between your renderer and a client's parser. Keep integer minutes as the value of record, derive hours for display, and document the rounding rule so a client reconciling the two fields arrives at your number rather than its own.
  5. Gate removal on observed convergence rather than on a date. Record the release version and environment_id against every request and expose per-field usage counts, so you can name the environments still reading hours. Publish a Sunset header (RFC 8594) with its Deprecation companion and an end-of-life date, then be prepared to move that date: a client-managed install two quarters behind is a fact about the estate, not a communications failure.
  6. Write down what a client that does nothing experiences at each step, because that is the contract's real specification. Additive steps: nothing. The enum addition: a failure if it switches exhaustively, which is why the fallback rule must pre-date it. Removal: a hard break, which is why it happens only behind a major version or after the usage count for that field has been zero across every environment you can see.

Worked solution (40 min)

  1. Write the current and target representations side by side, labelling each delta additive, behavioural or breaking with one line of reasoning.
  2. Write the transitional representation showing hours derived from minutes, with the rounding rule stated explicitly.
  3. Write the write path's rule for a request carrying both fields, including the inconsistent case and which field wins if you accept both.
  4. Design the usage telemetry: what you record per request to attribute a field read to an environment_id and release, and what you will still not be able to see.
  5. Draft the deprecation dates and headers, then write the single observation that authorises removing hours.
  6. For each step, write one sentence describing what a client that does nothing experiences.

Expected result: Additive introduction of minutes and activity inside the current major, hours derived from minutes with a documented rounding rule, a write path accepting either and rejecting inconsistent duplicates, enum additions withheld unless an unknown-value fallback was already published, per-environment field-usage telemetry, Sunset and Deprecation headers with a movable date, and removal of hours only behind a major version or against observed zero usage.

Verify it yourself

  • A client compiled against the old representation and left untouched keeps working through every step before removal.
  • hours and minutes cannot disagree, because only one of them is stored.
  • You can name, today, the environments that would break if hours were removed tonight.

Follow-up

  • Telemetry says one client-managed environment still reads hours and its contact has not replied in eight weeks. What do you do, and what do you refuse to do?
  • A client insists on pinning to a version for three years. What do you actually commit to supporting, and how does that appear in the contract?

Connector workers are OOM-killed every day and a half

Medium · memory leak · caching · heap analysis

Connector runtime workers are OOM-killed after 18 to 40 hours and a restart clears it. RSS grows monotonically at about 1.9 MB per completed run, flat regardless of how many records that run moved, and workers serving more distinct bindings do not grow faster than workers serving one. Live heap measured immediately after a forced full collection rises with the same slope as RSS. Traffic is unchanged week over week. Give the ordered diagnosis, the specific object you expect at the root, and the fix.

Approach

  1. Prove retention before hunting for it. RSS and the post-collection live set rising with the same slope means the objects are reachable, which is a leak in the ordinary sense. RSS rising while the live set stays flat would be allocator fragmentation or native buffers, and that is a different investigation with different tools.
  2. Find the unit the growth is keyed on by division rather than by guessing. 1.9 MB per completed run, flat across record counts and flat across distinct bindings, points at something retained exactly once per run. Per-record growth would implicate an accumulating buffer; per-binding growth would implicate a legitimate cache that is merely unbounded.
  3. Diff two heap snapshots an hour apart by retained size and walk the dominator tree to a single root. Expect a module-level dictionary whose entry count equals the number of runs completed since process start, which is the observation that confirms step two.
  4. Read the key and the value separately, because they are two different bugs. The key contains something run-scoped, an idempotency key or a correlation id, which is why the hit rate is zero and the map only ever grows. The value is megabytes rather than kilobytes because the compiled schema validator closes over the first parsed page, so every entry pins a client payload as well.
  5. Fix both halves. Re-key on (connector_id, source_schema_version), whose cardinality is in the hundreds and stable. Cap the map with a fixed-size LRU so an unforeseen key can never make it unbounded again. Stop the closure capturing the page, so an entry costs a compiled schema rather than a schema plus a payload.
  6. Make the class of bug visible next time: export entry count and hit rate for every process-level cache. A zero hit rate is the unmistakable signature of a run-scoped key and appears within minutes, whereas this one took 18 hours to present itself as an OOM kill.

Follow-up

  • Those retained pages are client payloads sitting in a long-lived shared process. What does that mean beyond memory?
  • A worker killed mid-run leaves connector_run rows in 'running'. Who reconciles them, and how do you avoid applying that range twice?
  • Same symptom, but the post-collection live set is flat and only RSS climbs. What are you looking at instead, and which tool do you reach for?

Suggested practice checklist

Roughly ninety minutes on weeknights with one longer weekend block. The plan cuts scope rather than compressing everything, on the assumption that one thing finished per night beats four half-started.

Day 1 — Fix the scope and take a cold baseline

  • Read the role description and write the three things the loop will almost certainly test, then write an explicit not-doing list and keep it visible all week.
  • Take one twenty-five-minute coding problem and one fifteen-minute design prompt cold, and write the single sentence naming what blocked each, because those two sentences decide where the remaining evenings go.
  • Set the week's rule: one thing finished every night, including the night you only have forty minutes.

Deliverable: A one-page scope with a not-doing list and two cold attempts, each carrying one sentence on what blocked it.

Day 2 — One pattern, written three times from blank

  • Choose the single pattern most likely to appear in your loop and write it three times from an empty file rather than editing the previous attempt.
  • On the third pass, write the invariant as a comment before the loop body and the complexity before the first line of code.
  • Stop at ninety minutes even if the third version is imperfect, and write the one thing you would fix given another hour.

Deliverable: Three independent implementations of the same pattern plus a note on what changed between them.

Day 3 — One design, only to the depth you can defend

  • Take one system shape and go only as far as requirements, interface and data model, refusing to draw a box you could not survive a follow-up about.
  • Attach one number to each non-functional requirement, deriving it rather than asserting it, and write the assumption the number rests on.
  • Write the one tradeoff you are choosing against and the observation that would make you reverse it.

Deliverable: One design at interface-and-schema depth with derived numbers and one written reversible tradeoff.

Day 4 — Only the fundamentals you will have to defend

  • Write, in under two hundred words each, the answers to the two questions that follow almost any implementation: why this structure and not the obvious alternative, and what happens to this code at a hundred times the input.
  • Write what an index actually costs: faster lookups on the indexed columns against a write that now maintains a second structure, plus the cases where the planner declines to use it anyway, low selectivity, or a predicate wrapping the column in a function.
  • Delete any answer you cannot deliver aloud in under a minute, since an answer that needs reading is not an answer you have.

Deliverable: Three written answers, each under two hundred words and each timed aloud.

Day 5 — Your own work, timed

  • Write a ninety-second and a four-minute version of your main project and time both aloud rather than reading them.
  • Prepare the two follow-ups that always come: what you would do differently, and how you knew it worked.
  • Put one number in the first sentence and be ready to say exactly where it came from and what it excludes.

Deliverable: Two timed narratives with one defensible number in the opening line.

Day 6 — The one full rehearsal, in the weekend block

  • Run a sixty-minute mock covering a coding round and a design round in one sitting with no break, because sustained attention is the thing evenings have not trained.
  • Immediately afterwards, and before hearing any feedback, write the three moments you lost the thread.
  • Spend the rest of the block only on those three moments, and on nothing you merely feel shaky about.

Deliverable: Mock notes naming three failure moments with a specific fix written under each.

Day 7 — Taper

  • Write the twenty-minute warm-up you will actually do on the morning: one problem you can already solve from a blank file, one design you can narrate, and nothing you have never seen.
  • Re-read only your own notes from this week and open no new material.
  • Write the logistics down: the editor or shared document you will be working in, whether execution and lookups are permitted, and the sentence you will use when you do not know something.

Deliverable: A one-page card holding the design structure, the project numbers, and the logistics.

'We rewrote it in a better framework' is not a story

A migration is a cost you chose to pay, not an achievement. The story is what the old system made expensive, what you measured before committing, what kept serving traffic during the cutover, and what you would have done if the numbers had come back flat. Without those, a rewrite reads as taste.

Describe your experience with network automation tools and how you have used them to reduce manual configuration.

Medium · behavioural and engineering judgement

Approach

  1. State the situation in two sentences and spend the rest on the reasoning.
  2. Name the disagreement and how you resolved it with evidence.
  3. Pick a story where you made the decision, not one where you watched it.

Follow-up

  • How did you know your change caused the improvement?
  • What did you decide not to do, and why?

How do you handle disagreements with team members regarding technical strategy or implementation paths?

Medium · behavioural and engineering judgement

Approach

  1. Pick a story where you made the decision, not one where you watched it.
  2. State the situation in two sentences and spend the rest on the reasoning.
  3. Close with what you would do differently, concretely.

Follow-up

  • What would you do differently if you ran that again?
  • What did you decide not to do, and why?

Walk me through a time you identified a critical bottleneck in a system; what steps did you take to resolve it?

Medium · behavioural and engineering judgement

Approach

  1. State the situation in two sentences and spend the rest on the reasoning.
  2. Give the blast radius: what could have broken, and what you measured.
  3. Name the disagreement and how you resolved it with evidence.

Follow-up

  • What would you do differently if you ran that again?
  • What did you decide not to do, and why?

Tell me about a time you had to manage competing priorities under a tight project deadline.

Medium · behavioural and engineering judgement

Approach

  1. Pick a story where you made the decision, not one where you watched it.
  2. Give the blast radius: what could have broken, and what you measured.
  3. Name the disagreement and how you resolved it with evidence.

Follow-up

  • What did you decide not to do, and why?
  • What would you do differently if you ran that again?