A Software Engineer at Nearform serves as a core contributor within a high-impact, consultancy-driven environment. You are not just writing code; you are solving complex business problems for major global clients. The role requires a blend of technical precision, adaptability, and the ability to thrive in a fast-paced, project-based model where you may rotate between different client engagements or technology stacks.
This position is critical because Nearform prides itself on technical excellence and deep expertise in modern web ecosystems, specifically Node.js, React, and increasingly, AI-integrated development. You will contribute to products that demand high performance and scalability, often working alongside cross-functional teams to deliver robust software solutions. The work is challenging, intellectually stimulating, and requires a high degree of autonomy and professional maturity.
Initial Screening Call
reportedThe 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 Assessment
reportedMost 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.
Final Discussions
reportedWhere the day includes a partner from product, design or data, that conversation is weighted like the technical ones and prepared for least. They are deciding one thing: whether having you in the room makes their decisions cheaper. That means options with costs attached, not implementation detail and not "it depends". An estimate someone can plan against — a range, the assumption that would push it to the high end, and what you would drop to hit the low one — is worth more than a confident single number, which everyone present already knows is wrong.
What to demonstrate
- Whether an estimate comes as a range with the assumption most likely to break it, and states what a specific scope cut would actually buy
- Whether a technical constraint is handed over as a choice with consequences on their side, rather than as a verdict they have no standing to argue with
- Whether you establish what decision is on the table before proposing anything
- Whether risk is raised while it can still change the plan, with the trigger that would confirm it, instead of reported afterwards as a slip
How to prepare
- Take a project that shipped late and write the two-sentence warning you could have given three weeks earlier, naming what you would have needed decided at that point
- Rehearse one estimate out loud until it arrives in three parts: the range, the single assumption that would blow it, and the smallest thing you would cut to protect the date
- Rewrite an objection you have actually made — the "we can't do that" version — as two options with their costs, so the choice ends up with the person who owns it
PracHub editorial advice for the preparation topics above.
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.
Forking the product per client instead of building a configuration or extension point
A fork is the cheapest possible answer for one client and the most expensive possible answer for the estate. Maintenance cost grows as O(clients x changes): a single security patch now needs one port per fork, each with its own conflicts, its own test run and its own release window, and the forks drift further apart with every port. The honest rule is that a required fork is a product gap, so it should either become a supported extension point with a stable interface, or be an explicit, time-boxed and separately priced artefact that is never expected to receive upstream fixes.
Optimising an axis nobody named
Ask which resource is actually scarce here: wall-clock latency, throughput, memory footprint, cost per request, or engineering time. Shaving a constant factor off an in-memory step is wasted effort when the same function makes a blocking remote call inside the loop.
Sharing mutable state with no stated owner
Say which thread, request or task owns each mutable structure, and what protects it when the answer is more than one: a lock, a queue that hands ownership across, or an immutable copy per reader. A structure documented as safe for concurrent reads is usually not safe for a concurrent write alongside those reads.
Choose a category, try a prompt, then open its approach, worked solution or follow-up when you need it.
Implement a functional game (such as BlackJack) with a deck, shuffling…
Implement a functional game (such as BlackJack) with a deck, shuffling, and hand-calculation logic.
Approach
- Name the brute-force solution and its complexity before improving on it.
- Restate the input: its shape, its size, and what is guaranteed about it.
- State the target complexity and say which constraint rules the naive version out.
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?
Explain the behavior of asynchronous code and Promises in JavaScript.
Explain the behavior of asynchronous code and Promises in JavaScript.
Approach
- Name the brute-force solution and its complexity before improving on it.
- Walk one small example through your approach before writing the whole thing.
- Restate the input: its shape, its size, and what is guaranteed about it.
Follow-up
- How does this change if the input no longer fits in memory?
- Which test case would catch an off-by-one here?
Create a REST API application from scratch, including endpoint impleme…
Create a REST API application from scratch, including endpoint implementation and testing.
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.
- Walk one small example through your approach before writing the whole thing.
Follow-up
- Which test case would catch an off-by-one here?
- How does this change if the input no longer fits in memory?
Given a specific Node.js script, predict the order of console log exec…
Given a specific Node.js script, predict the order of console log execution.
Approach
- Name the brute-force solution and its complexity before improving on it.
- Choose the data structure from the access pattern, not from familiarity.
- Walk one small example through your approach before writing the whole thing.
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?
Compute the minimal re-run set that preserves idempotency keys
One connector binding must cover [span_start, span_end). You have all its connector_run rows: (idempotency_key, attempt_no, status, watermark_from, watermark_to), with half-open ranges, where idempotency_key is derived deterministically from the connector and the range, so re-running a range reproduces its key. Only status='succeeded' counts as covered. Produce the minimal re-run plan: every instant in the span ends up covered, any sub-interval some prior run already claimed is re-run at exactly that claim's range, and genuinely unclaimed sub-intervals become new claims cut at the binding's chunk size. Explain what merging ranges would cost.
Approach
- Merge the succeeded ranges into a maximal disjoint union: sort by start, sweep extending the open segment while the next start is not beyond the current end. O(n log n) time, O(n) space, and this is the only status that contributes coverage.
- Take the complement of that union inside [span_start, span_end) to get the uncovered gaps, in O(u) over the merged segments. Clip at both ends so the plan never reaches outside the span.
- Sweep the non-succeeded claims (ambiguous, failed, abandoned) against the gap list with a two-pointer over both sorted lists. Any claim intersecting a gap is emitted at its original watermark_from and watermark_to, byte for byte, so the derived idempotency_key reproduces and the retry rejoins the same ledger row. O(n) after sorting.
- Subtract the emitted claim ranges from the gaps; whatever survives was never claimed by any attempt and becomes fresh ranges cut at the chunk size. Only these get new keys. O(u + c).
- Accept that a re-emitted claim may overlap time that is already succeeded. Range bookkeeping is not what makes the retry safe; the record-level dedupe key is, and re-reading a covered window costs one extra fetch against the client's quota, which is far cheaper than a key you can no longer reproduce.
- Exclude anything still in status 'running' with a live lease. Re-claiming it races the in-flight attempt, doubles the quota spend against the client endpoint, and produces two ledger writers for one key. Total plan cost is O(n log n) time and O(n) space.
Worked solution 40 min
- Span [00:00, 06:00). Runs: K1 [00:00, 02:00) succeeded; K2 [02:00, 03:00) attempt 1 ambiguous and attempt 2 failed; K3 [03:00, 04:00) succeeded; K4 [04:00, 05:00) abandoned. Nothing claims [05:00, 06:00). Chunk size is one hour.
- Merge succeeded ranges: [00:00, 02:00) and [03:00, 04:00). They do not touch, so the union has two segments.
- Complement inside the span: gaps are [02:00, 03:00) and [04:00, 06:00).
- Match claims to gaps: K2 intersects the first gap and is emitted at [02:00, 03:00); K4 intersects the second and is emitted at [04:00, 05:00). Both keep their original ranges and therefore their original keys.
- Subtract the emitted claims from the gaps: [05:00, 06:00) remains, unclaimed by any attempt, and becomes a single new claim at the one-hour chunk size.
- Compare against the merge shortcut: emitting the two gaps as [02:00, 03:00) and [04:00, 06:00) would give the second range a key that no prior attempt ever used, discarding K4's ledger history.
Follow-up
- An ambiguous run may have applied every record before the timeout. What do you read to settle it before re-running, and what is left if the client's schema offers no natural key?
- Retention pruned the ledger rows for one of these keys. What does the re-run do now, and what bounds how long the ledger must be kept?
- Two planners run concurrently against the same binding. What stops them emitting and executing the same plan twice?
Add a non-null column to a live work-record table
work_record holds roughly nine million rows and absorbs a month-end write burst. You must add currency CHAR(3) NOT NULL, backfill it from the engagement, add a foreign key to rate_card, and eventually drop the superseded hours_decimal column. Several releases are live, including client-managed installs running two-quarter-old code that still writes hours_decimal. Give the ordered plan, the lock each step takes and for roughly how long, how the backfill is chunked and restarted, and the signal that makes the drop safe.
Approach
- Sequence it expand, dual-write, backfill, switch reads, contract, and be explicit that only the last step is dangerous. Adding is cheap because an old release ignores a column it does not know about; removing breaks a writer you cannot upgrade and whose failure you may not see for weeks.
- ALTER TABLE work_record ADD COLUMN currency CHAR(3) — nullable, no default. That is a catalog-only change on every supported version and takes milliseconds. Refuse NOT NULL DEFAULT 'XXX': PostgreSQL 11 and later store a non-volatile default in the catalog and materialise it on read, so it is just as fast, but it hands all nine million rows a value that is already non-null and already wrong. That destroys the rest of the plan — the NOT NULL you add later is satisfied from the first second, so it can never find a row the backfill missed, and every consumer now has to distinguish a real currency from a sentinel forever. A NULL is the only marker that survives a crashed backfill.
- The statement is instant; the lock queue is not. An ACCESS EXCLUSIVE request waits behind every open transaction holding a conflicting lock on the table, and every request arriving after it queues behind that, so one long analytics SELECT turns instant DDL into a total stall. Run it with SET lock_timeout = '2s' inside a retry loop that catches 55P03 and backs off with jitter.
- Decide up front what an unconverged writer inserts. Two-quarter-old code omits currency, so it writes NULL, which means SET NOT NULL would start rejecting its inserts outright. Either fill the gap in the database with a BEFORE INSERT trigger that resolves currency from the engagement when NEW.currency IS NULL — one indexed lookup per insert, which you should time against the month-end burst before committing to it — or leave the column nullable and gate SET NOT NULL on the same release-convergence signal that gates the drop. What you must not do is paper over it with a default, which turns a visible NULL from an unconverged writer into an invisible 'XXX' that reconciliation will never flag.
- Backfill on a keyset cursor — WHERE work_record_id > $last AND currency IS NULL ORDER BY work_record_id LIMIT 5000 — one transaction per batch, advancing $last to the last work_record_id the batch wrote so filled rows are never revisited. Set statement_timeout, pause between batches so autovacuum and any replicas keep up, and persist the cursor so a crash resumes from the last committed batch. OFFSET n costs O(n) per page because the server scans and discards those rows, which makes the whole backfill quadratic in the table size.
- Take NOT NULL without a blocking scan, and only once the backfill reports zero rows remaining: ADD CONSTRAINT work_record_currency_nn CHECK (currency IS NOT NULL) NOT VALID takes a brief ACCESS EXCLUSIVE and scans nothing; VALIDATE CONSTRAINT runs under SHARE UPDATE EXCLUSIVE so reads and writes continue throughout, and raises check_violation, SQLSTATE 23514, naming a row if the backfill missed one — that failure is the entire reason the step exists, and it only exists because the column was added without a default; then SET NOT NULL, which on PostgreSQL 12 and later proves itself from the validated CHECK and skips its own full scan.
- Use the same shape for the foreign key — ADD CONSTRAINT ... REFERENCES rate_card(...) NOT VALID, then VALIDATE CONSTRAINT — and build any supporting index with CREATE INDEX CONCURRENTLY, which cannot run inside a transaction block, scans the table twice, and on failure leaves an INVALID index that must be dropped concurrently before you retry. Note what the FK does not prove: a single-column reference is MATCH SIMPLE, so rows with NULL currency pass validation untouched, and validating it before the backfill finishes certifies only the rows that were already correct.
- Gate the contract step on observed convergence rather than elapsed time: every non-destroyed environment must report a reported_release_id at or past the release that stopped writing hours_decimal, and reported_release_id IS NULL counts as unknown and therefore blocks. DROP COLUMN itself is a catalog-only update, instant under ACCESS EXCLUSIVE, reclaiming no space until a rewrite — the cost is entirely in the writer you did not know was still running.
Worked solution 45 min
- Write out each DDL statement in order, annotated with its lock mode and whether it is safe during the month-end burst.
- Implement the retry wrapper: SET lock_timeout, attempt, catch 55P03, sleep with jitter, retry a bounded number of times.
- Write the keyset backfill loop with the cursor persisted after each committed batch, and time one batch.
- Leave one row un-backfilled on purpose, run VALIDATE CONSTRAINT, and read the 23514 it raises — then fix that row and re-run. A plan in which this step cannot fail is a plan that is not checking anything.
- Write the convergence query over environment that returns the rows still blocking the drop.
- Ship the DROP COLUMN as its own migration whose precondition is that query returning zero rows.
Follow-up
- A client-managed install reports a release you no longer support and has not converged for two quarters. Do you drop, and what do you owe that client first?
- During the dual-write window, which column does the month-end reconciliation read, and what does a mismatch between the two mean?
- The backfill dies at batch 900 of 1,800. What exactly does the restart re-read, and how do you know it did not skip a range?
Find watermark coverage gaps with a window function
Each succeeded connector_run claims a half-open watermark range [watermark_from, watermark_to). Ranges for one connector can overlap, and a re-run can be nested entirely inside an earlier range. Write the query that returns, per connector_id, every interval not covered by any succeeded run, as (gap_from, gap_to), ordered by connector then time. State the frame clause you use and why the default one is wrong, and give the index that removes the sort.
Approach
- Rule out lag(watermark_to) first and say why: ordered by start time, the immediately preceding row may end earlier than a row before it, so a nested or shorter range makes you report a gap that a wider earlier range already covers. The correct comparison is against the running maximum of every previous watermark_to, not the last one.
- Compute max(watermark_to) OVER (PARTITION BY connector_id ORDER BY watermark_from, watermark_to ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING). The ROWS frame is load-bearing: the default frame is RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW, which includes the current row, so the running max always covers the current row's own end and the query reports no gaps ever.
- Add watermark_to as a tiebreaker in the window ORDER BY so rows sharing a start instant are ordered deterministically under a ROWS frame, then emit a gap where the running max is non-null and watermark_from is strictly greater than it. The first row per connector has an empty frame and a NULL max; that is not a gap unless you define a floor such as the binding's creation time, which you should decide explicitly.
- Filter status = 'succeeded' in the window's input, and argue the classification: an ambiguous run may or may not have applied its range, so counting it as covered can drop data silently. Gap detection should treat ambiguous as uncovered and leave resolution to the reconciliation path.
- Offer the shorter alternative with its precondition: on PostgreSQL 14 and later, range_agg(tstzrange(watermark_from, watermark_to, '[)')) per connector builds a multirange, and the gaps are its complement against the window you care about. It handles adjacency and nesting without any frame subtlety, at the cost of requiring that version.
- Index (connector_id, watermark_from, watermark_to) WHERE status = 'succeeded'. It supplies the window's required ordering directly, so the plan is a WindowAgg over an index scan with no Sort, and the work is O(n) over that connector's succeeded runs.
Follow-up
- How do you distinguish a real gap from a period when the connector was deliberately paused?
- The connector has run for two years and you only care about the last 30 days. What does adding that predicate do to the correctness of the running maximum?
- How would you turn this query into an alert without it firing on the trailing edge, where the newest range is simply not written yet?
Describe your process for setting up a development environment from sc…
Describe your process for setting up a development environment from scratch.
Approach
- Fix the scope first: who calls this, how often, and what they do when it fails.
- Choose a partition key and say what query it makes expensive.
- Name the failure you are designing for, then the recovery path.
Follow-up
- What breaks first when traffic grows ten times?
- What would you drop to keep the system up under load?
Explain your approach to unit testing and why it is critical for your …
Explain your approach to unit testing and why it is critical for your implementations.
Approach
- Name the read and write paths separately; they rarely have the same bottleneck.
- Choose a partition key and say what query it makes expensive.
- Name the failure you are designing for, then the recovery path.
Follow-up
- What would you drop to keep the system up under load?
- How does this behave when that dependency is down for an hour?
Place tenants across pooled and dedicated stores without leaking rows
Every row that is not global reference data carries client_id, and a query run for one client must never read another's. The platform serves shared-pool clients from one PostgreSQL cluster behind a transaction-pooling proxy, dedicated clients from a database each, and every region separately. Growth takes the estate from 200 clients to 2,000. Design the placement and the enforcement: the partition key, which layer enforces isolation, what that layer still lets through, and what breaks first when the client count multiplies by ten.
Approach
- Fix client_id as the partition key and place by (region, isolation_tier). Residency makes region a hard partition with no cross-region query at all, and the tier selects the store. Resist engagement_id as the key even though it is the authorisation root: engagements start and end inside one client, so a key that changes under a row turns a contract renewal into a data migration.
- Enforce below the application with row-level security keyed on a transaction-scoped setting: SET LOCAL app.client_id at the start of each transaction, policies of the form USING (client_id = current_setting('app.client_id', true)::bigint). Three specifics decide whether it holds. A plain SET survives the connection's return to a transaction-pooling proxy, which does not reset session state between transactions by default, so a handler that forgets its SET inherits the previous tenant's value and the policy then enforces the wrong client flawlessly. Policies do not apply to the table owner unless FORCE ROW LEVEL SECURITY is set, and never apply to a role with BYPASSRLS, so the migration role and the serving role must be different roles.
- State what RLS still lets through, because a boundary you cannot enumerate is not a boundary: it scopes rows, not an aggregate you computed earlier, not a cache entry keyed without the client, not a log line or an error payload carrying another client's data, and not a bug in the code that sets the variable. Assert that the setting matches the request's client immediately before the transaction's first query rather than trusting the middleware, which converts a leak into an error for the price of one comparison.
- Cost the alternative at both sizes rather than in principle. Database-per-client is comfortable at 200. At 2,000 the binding constraints are connection count (a pool per database per service instance, against PostgreSQL backends that are processes costing megabytes each), migration fan-out (one schema change becomes 2,000 runs whose slowest or half-failed member defines your release), and backup and restore surface. Pooled-with-RLS inverts every one of those costs and pays for it with a blast radius of one cluster.
- Choose per tier rather than globally: pooled with RLS as the default, a dedicated database where the contract demands it, and identical application code in both because the code never selects its own scope. What remains is routing, and routing belongs at connection acquisition, keyed by the client on the request, so a mis-routed request fails to acquire rather than succeeding against the wrong store.
Worked solution 40 min
- Create two tables carrying client_id, enable ROW LEVEL SECURITY and FORCE ROW LEVEL SECURITY, and add a policy reading current_setting('app.client_id', true).
- Put a transaction-pooling proxy in front and interleave requests for two clients over a small pool, with one code path using SET, one using SET LOCAL, and one deliberately omitting it.
- Run the same queries as the table owner and as a role holding BYPASSRLS.
- Leave the variable unset on a fresh backend and run a plain SELECT.
Follow-up
- Show me a support tool that legitimately reads across clients. What makes it safe, and how is each of its reads attributed?
- One pooled client's query plan regresses and saturates the cluster. Who else notices, and what did you build in advance to contain it?
- A row turns up under the wrong client. What is your first query, and what is your notification obligation while you run it?
Entitlement snapshot expires in lockstep across the fleet
Delivery requests across the estate return 403 for roughly 200 ms every five seconds. The entitlement service publishes one versioned snapshot; every caller caches it for five seconds and fails closed on a miss. Its request rate is near zero for most of each window and about 4,000 in a 300 ms burst, spread across the 800 application processes a rolling deploy restarted this morning. Its own handler service time is unchanged. Diagnose the shape, then give a fix that preserves fail-closed behaviour.
Approach
- Establish the period before the cause. Plot the origin's request rate at one-second resolution: a sawtooth whose period equals the cache TTL, with the mass inside a few hundred milliseconds, says the misses are synchronised, not that traffic grew.
- Measure two things next, in this order: key cardinality and in-flight concurrency per process. One key across the whole fleet means every process misses at the same instant. Four thousand requests from 800 processes means roughly five concurrent misses inside each process, so there is no single-flight and each in-flight request for the same key issues its own fetch.
- Explain why the phases are locked: the rolling deploy populated every cache within the same few seconds, so 800 independent five-second TTLs expire together and nothing in the system ever scatters them again.
- Fix three independent defects rather than one. Single-flight per key per process, so a process has at most one outstanding fetch for a key. A per-process randomised TTL, for example uniform over four to six seconds, so expiry decorrelates after any synchronising event. Refresh-ahead at about 80 percent of the TTL, so the hot key is replaced before it expires and a request never waits on a miss at all.
- Keep fail-closed honest about what staleness costs. Serving a stale snapshot on a fetch error is safe for the engagement end-date case, because ends_on travels inside the snapshot and the caller evaluates it locally. It is not safe for a mid-engagement suspension, which only arrives as an invalidation event. So bound the stale window explicitly at tens of seconds, fail closed beyond it, and state the resulting suspension propagation delay as the accepted price.
- Name the new steady state so nobody is surprised by it: compute it from the refresh interval rather than the TTL, because refresh-ahead is what now sets the period. Refreshing at 80 percent of a 5-second TTL means each process fetches every 4 seconds, assuming the refreshed entry restarts the TTL clock, so the floor is fleet size divided by the refresh interval: 800 / 4 = 200 requests per second, not the 800 / 5 = 160 that the TTL alone would suggest. Jitter nudges it up rather than down, because the rate averages 1/T and not 1/mean(T): with TTL uniform over 4 to 6 seconds the floor is about 203 requests per second. A process that stops asking for the key stops refreshing it, so 200 is the floor for a uniformly hot fleet and an upper bound for any other traffic mix. Verify by replaying 800 synchronised clients and confirming the origin sees a flat rate at that level with no burst above a stated ceiling.
Follow-up
- At 8,000 processes the steady refresh floor becomes 8,000 / 4 = 2,000 requests per second, set by process count and refresh interval alone and independent of user traffic. What changes in the design?
- A client is suspended for non-payment at 09:00. Walk the worst-case time until every process stops serving them, with your stale window in place.
- How would you detect the next stampede automatically, given that the origin's average rate barely moves when one occurs?
Day one measures instead of guessing, under a fixed rubric, and the remaining hours are allocated in proportion to the gaps before any studying begins. The allocation is deliberately not renegotiated midweek, because the area that feels worst on day three is usually the one that is moving.
Prepare, practise & reflect
One practical outcome each day. Spend longer where you need it.
0 / 7 done01Diagnostic, scored before you study anything
- Sit a 110-minute diagnostic in four blocks: forty-five minutes on two coding problems, twenty-five on one design prompt taken to interface and data model, twenty of short-answer fundamentals, and twenty delivering two behavioural answers aloud.
- Score each block from 0 to 3 on a fixed rubric where 3 is correct and fluent, 2 is correct but slow or prompted, 1 is partially correct and 0 is stuck, grading the artifact rather than how the attempt felt.
- Allocate days two to five in proportion to 3 minus each block's score, write the allocation down, and commit to leaving it alone.
Deliverable: A scored rubric and a fixed hour allocation for the rest of the week.
Practice prompt ↗Practice prompt ↗Worked solution ↗02Largest gap: find the boundary rather than the subject
- Split the weakest area into named sub-skills and rate each separately. For coding those are restating the problem, choosing the structure, stating the invariant, turning the invariant into loop bounds, handling empty and single-element input, and accounting for complexity out loud.
- Attempt three items positioned just above where the rating drops off, and for each write the first move you failed to make.
- Re-attempt one of them from blank four hours later with nothing open.
Deliverable: A sub-skill map with the two blocking sub-skills circled.
Practice prompt ↗Practice prompt ↗03Drill the blocking sub-skill by repeating the shape
- Do eight short repetitions of the same shape rather than eight different problems, so what gets practised is the pattern and not the puzzle.
- State the rule you now hold in one sentence, then test it against a case built to break it, a sliding window over an array containing negative values, or a cache-aside read path whose invalidation message is dropped.
- Have someone else read your one-sentence rule and find the precondition you left out.
Deliverable: One rule statement with its preconditions attached and one counterexample that would have caught the incomplete version.
Practice prompt ↗Practice prompt ↗04Second gap, plus maintenance on the strongest area
- Run the same sub-skill decomposition on the second-largest gap in half the time.
- Spend twenty-five timed minutes on the block you scored highest, choosing the hardest item you can still finish rather than a warm-up.
- Write whether each area fails you on recall, on setup, or on execution, and set the fix accordingly: repetition for recall, a written checklist for setup, timed work for execution.
Deliverable: A second sub-skill map plus a one-line failure diagnosis for each area.
Practice prompt ↗Practice prompt ↗Worked solution ↗05The gap that is not a skill
- Record one technical and one behavioural answer, then count two things in the playback: seconds before your first clarifying question, and sentences you began without knowing where they would end.
- Practise saying that you do not know, followed by how you would find out, without letting it soften into a guess, and practise stating a complexity or an estimate before being asked for it.
- Redeliver one answer under a hard ninety-second cap, which forces structure ahead of detail.
Deliverable: Two recordings with a counted reduction in time-to-first-question.
Practice prompt ↗Practice prompt ↗06Retest under day-one conditions
- Sit the same 110-minute structure with new prompts of comparable difficulty and score it on the identical rubric.
- For any block that did not move, change the method rather than adding hours: a block stuck at 1 usually means the practice was too varied, not too short.
- Write down which single block you would still lose the offer on.
Deliverable: A second scored rubric placed beside the first, with one named remaining risk.
Practice prompt ↗Practice prompt ↗07Full loop under interview conditions
- Run a sixty-minute mock over the two blocks that moved least, with an interviewer briefed to interrupt and change direction mid-answer.
- Write the recovery script for going blank: restate the question, state your assumption, name the first thing you would check.
- Say every rule from the week aloud without reading it, and cut any you cannot state in a single sentence, since a rule you have to reconstruct mid-answer will not survive an interruption.
Deliverable: A one-page card holding the recovery script and only the rules you could state from memory.
Practice prompt ↗Practice prompt ↗Worked solution ↗Expand any day for tasks and deliverables. Your progress is saved on this device.
Team size, service count and tickets closed say very little. Seniority shows in the decision you owned: what you chose not to build, which constraint you traded away, whose objection you had to resolve before anything could move. A large project where you executed someone else's plan is a small story.
Describe your experience with specific testing libraries like Jest or …
Describe your experience with specific testing libraries like Jest or Cypress.
Approach
- Close with what you would do differently, concretely.
- Name the disagreement and how you resolved it with evidence.
- State the situation in two sentences and spend the rest on the reasoning.
Follow-up
- What would you do differently if you ran that again?
- What did you decide not to do, and why?
How do you handle situations where you need to make independent techni…
How do you handle situations where you need to make independent technical choices?
Approach
- Name the disagreement and how you resolved it with evidence.
- Give the blast radius: what could have broken, and what you measured.
- 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?
Ship a connector on a deadline with named debt
Describe shipping something you knew was incomplete against a fixed external date: a client change window, a month-end, a contractual go-live. Name the specific shortcut, such as an ambiguous outcome classified as a plain failure, a watermark advanced outside the ledger transaction, or an unchunked backfill. State the bound you put around it, who accepted the risk and where that was recorded, what would have made you refuse to ship, and what actually happened to the debt afterwards. Do not describe debt you never wrote down.
Approach
- Name the shortcut precisely enough that its failure mode follows from the description: no ambiguous state, so a timeout retried and duplicated; or the watermark committed in a different transaction from the ledger row, so a crash between them loses or replays a range.
- Give the bound, which is what separates a judged risk from a gamble: a first run restricted to a reversible slice, a record cap, a kill switch, or a reconciliation read against the target scheduled before the next run.
- Say who accepted the risk and in what artefact. 'The client knew' is credible only with a written record and a date.
- State your refusal line and why this fell on the shipping side of it. Irreversibility is the usual test: damage you can undo ships, silent corruption inside a client's system does not.
- Report what happened to the debt with a date, including the case where it is still live, and what it has cost since.
Follow-up
- The shortcut fires in production. Walk through the first hour: what you look at, in what order, and what you tell the client.
- Same deadline, but the shortcut risks writing duplicates into the client's system rather than into yours. Does your answer change?
- 01
Describe your experience with specific testing libraries like Jest or Cypress.
- 02
How do you handle situations where you need to make independent technical choices?
- 03
Describe shipping something you knew was incomplete against a fixed external date: a client change window, a month-end, a contractual go-live. Name the specific shortcut, such as an ambiguous outcome classified as a plain failure, a watermark advanced outside the ledger transaction, or an unchunked backfill. State the bound you put around it, who accepted the risk and where that was recorded, what would have made you refuse to ship, and what actually happened to the debt afterwards. Do not describe debt you never wrote down.
Is this an official Nearform interview guide?
No. It is PracHub's own research and practice material for the Software Engineer role at Nearform. Rounds and questions reflect what candidates have reported, not a process Nearform has published, and they change over time. Confirm the current format and scope with your recruiter.
PracHub interview research ↗How long should I prepare for the technical interview?
You should spend at least 3–5 days brushing up on your core language (JS/TS) and practicing writing unit tests in a blank project. The focus is on speed and accuracy, so comfort with your local environment is key.
PracHub interview research ↗Does Nearform provide the coding environment?
No, you should arrive with your own local development environment ready. Ensure you have your testing framework and IDE configured before the session begins to avoid wasting precious time.
PracHub interview research ↗Is the culture at Nearform collaborative or competitive?
It is highly collaborative. While the interviews are rigorous, they are designed to see how you perform as a teammate. Always communicate your thought process out loud.
PracHub interview research ↗What is the typical turnaround time for feedback?
While experiences vary, the process is generally fast-paced. You should expect updates within a week, though it is professional to follow up if you have not heard back after the expected timeframe.
PracHub interview research ↗Sources & methodology 3 sources ↗
No official company page is cited. Rounds and questions come from candidate reports and PracHub editorial material; each source shows the date it was read.
- 01PracHub interview research ↗
PracHub editorial research into this company and role, maintained with this guide. Candidate-reported, not an employer publication.
PracHub page · Accessed 2026-09-22 - 02PracHub Software Engineer practice ↗
Cross-company practice questions for this role.
PracHub page · Accessed 2026-09-22 - 03PracHub interview preparation framework ↗
The framework the preparation plan follows.
PracHub page · Accessed 2026-09-22