Interview guides · Kran Consulting Private
Kran Consulting Private Software Engineer Interview Questions & Guide 2026
Kran Consulting Private software engineer prep: algorithm drills where every complexity claim has to name the assumption under it.
The 60-second brief
The Software Engineer role at Kran Consulting Private serves as the technical backbone for the organization’s project delivery and operational efficiency. As an engineer here, you are tasked with building, maintaining, and optimizing software solutions that directly impact the company's ability to serve its clients effectively. Your work bridges the gap between complex technical requirements and tangible business outcomes, requiring a blend of disciplined coding practices and a clear understanding of project-specific needs.
Most rounds test how you handle an under-specified problem more than what you recall. A wrong first approach you correct out loud is recoverable; a constraint you assumed silently and were never given is the expensive mistake.
Focus areas
- Bound credential lifetime by the engagement end date
- Diagnose failures inside environments you cannot instrument
- Make integration retries idempotent without target-side idempotency keys
Overview
This position is particularly critical because it demands both core language proficiency and an ability to navigate database operations and framework-level challenges. Whether you are working on backend services, web interfaces, or database management, you will be expected to contribute to high-quality codebases that ensure system stability and performance. For those who enjoy solving foundational technical problems and seeing the direct impact of their code on organizational operations, this role offers a challenging and rewarding environment.
The Kran Consulting Private interview process
Kran Consulting Private candidates report 4 rounds · ≈ 3-5 weeks. The stages below are what candidates describe, not a published process.
Technical Screening
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
Coding Assessments
Most of the time lost in this format is not lost to thinking. It goes to a standard-library call you half-remember, an off-by-one in a loop bound, and a debugging loop that mutates code at random until something passes. When output is wrong, stop re-reading the whole function: take the smallest input that reproduces it and walk the state through by hand, printing intermediates if the environment allows. Guessing at a fix without a failing case you understand is how a five-minute bug becomes twenty, and the clock does not pause while you do it.
What to demonstrate
- Whether you reach the right structure without a detour, and can write it from memory rather than only recall that one exists
- Whether overflow is considered where the language has fixed-width integers, since a signed 32-bit value stops at 2,147,483,647 and then wraps in Java, is undefined behaviour in C++, and does not arise in Python, whose integers grow instead
- Whether recursion depth is treated as a constraint on large inputs, given that CPython's default limit is 1000 frames and a deep recursion can exhaust the stack in any language where an iterative version would not
- Whether a failing case is isolated and explained before any edit is made to the code
How to prepare
- From an empty file and with no references open, implement the pieces you lean on most: a heap push and pop, an iterative DFS with an explicit stack, and a binary search whose midpoint is written lo + (hi - lo) / 2, which avoids the overflow that (lo + hi) / 2 can hit in a fixed-width integer type
- Time yourself on the ten library calls you look up most, such as sorting with a custom comparator, splitting and joining strings, and finding the next key at or above a value in an ordered map, until the lookup is gone
- Take a solution you know is broken and, before touching it, write one sentence naming the input, the expected value and the actual value. Repeat until you do it without deciding to.
Technical Interviews
The same problem is scored by two different mechanisms depending on the format, and preparing for one does not cover the other. With a person watching, partial progress is visible and a hint is a correction you can absorb; silence is the expensive failure, because nobody can read a half-written function. With an automated grader there is no partial credit for what you were about to do, nobody to ask, and the worked examples in the prompt are the entire specification. Read them as a contract, down to whether an empty result should be an empty list or no output at all.
What to demonstrate
- In a live session, whether your commentary tracks what your hands are doing, and whether a hint redirects you or gets defended against
- In an automated one, whether you cover the cases the examples do not show, since the hidden cases are where the score moves
- Whether you manage the clock on purpose: abandoning an approach that is not converging while there is still time to write something simpler that finishes
How to prepare
- Have someone hand you a problem and feed you one deliberately wrong hint. Practise testing it against a concrete case instead of accepting or rejecting it on authority.
- Do one timed run a week in a plain browser editor with autocomplete, linting and your own snippets switched off, which is closer to what these environments give you
- For the automated format, write the harness before the solution: a main that feeds the worked examples plus an empty and a single-element case and prints expected against actual, so a wrong submission is caught by you first
HR Discussion
When a round has no standard shape, it is often there because something is still open: an area no earlier conversation reached, a round where the signal came out mixed, or a decision someone is not ready to make alone. Work out which by going back over what each earlier round actually covered rather than how it felt, and arrive able to give evidence on that point without being asked twice. Weak answers replay the loop's earlier material at the same depth. Strong ones go a level deeper and stay consistent with what you already said.
What to demonstrate
- Whether your account of a project matches the one you gave earlier in the loop, since what you said before may be available to whoever runs this round
- Whether you can go a level deeper on something already covered, reaching the decision and its alternatives rather than repeating the summary
- Whether you state your own uncertainty accurately, including parts of a system you did not build and decisions you inherited, instead of claiming even ownership across all of it
- Whether you can answer a question you handled poorly earlier by naming what you missed, rather than delivering a polished second version as if the first had not happened
How to prepare
- Reconstruct the loop on one page: for each round, the questions you were asked and the answer you actually gave, not the better one you thought of afterwards. The gaps on that page are your best available guess at why this round exists.
- Take the two claims you made earlier that carry the most weight and assemble the backing for each: the measurement, the date, what broke, the decision you would make differently now.
- Write down the three facts about your work that must not drift between tellings, such as team size, timeline and your own role, and check your stories against that list rather than trusting recall under pressure
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.
Tenant context leaking across a connection pool
Setting the tenant on a connection and relying on it for the rest of the request looks correct in every test that runs one request at a time. Under transaction pooling the connection goes back to the pool with that session state still attached, and the next checkout, serving a different client, inherits it; row-level security then enforces the previous tenant's policy flawlessly and returns the wrong rows. It reproduces only under concurrency, which is the load profile your integration tests do not have. Use a transaction-scoped setting, and assert that the setting matches the request's tenant immediately before the first query rather than trusting that it was set correctly upstream.
Sorting when the problem never required a total order
Match the algorithm to the guarantee actually needed: the top k comes from a size-k heap in O(n log k) time and O(k) space, distinctness needs a set rather than an ordering, and a small bounded integer key range admits a linear counting pass. A full O(n log n) sort is the right default only when you genuinely need everything in order.
Comparing floating-point values for equality, or holding money in them
Binary floating point cannot represent 0.1 exactly, so repeated addition drifts and an equality check fails on values that are mathematically equal. Store currency as integer minor units or a decimal type, and compare floats against a tolerance you chose for a stated reason.
Questions & practice
Implement a Fibonacci series using an array.
Medium · data structures and algorithms
Approach
- State the target complexity and say which constraint rules the naive version out.
- Choose the data structure from the access pattern, not from familiarity.
- Name the brute-force solution and its complexity before improving on it.
Follow-up
- What is the worst case, and how likely is it on real data?
- Which test case would catch an off-by-one here?
Write a program to identify prime numbers.
Medium · data structures and algorithms
Approach
- Choose the data structure from the access pattern, not from familiarity.
- State the target complexity and say which constraint rules the naive version out.
- Name the brute-force solution and its complexity before improving on it.
Follow-up
- What is the worst case, and how likely is it on real data?
- How does this change if the input no longer fits in memory?
Create a pattern printing program (e.g., stars).
Medium · data structures and algorithms
Approach
- Restate the input: its shape, its size, and what is guaranteed about it.
- Walk one small example through your approach before writing the whole thing.
- Choose the data structure from the access pattern, not from familiarity.
Follow-up
- Which test case would catch an off-by-one here?
- How does this change if the input no longer fits in memory?
Compare release versions and report the live estate spread
Easy · parsing · version skew · ordering
Each environment row carries reported_release as TEXT: either NULL, or a version of the form MAJOR.MINOR.PATCH with an optional -rc.N suffix. Up to 400 environments. Ordering rule: compare major, then minor, then patch numerically; a -rc.N build precedes the same MAJOR.MINOR.PATCH with no suffix, and rc numbers compare numerically. Return the lowest and highest reported versions, the minor-version spread between them, and the count of environments whose reported_release is NULL. NULL means the environment's version is unknown, not that it converged.
Approach
- Parse each string once into a comparable tuple: (major, minor, patch, is_final, rc), where is_final is 1 for a plain release and 0 for a pre-release, and rc is 0 when absent. Tuple comparison then yields the stated order for free, including 3.10.0-rc.1 < 3.10.0. O(L) per string, O(n*L) total.
- Track min and max in one linear scan rather than sorting: O(n) time, O(1) extra space, and n is only a few hundred so the parse dominates either way.
- Fail loudly on a string that does not match the grammar instead of defaulting it to 0.0.0 or to the newest release. An unparseable reported_release is a reconciliation-ingest bug, and either default hides it in the direction that looks safe.
- Compute the minor spread only when min and max share a major version. Across majors the difference of minor numbers is meaningless, so return the pair of versions and flag the condition rather than a number.
- Report the NULL count as its own output line. Folding unknowns into 'converged' is how an install ends up running a release nobody knew was still live.
Worked solution (15 min)
- Input reported_release values: '3.9.0', '3.10.2', '3.10.0-rc.1', NULL, '3.9.0'.
- Parse to tuples: (3,9,0,1,0), (3,10,2,1,0), (3,10,0,0,1). The NULL is diverted to the unknown counter before parsing.
- Scan for min and max by tuple: min is (3,9,0,1,0) = 3.9.0, max is (3,10,2,1,0) = 3.10.2. Confirm 3.10.0-rc.1 sits strictly between them.
- Same major (3), so the minor spread is 10 - 9 = 1. Unknown count is 1.
Expected result: lowest='3.9.0', highest='3.10.2', minor_spread=1, unknown_count=1. A lexicographic sort of the same list would have returned highest='3.9.0' and lowest='3.10.0-rc.1'.
Verify it yourself
- The parsed tuple for '3.10.0-rc.1' compares less than the tuple for '3.10.0' and greater than the tuple for '3.9.0'.
- unknown_count plus the number of parsed versions equals the number of environment rows, so nothing was dropped.
- Swapping the input order does not change any of the four outputs.
- Adding a second environment already at '3.9.0' leaves the spread at 1, since spread is over distinct versions present, not over row counts.
Follow-up
- Two majors are live at once. What does 'spread' mean now, and what single number, if any, would you still publish?
- reported_release is a claim from the environment, and last_heartbeat_at may be days old for a client-managed install. At what staleness does this figure stop being usable?
- How would you enforce the supported window when the rollout plan is built, instead of reporting a violation after it already exists?
Generate invoice lines once under concurrent month-end runs
Hard · isolation levels · unique constraints · idempotency
Month-end generation reads approved work_record rows for one engagement and period, sums minutes, inserts one invoice_line whose generation_key is UNIQUE over (engagement_id, period_start, period_end, line_type, generator_version), then sets those work records to invoiced with locked_at and invoice_line_id. Two runs execute concurrently for the same engagement while late approvals are still committing. Working in PostgreSQL, name the anomaly at READ COMMITTED and at REPEATABLE READ, and specify the controls that make a retry a no-op rather than a second charge.
Approach
- At READ COMMITTED each statement takes a fresh snapshot, so the SELECT that computed the sum and the later UPDATE see different data: an approval committed between them is a phantom the sum missed but the UPDATE can still mark invoiced. Worse, an UPDATE that blocks on a row another transaction is changing re-evaluates its WHERE against the new row version once the lock releases, so WHERE status = 'approved' silently skips rows the other run already moved — fewer rows than you counted, and no error anywhere.
- At REPEATABLE READ, which in PostgreSQL is snapshot isolation, the sum and the update agree because the whole transaction shares one snapshot, but a concurrent update to the same row aborts you with serialization_failure, SQLSTATE 40001, which the application must catch and retry. Snapshot isolation still permits write skew across different rows; only SERIALIZABLE with SSI excludes it, at the cost of more 40001s and predicate-lock memory bounded by max_pred_locks_per_transaction.
- Make the second run collide instead of race: take a transaction-scoped advisory lock on a hash of (engagement_id, period_start, line_type) at the top of the run, so one generates while the other waits and then finds finished work. Keep the UNIQUE generation_key as the backstop, because the advisory lock is per-database-connection state that a failover or a second database node does not carry.
- Treat the unique violation as success. Catch SQLSTATE 23505, re-select the existing invoice_line by generation_key, and return it. INSERT ... ON CONFLICT DO NOTHING ... RETURNING returns zero rows on conflict, so a handler that trusts RETURNING writes a NULL invoice_line_id and reports a failure for work that in fact completed; ON CONFLICT DO UPDATE is worse, because it overwrites a line that has already been issued.
- Bound the read deterministically rather than by timing: select the work records FOR UPDATE ordered by work_record_id so both runs acquire row locks in the same order and cannot deadlock, and filter on approved_at < the run's start timestamp so a late approval is out of scope by definition and lands in the next period.
- Enforce immutability in the database, not in the service. A BEFORE UPDATE trigger raising when locked_at IS NOT NULL and a billed column changes, plus CHECK (status <> 'invoiced' OR invoice_line_id IS NOT NULL), means the constant and legitimate business pressure to edit an invoiced entry resolves into an appended credit_note row instead of a quiet update.
Follow-up
- A line has been issued and then an approval behind it is reversed. What rows do you write, and what does the original line look like afterwards?
- How many times does the run retry before giving up, and what state does it leave behind for the operator who picks it up?
- After the fact, how do you prove no engagement-period was billed twice, without trusting the application code that wrote it?
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
- 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.
- 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.
- 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.
- 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.
- 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)
- Create the table with a handful of rows across three idempotency keys, including one key with attempts 1 failed, 2 ambiguous, 3 succeeded.
- Write the inner DISTINCT ON query alone and confirm it returns one row per key.
- Wrap it and add WHERE status <> 'succeeded' plus ORDER BY watermark_from in the outer query.
- 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?
What is the Event Loop in JavaScript?
Medium
Approach
- State your assumptions explicitly before working the problem.
- Say what you would check first and why it is the highest-information step.
- Work from the requirement backwards to the design.
Follow-up
- How would you know your answer was wrong?
- What assumption would you test first?
Explain the concepts of Inheritance, Polymorphism, and Abstraction.
Medium
Approach
- Clarify what is being asked and what a complete answer contains.
- Say what you would check first and why it is the highest-information step.
- Work from the requirement backwards to the design.
Follow-up
- What assumption would you test first?
- How would you know your answer was wrong?
Fan out desired state and collect convergence evidence per tier
Medium · fan-out · reconciliation · partial observability · rollout
The control plane is the only writer of desired state for a few hundred environments (environment_id, isolation_tier, region, desired_release_id, reported_release_id, reported_config_fingerprint, status, last_heartbeat_at). Shared-pool and dedicated environments sit in networks you can reach; client-managed installs do not and can only report outward on their own schedule, sometimes not for weeks. You have to roll out a release and then, later, decide it is safe to remove a deprecated API field. Design the distribution path and the reporting path, and define exactly what converged means.
Approach
- Split delivery by reachability instead of pretending one mechanism covers both. Reachable tiers get a push-and-reconcile loop you drive; client-managed installs pull desired state and post a report, because nothing you build reaches into a network you have no route to. Both write the same reported_ columns, so the question of what is live stays one query rather than two subsystems with different truths.
- Define converged as a positive statement from the environment: reported_release_id equals desired_release_id, reported_config_fingerprint equals the fingerprint of the desired config, and last_heartbeat_at falls inside that tier's freshness window. A NULL reported_release_id means unknown, and unknown must never be folded into converged - that fold is precisely how a client's install ends up on a version nobody knew was running.
- Recognise that a few hundred targets is not a throughput problem, so spend the design on evidence rather than on delivery concurrency. Carry reported_at per environment, expose its age, and make any 'N of M converged' number carry the age distribution of the missing rows; without that, the dashboard's reassuring figure is the thing that causes the incident.
- Gate the contract step on observed convergence, never on elapsed time. The removal condition is that no fresh report names a release below the target and no environment is stale or unknown, which means one silent install blocks the removal - the correct and unpopular answer. Then make the block actionable by reporting it per client with an age, so it becomes a specific conversation rather than an indefinite wait.
- Bound the version spread rather than discovering it later: refuse to provision or upgrade onto a release outside the supported window, and track max(desired) minus min(reported) as a first-class number. An unmeasured spread widens until some schema change becomes impossible, and by then the cost lands on whoever proposes it.
Worked solution (25 min)
- Load 300 environment rows across the three tiers with a dozen distinct release ids and heartbeat ages from seconds to four months.
- Write convergence as a three-part condition and removal-safety as its negation, then run both and read the outputs.
- Set reported_release_id to NULL on 20 client-managed rows and re-run removal-safety.
- Introduce a config fingerprint mismatch on an environment whose release id matches, and see which condition catches it.
Expected result: Removal-safety returns blocked whenever any environment is stale or unknown, and names them with their client and report age rather than returning a bare boolean. The NULL rows block removal instead of counting as converged. The fingerprint mismatch is caught by the config term while the release term passes, which is why convergence is three conditions rather than one.
Verify it yourself
- No query on the rollout path reads reported_release_id without also testing heartbeat freshness.
- Converged, diverged, stale and unknown are exhaustive and mutually exclusive, and their counts sum to the environment total.
- Provisioning an environment onto a release outside the supported window fails rather than widening the spread.
Follow-up
- A client-managed install has not reported for four months. It may be decommissioned or it may be serving. What do you do before removing the field?
- One environment reports a release id you never published. What are the candidate explanations, and what do you do with the row meanwhile?
- How would a rollout stop itself automatically, and what signal could you possibly have for a tier you cannot instrument?
Environment list page slows as the estate grows
Easy · n+1 query · latency · batching
An estate console lists environments for an operator: environment_id, client name, isolation_tier, the desired release version string, the reported release version string, and the engagement's current status. A 10-row filter returns in 25 ms; the unfiltered 200-row page takes 640 ms. No single statement exceeds 0.5 ms, and the database records 553 statements for that request. Rows where reported_release_id is NULL are measurably cheaper than the rest. Diagnose the cause and give the fix, stating the statement count you expect afterwards.
Approach
- Get the shape before theorising about plans. Divide the per-request statement delta in pg_stat_statements by the rows returned: 553 statements for 200 rows, none over 0.5 ms, is round trips rather than work, and rules out a bad plan immediately.
- Account for 553 exactly, because the arithmetic is the diagnosis and not a detail. One driver query, one desired-release lookup per row, one current-engagement lookup per row, and one reported-release lookup only for the rows where reported_release_id is non-null: 1 + 200 + 200 + 152. The 152 is why client-managed rows are cheaper, and if the sum did not land on the observed number something else is firing too.
- Sanity-check the cost model: 553 round trips at roughly 1 ms each is about 0.55 s on top of a 25 ms baseline, which accounts for the 640 ms. A hypothesis that does not reproduce the measured number is not finished.
- Batch it. Collect the ids from the driver query, then fetch releases with WHERE release_id = ANY($1) and the open engagement versions with WHERE engagement_id = ANY($1) AND effective_to IS NULL, and assemble in memory. Three statements per page, independent of page size.
- Check index usage before declaring victory. The engagement fetch must use the partial unique index on (engagement_id) WHERE effective_to IS NULL; without it, ANY($1) scans every amendment version of every engagement and the fix regresses harder than the bug did.
- Pin the behaviour with a test asserting a constant statement count per request, since no individual query here is slow enough to ever trip a slow-query alarm.
Follow-up
- The operator wants to sort the page by reported release version. Where does that sort have to happen now, and what does it cost?
- Page size goes to 1,000. Which breaks first, the driver query or the ANY($1) parameter list?
- reported_release_id NULL means unknown, not converged. How should the list render that, and what does the count at the top of the page then mean?
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.
The estimate and the day you knew
A slipped date is only a bad story if you sat on it. What matters is what you believed when you gave the number, the signal that told you it was wrong, how many days passed before you said so, and what you cut rather than asking for more time. Scope you defended counts as much as scope you dropped.
Discuss your experience in creating basic HTML/CSS form pages.
Medium · behavioural and engineering judgement
Approach
- Give the blast radius: what could have broken, and what you measured.
- Name the disagreement and how you resolved it with evidence.
- Pick a story where you made the decision, not one where you watched it.
Follow-up
- What did you decide not to do, and why?
- What would you do differently if you ran that again?
Disagree in review about a dedupe key derivation
Medium · idempotency · code review · schema drift
A colleague's connector change computes its dedupe key as a hash over the whole source payload. You believe it must be derived from named, stable source fields with a stored derivation version, because the client renames and retypes fields without notice and every previously computed key must stay reproducible. Recount a code review disagreement of this shape that you actually had. Give the argument you made, the evidence that moved it, how long it stayed open, and what you did when the author was still unconvinced.
Approach
- State the technical claim as a failure rather than a preference: hashing the whole payload means any upstream field addition changes every key, so the next run finds no ledger hit and re-applies records the client already has.
- Say what evidence you produced. A replay of a real past payload change through both derivations, or a count of fields in the sample that have already changed shape, wins a review; seniority does not.
- Describe the boundary you used between disagreeing and committing: reversible choices get one round and then the author decides, while a choice that corrupts persisted data is worth escalating. Say which this was and why.
- Include the cost you accepted. A versioned derivation means storing the input field names and the derivation version alongside the hash, and keeping each old version's code path alive as long as its keys can still be looked up.
- Close with what the review changed beyond the one diff: a test that pins the key across a renamed field, or a written rule about what may feed a dedupe key.
Follow-up
- The client renames a field that feeds the key. Walk through what your derivation version does on the next run, and what happens to records already applied under the old key.
- The author was senior to you and unconvinced, and the merge was needed that afternoon. What did you do?
Reverse a decision on token validation after measuring
Medium · credential lifetime · reversibility · measurement
Recount a decision you made and then reversed on evidence. A worked example from this domain: choosing offline validation of self-contained credentials for latency, then reversing once you measured that a revoked grant stayed usable until its own expiry, with an engagement already closed. State what you originally optimised for, the measurement that changed your mind, what the reversal cost in work already done, and how you concluded that reversing was cheaper than compensating around the original choice.
Approach
- State the original decision with the objective it optimised and the property you did not measure. Revocation lag is the usual one, because it is invisible until somebody is offboarded mid-engagement.
- Give the measurement as a number with a method: time from revocation to first rejected call, observed across real grants, not a value read out of a configuration file.
- Compare the mechanisms on all three axes honestly. Offline validation adds no round trip and keeps working while the issuer is down, but cannot recall a token before its own expiry because offline validation never asks anyone. Introspection bounds revocation lag by its cache TTL and pays for it with a per-call dependency on the issuer's availability and latency.
- Say what you actually chose, which is often neither extreme: a short TTL with offline validation is frequently correct, because capping every grant at min(requested_ttl, engagement_end + grace - now) bounds worst-case access by a number you picked rather than by whether a revocation call succeeded.
- Account for the sunk work and the migration: what shipped, what was discarded, and how you avoided a flag day for callers already holding credentials.
Follow-up
- What TTL do you pick, and what requirement are you trading against as you shorten it?
- An engagement terminates early on a Friday evening. Trace the worst case under your final design and say precisely what is still valid on Saturday morning.