| Round | What they test | Your prep priority |
|---|---|---|
| Round 1 — DSA / coding | Data structures, async patterns, maybe a live coding problem (order book, rate limiter) | High |
| Round 2 — Full-stack deep dive | React patterns, Node.js event loop, WebSocket implementation, DB schema design | High |
| Round 3 — System design | Design an order matching engine or real-time P&L service. Scale, reliability, correctness | High |
| Round 4 — Culture / leadership | End-to-end ownership, incident handling, cross-functional collaboration, technical decisions | Medium |
- 96% latency reduction on the BI platform — perfect for "tell me about a performance problem you solved"
- Automated Options Trading Platform at Analogyx — real production trading system with WebSockets, stop-loss, distributed workers
- Delta Trading Bot — crypto options on ETH, OTM/ITM sell strategy, hedge layer, async FastAPI scheduler
- React trading dashboards with real-time execution monitoring — directly relevant to Pi42's frontend needs
Node.js is single-threaded but non-blocking. The event loop has these phases in order:
// Event loop phases (each iteration = one "tick") 1. timers — executes setTimeout / setInterval callbacks due now 2. pending I/O — I/O callbacks deferred from previous iteration 3. idle/prepare — internal Node use 4. poll — retrieves new I/O events; executes their callbacks 5. check — setImmediate() callbacks run here 6. close — close events (socket.on('close', ...)) // Between each phase: process ALL microtasks first // Microtasks = Promise .then() / .catch() + queueMicrotask() // process.nextTick() runs before microtasks
Heavy CPU tasks block the event loop — use worker_threads for CPU-intensive work (e.g. computing Greeks across thousands of positions). I/O is handled by libuv's thread pool (default 4 threads), callback returns to poll phase.
// Promise.all — waits for ALL, short-circuits on first rejection await Promise.all([fetchBalance(), fetchPositions(), fetchOrders()]) // Use when ALL results are needed (e.g. load dashboard data in parallel) // Promise.allSettled — waits for ALL to finish regardless const results = await Promise.allSettled([riskCheck(), marginCheck()]) results.forEach(r => r.status === 'fulfilled' ? r.value : r.reason) // Use when partial failure is acceptable // Promise.race — first one to settle wins (resolve OR reject) await Promise.race([fetchMarketData(), timeout(2000)]) // Classic timeout pattern for latency-sensitive order APIs // Promise.any — first SUCCESSFUL resolve wins await Promise.any([primaryFeed(), backupFeed(), tertiaryFeed()]) // Great for redundant market data feeds with failover
Architecture approach:
- Horizontal scaling: Multiple Node.js instances behind a load balancer. Use sticky sessions (IP hash) so a client always hits the same node.
- Redis Pub/Sub: When a trade happens on node-1, publish to Redis. All other nodes subscribe and push to their connected clients.
- uWebSockets.js instead of the ws library — 10x lower memory per connection, written in C++.
- Connection housekeeping: Ping/pong heartbeats every 30s, terminate dead connections. Track subscriptions per connection (don't send BTC data to ETH traders).
- Message batching: Don't send one message per tick. Batch updates every 50–100ms per connection — reduces CPU, network, and client re-renders.
// Redis pub/sub pattern across Node.js instances const publisher = redis.createClient() const subscriber = redis.createClient() // On trade execution publisher.publish('market:ETH-USDT', JSON.stringify(tradeData)) // Each WS node subscribes and fans out to its local connections subscriber.subscribe('market:ETH-USDT', (msg) => { localClients.forEach(ws => ws.send(msg)) })
Backpressure occurs when a writable stream can't consume data as fast as a readable produces it. Without handling it, buffers grow unboundedly — memory leak / crash.
// pipe() handles backpressure automatically readable.pipe(writable) // Manual handling const canWrite = writable.write(chunk) if (!canWrite) { readable.pause() // stop producing writable.once('drain', () => readable.resume()) // resume when clear }
Trading relevance: If you're writing tick data to PostgreSQL slower than WebSocket ticks arrive, backpressure management prevents data loss. In practice: buffer in Redis or Kafka (not memory), process in batches.
The crypto module provides cryptographic functionality that includes a set of wrappers for OpenSSL's hash, HMAC, cipher, decipher, sign, and verify functions.
const crypto = require('crypto'); // 1. Hashing (e.g. for generating deterministic IDs) const hash = crypto.createHash('sha256') .update('data') .digest('hex'); // 2. HMAC Signing (Essential for Exchange API Auth) function signRequest(payload, secret) { return crypto.createHmac('sha256', secret) .update(JSON.stringify(payload)) .digest('hex'); } // 3. Secure Random Bytes (for salts, session tokens) const salt = crypto.randomBytes(16).toString('hex');
crypto.sign with Ed25519 keys (common in crypto) which is faster than RSA and more secure for small payloads.Buffers are used to handle binary data directly. In a trading system, WebSocket ticks or binary protocols (like Protobuf or FIX) are often processed as Buffers to avoid the overhead of string conversion.
// Allocating memory outside the V8 heap (more stable for large data) const buf = Buffer.allocUnsafe(1024); // faster but contains old data // Fast binary parsing const price = buf.readDoubleBE(0); const quantity = buf.readDoubleBE(8); // TypedArrays for mathematical operations (Greeks, Indicators) const priceHistory = new Float64Array(1000);
Benefits: Zero-copy operations, reduced Garbage Collection (GC) pressure, and direct memory access for high-speed network I/O.
Token bucket: bucket has capacity N. Tokens refill at rate R per second. Each request consumes 1 token. Request rejected if bucket empty.
class TokenBucketRateLimiter { constructor(capacity, refillRatePerSec) { this.capacity = capacity this.tokens = capacity this.refillRate = refillRatePerSec this.lastRefillTs = Date.now() } consume() { this._refill() if (this.tokens < 1) return false // reject this.tokens -= 1 return true } _refill() { const now = Date.now() const elapsed = (now - this.lastRefillTs) / 1000 const added = elapsed * this.refillRate this.tokens = Math.min(this.capacity, this.tokens + added) this.lastRefillTs = now } } // For distributed rate limiting: store tokens in Redis (INCR + TTL) // Use Lua script for atomic check-and-decrement
// useMemo — memoizes a COMPUTED VALUE const totalPnl = useMemo(() => positions.reduce((sum, p) => sum + p.unrealizedPnl, 0), [positions] // only recomputes when positions array changes ) // useCallback — memoizes a FUNCTION REFERENCE const handleCancel = useCallback((orderId) => { cancelOrder(orderId) }, [cancelOrder]) // stable reference → child wrapped in React.memo won't re-render // ⚠ Trap: both have COST (comparison work every render) // Only use when: (1) expensive computation or (2) preventing child re-renders // Don't wrap every value/function — profile first
This is exactly what Pi42 needs. Your answer should cover four layers:
- 1. Virtual scrolling: Only render visible rows. Use react-virtual or react-window. Rendering 200 DOM nodes vs 10,000 is ~20x faster.
- 2. Throttled WebSocket → state: Don't setState on every tick. Buffer updates in a ref, flush to state via requestAnimationFrame at 60fps max.
- 3. React.memo + stable keys: Wrap row component in React.memo with price-level key. Only rows whose data changed re-render.
- 4. Immutable update pattern: Use shallow copy for changed rows only — structural sharing keeps reconciliation O(changed) not O(all).
const wsBuffer = useRef([]) useEffect(() => { const ws = new WebSocket('wss://api.pi42.com/orderbook') ws.onmessage = (e) => wsBuffer.current.push(JSON.parse(e.data)) const rafId = requestAnimationFrame(function flush() { if (wsBuffer.current.length > 0) { setOrderBook(prev => applyUpdates(prev, wsBuffer.current)) wsBuffer.current = [] } requestAnimationFrame(flush) }) return () => { ws.close(); cancelAnimationFrame(rafId) } }, [])
React builds a virtual DOM tree. On state change, it creates a new tree and diffs it against the previous (reconciliation). Differences get applied to real DOM (commit phase).
Unnecessary re-render triggers:
- Parent re-renders → all children re-render unless wrapped in React.memo
- New object/array/function reference in props on every render (common with inline
{ }or() => {}) - Context value changing reference even if data is the same
- Wrong key — using array index as key causes full re-mount when list order changes
-- Range partition by month — keeps each partition small & manageable CREATE TABLE trades ( id BIGSERIAL, symbol VARCHAR(20) NOT NULL, user_id BIGINT NOT NULL, order_id BIGINT NOT NULL, side VARCHAR(4) NOT NULL, -- 'buy' | 'sell' price NUMERIC(20,8) NOT NULL, quantity NUMERIC(20,8) NOT NULL, fee NUMERIC(20,8) NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (id, created_at) ) PARTITION BY RANGE (created_at); CREATE TABLE trades_2025_05 PARTITION OF trades FOR VALUES FROM ('2025-05-01') TO ('2025-06-01'); -- Indexes (on the parent — cascade to all partitions) CREATE INDEX idx_trades_user_time ON trades (user_id, created_at DESC); CREATE INDEX idx_trades_sym_time ON trades (symbol, created_at DESC); -- For P&L queries: use NUMERIC not FLOAT (exact decimal arithmetic) -- For mark price: store separately, never derived from trades table
| Level | Dirty Read | Non-repeatable Read | Phantom Read |
|---|---|---|---|
| Read Uncommitted | ❌ Possible | ❌ | ❌ |
| Read Committed (PG default) | ✓ Safe | ❌ Possible | ❌ |
| Repeatable Read | ✓ | ✓ | ❌ Possible |
| Serializable | ✓ | ✓ | ✓ Safe |
For trading systems: Use SERIALIZABLE or SELECT FOR UPDATE (row-lock) for balance deductions and margin checks. Never allow a race condition where two orders both pass a margin check simultaneously and both consume the same collateral.
BEGIN; SELECT available_margin FROM accounts WHERE user_id = 123 FOR UPDATE; -- locks this row until transaction ends -- check if margin sufficient UPDATE accounts SET available_margin = available_margin - :required WHERE user_id = 123; COMMIT;
Use a sorted map (price → queue of orders). Bids descending, asks ascending.
class OrderBook { constructor() { this.bids = new Map() // price → [orders] — sorted desc externally this.asks = new Map() // price → [orders] — sorted asc this.bidPrices = [] // sorted desc this.askPrices = [] // sorted asc } addOrder(order) { const book = order.side === 'buy' ? this.bids : this.asks if (!book.has(order.price)) { book.set(order.price, []) this._insertSorted(order.side, order.price) } book.get(order.price).push(order) // price-time priority: FIFO per level } getBestBid() { return this.bidPrices[0] } getBestAsk() { return this.askPrices[0] } getSpread() { return this.getBestAsk() - this.getBestBid() } match(incomingOrder) { const oppSide = incomingOrder.side === 'buy' ? this.asks : this.bids const prices = incomingOrder.side === 'buy' ? this.askPrices : this.bidPrices const trades = [] let remaining = incomingOrder.quantity while (remaining > 0 && prices.length > 0) { const bestPrice = prices[0] if (incomingOrder.side === 'buy' && bestPrice > incomingOrder.price) break if (incomingOrder.side === 'sell' && bestPrice < incomingOrder.price) break const queue = oppSide.get(bestPrice) while (queue.length > 0 && remaining > 0) { const resting = queue[0] const fillQty = Math.min(remaining, resting.quantity) trades.push({ price: bestPrice, quantity: fillQty, ... }) resting.quantity -= fillQty remaining -= fillQty if (resting.quantity === 0) queue.shift() } if (queue.length === 0) { oppSide.delete(bestPrice); prices.shift() } } return trades } }
// EMA formula: EMA(t) = price(t) * k + EMA(t-1) * (1 - k) // k = 2 / (period + 1) — smoothing factor // O(1) per tick, O(1) memory — no sliding window needed class EMA { constructor(period) { this.k = 2 / (period + 1) this.ema = null } update(price) { if (this.ema === null) { this.ema = price; return this.ema } this.ema = price * this.k + this.ema * (1 - this.k) return this.ema } } const ema20 = new EMA(20) ticks.forEach(t => ema20.update(t.close))
1. Clarify requirements — ask: scale (users, TPS), latency SLA, consistency vs availability tradeoff, read/write ratio
2. High-level components — sketch the boxes first, talk through data flow
3. Data model — schema, data types, partitioning strategy
4. Deep-dive bottlenecks — where is the hot path? how do you scale it?
5. Failure modes — what happens if component X dies? how do you recover?
Key design decisions to mention:
- Single-threaded per symbol: The matching engine for ETH-USDT is one process. No locks needed — sequential processing guarantees correctness. Scale by adding processes for new symbols.
- In-memory order book: All active orders in memory. PostgreSQL is the persistent store — write order on creation, update on fill/cancel. Recovery: replay Kafka log from last checkpoint to rebuild in-memory state.
- Kafka partitioned by symbol: Guarantees ordered delivery per symbol. Consumers (matching engine) process in sequence — no race conditions.
- Separate balance check from matching: Balance/margin pre-checked at ingress. Matching engine trusts it. Balance service deducts atomically after trade event.
- Mark price vs last trade price: Liquidation checks use mark price (index-based average of spot exchanges) to prevent manipulation. Stored and updated separately from the order book.
Core formula:
-- Unrealized P&L (long position) unrealized_pnl = (mark_price - avg_entry_price) × qty × contract_multiplier -- Realized P&L (on close) realized_pnl = (exit_price - avg_entry_price) × closed_qty × multiplier - fees
Architecture:
- Price tick source: Kafka topic
market.price.{symbol}updated every 100ms from aggregated index. - P&L service: Consumes price ticks. For each tick, fetches all open positions for that symbol from Redis (not DB — too slow). Computes P&L, writes result to Redis hash:
user:{id}:pnl:{symbol}. - WebSocket push: Separate WS service. Subscribes to Redis keyspace notifications. When P&L key updates, pushes delta to connected user.
- Cold path: PostgreSQL for historical P&L, end-of-day reports, audit trail.
Batch compute: one price tick triggers one Redis pipeline that updates all 1M users' P&L in a single round-trip. Push only if delta > threshold (e.g. 0.01% change) to avoid flooding WebSocket clients. Use Redis cluster — shard by user_id.
Two-path design — fast path + safe path:
Redis for available margin check. Atomic DECRBY. If result is negative, reject. Redis is ~0.1ms vs PostgreSQL ~5ms. Acceptable for 10k orders/sec.
Every 500ms, compare Redis margin with PostgreSQL ground truth. If drift detected (crash recovery, race), PostgreSQL wins. Correct Redis. Alert on large discrepancies.
Liquidation engine:
- Monitor margin ratio = maintenance_margin / account_equity continuously
- When ratio falls below threshold: place liquidation market order, cancel all open orders first
- Use mark price, not last traded price — prevents liquidation cascades from manipulated spot trades
- Insurance fund absorbs losses when liquidation price is worse than bankruptcy price
| Order Type | Behaviour | Use case |
|---|---|---|
| Market | Execute immediately at best available price | Fast entry/exit. Guaranteed fill, uncertain price. |
| Limit | Execute only at specified price or better. Goes to book if not immediately fillable. | Controlled entry. May not fill if price doesn't reach. |
| Stop-Market | When mark price hits stop price → place market order | Stop-loss: cut losses automatically |
| Stop-Limit | When price hits stop → place limit order | Stop-loss with price control. Risk: may not fill in fast market. |
| OCO | Two orders linked. First fill cancels other. | Take-profit + stop-loss simultaneously |
| Post-Only | Cancels if it would execute as taker. Always goes to book as maker. | Market makers ensuring they earn rebates, not pay fees |
| Reduce-Only | Can only reduce existing position, never increase | Prevents accidentally doubling position on stop-loss |
| IOC | Immediate or Cancel — fill what you can now, cancel remainder | Algorithmic trading needing partial fills acceptable |
Right to BUY the underlying at the strike price. Profitable if price goes UP above strike.
ITM Call: ETH at $3500, strike $3000 → has $500 intrinsic value
OTM Call: ETH at $3500, strike $4000 → pure time value only
Right to SELL at strike price. Profitable if price goes DOWN below strike.
ITM Put: ETH at $3500, strike $4000 → has $500 intrinsic value
OTM Put: ETH at $3500, strike $3000 → pure time value only
"We looked at the recent N-hour high and low as a range. We sold OTM call and put options (short straddle / strangle) — collecting premium from both sides. We placed buy orders above and below to define maximum loss (the hedge). As time passed and ETH stayed within range, options decayed to zero (theta decay) and we kept the premium. This is a defined-risk, theta-positive strategy."
| Greek | What it measures | Practical meaning |
|---|---|---|
| Delta (Δ) | Option price change per $1 move in underlying | Call delta: 0 to 1. Put delta: -1 to 0. ATM ≈ 0.5 |
| Gamma (Γ) | Rate of delta change per $1 move | High near expiry. Risk of delta swings accelerating |
| Theta (Θ) | Time decay per day | Always negative for buyers, POSITIVE for sellers. Your bot profits from this. |
| Vega (V) | Sensitivity to implied volatility | Rising IV increases option price. Sellers want low/falling IV. |
| Rho (ρ) | Interest rate sensitivity | Less relevant for short-dated crypto options |
Fixed expiry date. Price converges to spot at expiry. Settlement in cash or physical delivery. Basis = futures price − spot price, shrinks to zero at expiry.
No expiry. Can hold forever. Funding rate mechanism replaces convergence — keeps perp price anchored to spot index price.
Funding rate mechanics:
- Calculated every 8 hours (or 1 hour on some exchanges)
- If perp price > spot: funding is positive → longs PAY shorts (discourages buying, pulls price down)
- If perp price < spot: funding is negative → shorts PAY longs (discourages selling, pushes price up)
- Rate = (perp − spot) / spot × dampening factor
// Example: ETH at $3000, 10x leverage, 1 ETH long position Position value = $3,000 Initial margin = $3,000 / 10x = $300 // collateral deposited Maint. margin = $3,000 × 0.5% = $15 // minimum to stay open Liquidation price = entry − (initial_margin − maint_margin) / qty = $3000 − ($300 − $15) / 1 = $2,715 // If ETH drops to $2,715 → liquidation // Bankruptcy price = $3000 − $300 = $2700 (full loss) // Insurance fund covers gap between $2,715 and $2,700
Cross margin vs Isolated margin:
- Isolated: Only the allocated margin for this position is at risk. Liquidated position cannot drain your whole account.
- Cross: All account equity is available as margin. Harder to get liquidated but whole account at risk.
Mark price = index price (weighted average of ETH price across major spot exchanges) + EMA of basis (perp − spot). It is designed to be manipulation-resistant.
Why not use last traded price for liquidations?
- A large trader could place a large market sell on the exchange to briefly crash last traded price
- This could trigger thousands of stop-losses and liquidations
- Those liquidations cascade and crash the price further (cascade liquidation event)
- Mark price cannot be manipulated easily — it's anchored to global spot prices across exchanges
Situation — context, constraints, what was at stake
Task — what specifically you were responsible for
Action — what YOU did (not the team)
Result — quantified outcome
S: "At Analogyx, I was a solo engineer responsible for the entire automated trading platform — there was no separate backend or frontend team."
T: "I owned everything: architecture design, backend execution engine, real-time market data pipeline, and the monitoring dashboard. Also production support when it broke at 9am when markets opened."
A: "I designed a Flask + Celery distributed architecture for order execution. Built WebSocket microservices for high-frequency tick ingestion. Built a stop-loss monitoring system that ran in a separate Celery worker so it could never be blocked by order placement. React.js dashboard for live position monitoring."
R: "System ran live on NSE Options with real capital. Stop-loss system successfully eliminated execution slippage during high-volatility sessions — the original manual approach had missed stops twice."
S: "Our enterprise BI platform was running analytics reports that took 40 minutes to complete. Some clients had SLA requirements and were threatening to churn."
T: "I was asked to investigate — turned out nobody had profiled the backend before, it had grown organically."
A: "Used PostgreSQL EXPLAIN ANALYZE to identify N+1 query patterns — we were making hundreds of small queries inside loops. Rewrote with JOINs and CTEs. Added database-level indexes on the most frequent filter columns. Moved heavy aggregation from Python into SQL window functions. On the frontend, added pagination and lazy-loaded expensive chart data separately from the main report."
R: "Total execution time dropped from 40 minutes to under 90 seconds — a 96% reduction. Zero code infrastructure change, pure query and data access pattern optimization."
"I think of them as two separate layers. Correctness is non-negotiable for anything involving money — trade execution, balance mutations, P&L. For these, I use serializable transactions or row-level locking. I'd rather queue a few orders than let two orders both pass a margin check simultaneously and both draw from the same collateral."
"Speed matters for the presentation layer — the live price feed, the unrealized P&L display. These can be eventually consistent. I'd compute them in Redis, push over WebSocket, and reconcile with the ground truth in PostgreSQL every few seconds. A trader seeing their P&L 50ms later is fine. A trader being under-collateralized because we skipped a lock is not."
"In my trading platform at Analogyx, the stop-loss monitor ran in a completely isolated Celery worker. Even if the order placement worker was saturated, stop-losses would never miss their trigger."
S: "During high volatility on NSE options expiry day, the WebSocket tick ingestion service started dropping ticks — market data was 2-3 seconds stale. Live orders were executing on wrong prices."
T: "I was the only engineer on the system. Had to triage, communicate, and fix under live market conditions."
A: "First, halted automated order placement (safety first). Checked system metrics — the WebSocket reconnect logic had a bug where it wasn't exponentially backing off, and the repeated reconnects were exhausting TCP connections. Hot-patched the reconnect logic with exponential backoff + jitter. Also added a staleness check: if last tick timestamp is > 500ms, pause order placement and alert."
R: "Service recovered in under 3 minutes. Post-incident: added tick staleness monitoring as a first-class alert, and added integration tests for the reconnect path."
"Honestly, my primary backend language is Python — FastAPI and Flask. I've worked with Node.js on side projects and understand the event loop model well, but I won't pretend I have years of production Node.js. What I'd say is: the architecture patterns for async, event-driven, real-time systems are identical between Python asyncio and Node.js. The learning curve is syntax and ecosystem, not mental model."
"I built the same WebSocket tick ingestion patterns, the same async background worker patterns, the same rate limiting logic in Python that I'd be building in Node. I'd expect to be productive in Node.js within a couple of weeks, and I'd invest in getting there fast."
"What I bring that's rare is the combination of real production trading system experience plus full-stack ownership — that's harder to find than someone who knows Node.js but has never shipped a trading platform."
"The work I found most energizing at Analogyx was the trading platform — the combination of real-time systems, correctness under pressure, and the domain itself. I built an automated NSE options trading system and a crypto options bot on Delta Exchange. I understand order books, margin systems, and WebSocket market data feeds from having built them in production."
"Pi42 is one of the few places where that specific combination of full-stack + trading infrastructure is the core of the role, not a side project. That's the direction I want to go."
- What does the current matching engine stack look like — is it Node.js, Golang, something else? Any plans to migrate?
- How do you handle the correctness vs latency tradeoff in your margin check flow today?
- What's the biggest engineering challenge you're facing in the next 6 months?
- How is the team structured — is the full-stack role expected to be closer to product features or closer to exchange infrastructure?
- What does the on-call rotation look like for production incidents?
| Term | Definition |
|---|---|
| Order book | List of all pending buy (bid) and sell (ask) orders at each price level |
| Bid / Ask | Best bid = highest buy price. Best ask = lowest sell price. Spread = ask − bid. |
| Maker / Taker | Maker adds liquidity (limit order to book). Taker removes it (market order). Makers pay lower/negative fees. |
| Mark price | Manipulation-resistant price for margin/liquidation. Derived from spot index + EMA basis. |
| Liquidation | Forced position close when margin ratio hits maintenance threshold |
| Funding rate | Periodic payment between perp longs and shorts to keep perp price near spot |
| Open interest | Total number of unsettled contracts (measures market size, not volume) |
| Basis | Futures price − spot price. Positive = futures premium, negative = backwardation |
| Greeks | Delta (price sensitivity), Theta (time decay), Vega (vol sensitivity), Gamma (delta acceleration) |
| P&L | Unrealized P&L = open position gain/loss. Realized = locked-in after close. |
| Slippage | Difference between expected price and actual execution price (high in low liquidity) |
| Scalping | Trading strategy targeting small price changes for frequent small profits |
| Whale | Entity holding large enough amounts of crypto to influence market price |
| Concept | Definition / Importance |
|---|---|
| Matching Engine | The core "brain" that matches buy and sell orders based on priority rules. |
| Colocation | Placing servers in the same data center as the exchange to reduce latency to microseconds. |
| FIX Protocol | Financial Information eXchange — industry standard for electronic trading messages. |
| L1 / L2 / L3 Data | L1: Best bid/ask. L2: Full order book depth. L3: Individual order details (used by HFT). |
| Smart Order Router | Algorithm that splits large orders across multiple exchanges to get the best price. |
| Wash Trading | Illegal practice of buying and selling to oneself to create fake volume. |
| Concept | Python (asyncio) | Node.js equivalent |
|---|---|---|
| Async def / await | async def fn(): await something() | async function fn() { await something() } |
| Event loop run | asyncio.run(main()) | Automatic — starts on process start |
| HTTP server | FastAPI / uvicorn | Express.js / Fastify / NestJS |
| Background tasks | Celery workers | Bull / BullMQ (Redis-backed queues) |
| CPU offload | multiprocessing | worker_threads module |
| WebSocket | websockets / FastAPI WS | ws / socket.io / uWebSockets |
| ORM | SQLAlchemy | Prisma / TypeORM / Drizzle |
| Package manager | pip / poetry | npm / yarn / pnpm |
- Re-read your resume — every bullet should have a 2-min story ready
- Know the Delta Trading Bot strategy cold — high chance they ask about it
- Practice the order book implementation on paper (not IDE)
- Review Node.js event loop phases — will be asked
- Revise PostgreSQL EXPLAIN ANALYZE output reading
- Check Pi42.com — understand what pairs they offer, their fee structure, any recent news
- Prepare your "Node.js gap" answer — don't wait to be put on the spot
- Bring 3 questions for the interviewer (listed in Behavioral tab)