Interview guides · Automatiq
Automatiq Software Engineer Interview Guide
Automatiq Software Engineer interview guide: 3 reported rounds, Terraform debugging, API design, async JavaScript and behavioral prep with a 7-day plan.
The 60-second brief
Automatiq is described as a company that builds automation infrastructure behind transaction and distribution systems, replacing manual workflows with software. Candidates describe engineers there working on high-throughput API integrations, backend services, some frontend work and infrastructure configuration written as code. The requirements candidates list name JavaScript, TypeScript, Node.js or Go, cloud experience on AWS or GCP, and hands-on Terraform. Docker, Kubernetes and experience with high-throughput messaging are listed as extras. The engineering team is reported to be spread across several countries, including New York, Barcelona, Lahore and Brazil.
This guide is for candidates interviewing for the Software Engineer role at Automatiq. It covers the three reported rounds, the sixteen questions candidates report (API and distributed-systems design, Terraform and pipeline troubleshooting, JavaScript and language concepts, and behavioral prompts) and a seven-day plan that ends with a mock panel. It does not cover other roles at the company.
Candidates report three stages over roughly 3-5 weeks: Initial Screening, Technical Assessment and Technical Panel Interview. Reports describe live Terraform troubleshooting at the panel, and the order of the early screens is said to vary by location and hiring team.
Focus areas
- Terraform (Infrastructure as Code)
- Infrastructure as Code (IaC)
- Live Coding / Troubleshooting
Overview
The reported questions fall into four groups. Four are design questions: a high-throughput API integration that survives rate limits and transient failures, a monolith-to-microservices refactor, data consistency across distributed databases, and a rate-limiting middleware for an API gateway. Four concern infrastructure and automation: structuring Terraform for several environments, a failing deployment pipeline, secrets in CI/CD, and a syntax or state mismatch in a Terraform plan. Four are language and coding questions, including the JavaScript event loop and a live debugging exercise on an infrastructure configuration file. Four are behavioral. Preparation should therefore be split between systems reasoning you can talk through and configuration you can read and fix on a shared screen.
Candidates describe the work as practical: API integrations, services that move transactional data, and infrastructure kept in version control. Reports say the panel has asked candidates to troubleshoot Terraform live and that people have struggled with it. Treat that as the highest-risk item. Know the resource, variable, output and module blocks well enough to spot a missing argument, a bad reference or a dependency cycle, and know what the state commands do when the plan disagrees with reality.
The design questions share a theme: what happens when a dependency is slow, a request is repeated, or two stores disagree. Practise answering each with a simple working version first, then naming the single failure that justifies each added component (a queue, a cache, a lock, a second region). Candidates also report example prompts such as syncing inventory across external marketplaces and building a logging and monitoring pipeline for API latency, so the same habit applies there. Finish with behavioral stories that include the technical reasoning, since the reported prompts ask about disagreeing with a lead, shifting priorities, explaining a bug to non-engineers and code review.
The Automatiq interview process
Candidates report three stages over roughly 3-5 weeks. One description of the process gives a shorter span of two to three weeks, so treat the timing as approximate and confirm the schedule with the recruiter.
Initial Screening
The Initial Screening is a conversation about your background, what you are looking for, and your high-level technical experience. Candidates report that the recruiter screen can include a short technical Q/A on coding and infrastructure tools, and that remote or hybrid arrangements are discussed here. Approach it as a first technical conversation, not a formality: have a brief, specific account of your Terraform, cloud and JavaScript or Go work ready.
What to demonstrate
- Fit between your background, expectations and the role
- High-level experience with coding and infrastructure tooling
- Ability to explain technical work concisely
How to prepare
- Write a short walkthrough of your career that names the services you built, the cloud you ran them on (AWS or GCP) and any Terraform you wrote
- Prepare one-paragraph answers to basic questions you can expect from the listed stack: what Terraform state is, how the JavaScript event loop schedules callbacks, how you keep secrets out of a CI/CD pipeline
- Decide your location and remote-work constraints before the call so the conversation about arrangements is quick
Technical Assessment
The Technical Assessment is reported as a deeper technical evaluation that may be a specialised Q/A session or an external coding test. The format is not fixed, so prepare for both: timed coding in a language you know well, and spoken answers on the reported design and infrastructure topics. Candidates list a retry mechanism with exponential backoff and refactoring blocking code to run asynchronously as example scenarios in the coding area.
What to demonstrate
- Core coding proficiency, as an external test would benchmark it
- Working, tested code that handles edge cases
- Technical knowledge in the Q/A format, across design and infrastructure topics
How to prepare
- Implement a retry helper with exponential backoff, a cap and jitter in JavaScript or TypeScript, retrying only transient failures, and write tests for it with a fake timer
- Rewrite a synchronous loop that calls a network API so it runs concurrently with a limit, and explain what the event loop does while it waits
- Say aloud how you would keep data consistent across several databases in a real-time transactional system, and how you would build a rate limiter for a gateway
Technical Panel Interview
The Technical Panel Interview is reported as the main evaluation gate, with engineering leaders and prospective teammates. Candidates report live troubleshooting of Terraform configuration, such as syntax errors and dependency errors in a file, and design discussions where you justify trade-offs. Reports describe it as a collaborative working session, so ask clarifying questions and agree requirements before you write anything.
What to demonstrate
- Practical problem-solving on live code and configuration
- Reading and fixing Terraform configuration, including state and dependency problems
- Justifying design trade-offs aloud to several people
How to prepare
- Practise on a shared screen: take a Terraform file with planted errors (a missing required argument, a wrong reference, a dependency cycle) and fix it using validate, plan and the state commands
- Know how you would split Terraform into modules and per-environment variables, and how you would move manually created resources under version control
- Rehearse two designs end to end, the high-throughput API integration and the inventory sync across external marketplaces, each starting from a simple working version
Where candidates lose points
Treating Terraform as theory and freezing when handed a broken file on a shared screen
Practise out loud on files with planted faults: a missing required argument, a reference to an undeclared variable, a dependency cycle. Run validate, then plan, and say what each result tells you before changing anything.
Drawing a full distributed architecture for the rate limiter or API integration before a simple version exists
State a single-process version first, then add one component at a time and name the failure that justifies it, such as a shared counter store once the gateway runs on several nodes.
Writing a retry loop that retries every error forever, with no cap, no jitter and no idempotency story
Retry only transient failures such as timeouts, 429 and 5xx responses, honour a retry-after header when present, cap attempts and delay, add jitter, and say how a repeated request avoids being applied twice.
Describing JavaScript as multi-threaded or confusing asynchronous with parallel when asked about the event loop
Explain the call stack, the microtask queue for promises and the task queue for timers, and note that CPU-heavy work needs worker threads or another process because one slow callback blocks everything.
Giving behavioral answers with no decision, no evidence and no outcome, or casting the lead as the problem
For each reported behavioral prompt, write a story with the situation in two sentences, your reasoning, the evidence you used, what changed and what you would do differently.
Questions & practice
Explain the event loop in JavaScript and how asynchronous execution differs from multi-threaded processing.
Medium · Coding & Language-Specific Concepts
Approach
- Describe the model: JavaScript runs your code on a single thread with one call stack. The runtime (the browser, or libuv in Node) performs timers and I/O outside that stack and queues their callbacks to run once the stack is empty.
- Explain the two queues: after each macrotask (timer callback, I/O callback, event), the runtime drains the whole microtask queue (promise reactions, queueMicrotask) before taking the next macrotask. So console.log('A'); setTimeout(() => console.log('B'), 0); Promise.resolve().then(() => console.log('C')); console.log('D') prints A D C B.
- Contrast with threads. Async I/O gives concurrency without parallel execution of JavaScript, so no locks are needed around in-memory state. In a multi-threaded runtime, threads run in parallel on shared memory and need locks or atomics. Node does use a libuv thread pool for some work, such as fs, dns.lookup, crypto and zlib.
- Name the cost: CPU-bound work, like a large JSON parse or a tight loop, blocks every other request because nothing preempts it. The fixes are chunking the work, worker_threads (separate isolates that communicate by messages or SharedArrayBuffer), or a separate process.
- Point out the remaining race: there is no preemption between awaits, but other callbacks can run at every await. A read-await-write sequence on shared state can still interleave and lose an update.
Follow-up
- What is the difference between awaiting inside a for loop and using Promise.all, and when would you want each?
- How would you detect that a Node service's event loop is being blocked in production?
- Does setTimeout(fn, 0) run fn immediately? Why not?
How do you optimize memory usage and execution speed in a resource-constrained runtime environment?
Medium · Coding & Language-Specific Concepts
Approach
- Measure before changing anything. Set a target (p99 latency, peak RSS, startup time), then profile: a CPU profile or flame graph (node --cpu-prof), heap snapshots and allocation tracking. Optimize only the hot path the profile shows.
- Look for algorithmic wins first: replace a nested-loop lookup (O(n^2)) with a Map or Set (O(n)), remove repeated parsing or serialization, and batch I/O instead of making one call per item.
- Cut peak memory by streaming instead of buffering whole payloads (Node streams with pipeline, which handles backpressure), processing in fixed-size batches, and using typed arrays for large numeric data.
- Find leaks in the usual places: unbounded caches or global Maps, event listeners never removed, closures holding large objects, timers never cleared. Replace any cache with a bounded one (LRU with a size limit or TTL).
- Fit the runtime to the container. Set V8's old-space limit (--max-old-space-size) below the container memory limit so the heap is collected before the kernel OOM-kills the process. Remember that RSS also includes Buffers and native memory outside the JS heap. Confirm each change with a before-and-after benchmark.
Follow-up
- A container keeps getting OOM-killed, but heapUsed looks normal. Where else can the memory be?
- How would you find what is retaining objects using two heap snapshots?
- Give an example where you would trade memory for speed, and one where you would do the opposite.
What are the pros and cons of using strongly-typed languages versus dynamic languages for building rapid autom
Medium · Coding & Language-Specific Concepts
What are the pros and cons of using strongly-typed languages versus dynamic languages for building rapid automation tools?
Approach
- Untangle the terms first. Static versus dynamic is about when types are checked, and strong versus weak is about implicit coercion. Python is dynamic and fairly strong, JavaScript is dynamic with loose coercion, TypeScript adds static checking that is erased at runtime, and Go is statically typed and compiled.
- Static typing pros: errors surface before the job runs, refactoring is safer across many callers, editors can autocomplete, and function signatures document the interfaces. Cons: more upfront ceremony and a build step, which slows throwaway scripts.
- Dynamic typing pros: fast iteration, a REPL, little boilerplate, and good fit for glue code. Cons: a type error can wait in a rarely taken branch and only fail hours into a run or during an incident.
- Whichever language you pick, validate external data at the boundary. TypeScript types don't check JSON from an API or a config file at runtime, so use a runtime schema validator (for example zod or pydantic). Gradual typing (TypeScript, Python type hints with mypy) is a practical middle ground.
- Choose by the tool's lifespan, number of users and blast radius. A one-off script can stay dynamic. A shared automation CLI that touches production benefits from static types, and Go adds a single static binary that is easy to distribute.
Follow-up
- What can still go wrong at runtime in a fully type-checked TypeScript automation script?
- At what point would you rewrite a working Python or shell script in a typed language?
- How would you validate the shape of a configuration file before the tool acts on it?
Find version gaps and relay lag with window functions
Medium · window functions · event tables · frame semantics
outbox_event holds event_id, aggregate_type, aggregate_id, aggregate_version, event_type, payload, status ('pending','published','dead'), attempts, created_at, published_at. A projection is missing rows and you must decide whether the relay skipped events or the consumer dropped them. Write three queries over the last seven days: one listing every aggregate_id whose published aggregate_version sequence has a hole, one giving per-day counts with a running total, and one returning the newest published event per aggregate. For each, say where the window function is evaluated relative to WHERE and LIMIT. PostgreSQL 16.
Approach
- Gaps: compute lead(aggregate_version) OVER (PARTITION BY aggregate_id ORDER BY aggregate_version) in a subquery, then filter next_version <> aggregate_version + 1 in the outer query. Window functions are evaluated after WHERE, GROUP BY and HAVING and before the outer ORDER BY and LIMIT, so the predicate cannot sit in the same WHERE clause and PostgreSQL 16 has no QUALIFY.
- Say what the seven-day filter does to the answer: it truncates every partition, so the first row per aggregate has no predecessor inside the window and a hole spanning the boundary is invisible. Widen the window, or join to resource.version as the authority for the true maximum.
- Running total: SELECT date_trunc('day', created_at) AS d, count() AS n, sum(count()) OVER (ORDER BY date_trunc('day', created_at) ROWS UNBOUNDED PRECEDING). An aggregate inside a window call is legal because grouping runs before windowing. The grouping key is unique per row here so ROWS and RANGE agree, but write the frame anyway — over ungrouped rows with tied timestamps the default RANGE frame pulls in every peer row and the total jumps.
- Newest per aggregate: DISTINCT ON (aggregate_id) ... ORDER BY aggregate_id, aggregate_version DESC is the cheap PostgreSQL-only form when an index matches that order; row_number() OVER (PARTITION BY aggregate_id ORDER BY aggregate_version DESC) = 1 is the portable form and needs a subquery for the same evaluation-order reason as the gap query.
Follow-up
- Relay failover redelivers events. Does a duplicate break the gap query, and how would you detect one from this table alone?
- Turn the gap check into a continuous monitor rather than a query someone runs after an incident. What does it watch?
Write the update path that detects a concurrent edit
Medium · optimistic concurrency · transactional outbox · audit history
resource carries version INT NOT NULL DEFAULT 1. resource_revision holds revision_id, resource_id, version, actor_user_id, change_kind, patch JSONB, request_id, created_at with UNIQUE (resource_id, version). outbox_event holds aggregate_type, aggregate_id, aggregate_version, event_type, payload, status. A PUT carries the version the client read. Write the exact statements for the single transaction that applies the edit, records the revision and enqueues 'resource.updated', and give the handler's branch on zero affected rows. Then say what PostgreSQL 16 does under READ COMMITTED when two of these updates hit one row at once.
Approach
- One transaction, three writes, no network call inside it: UPDATE resource SET title = $3, version = version + 1, updated_at = now() WHERE resource_id = $1 AND tenant_id = $4 AND version = $2; then INSERT the resource_revision row at version $2 + 1; then INSERT the outbox_event row at the same aggregate_version. The event goes to a table rather than a broker because no transaction spans both.
- Branch on the affected-row count before doing anything else. Zero has three causes — stale version, wrong tenant, row gone — so re-read once and map to 409 carrying the current version, or 404 for an id outside the caller's tenant, which also stops the endpoint confirming that another tenant's id exists.
- State the engine behaviour instead of assuming it. Under READ COMMITTED the second UPDATE blocks on the row lock, and when the first commits PostgreSQL re-evaluates the WHERE clause against the newly committed row, so the version predicate now fails and the statement reports zero rows. Under REPEATABLE READ the identical collision raises SQLSTATE 40001 instead, so the handler must fold both shapes into one conflict response.
- Keep UNIQUE (resource_id, version) even though the predicate already serialises writers. It is what makes a lost update unwritable if any other path ever reaches the revision table, and it converts a logic bug into 23505 rather than into a silently missing history row.
Follow-up
- A client sends the version it read ten minutes ago and the resource has moved three versions. What is in your 409 so it can resolve the conflict without a full re-fetch?
- Two editors, two disjoint fields, no overlap. Does your answer still refuse the second write, and should it?
How would you design a high-throughput API integration that handles rate limits and transient network failures
Medium · Architecture & System Design
How would you design a high-throughput API integration that handles rate limits and transient network failures gracefully?
Approach
- Put a durable queue between producers and the integration workers, so bursts get absorbed and nothing is lost when the provider slows down. Cap concurrency per provider and run a client-side token bucket sized to the published quota, so I stay under the limit and don't find it by collecting 429s.
- Sort failures before retrying. Timeouts, connection resets, 429, 502, 503 and 504 are retryable. A 400, 401, 403, 404 or 422 is not, and retrying it only burns quota. When a Retry-After header is present, it wins over my own schedule.
- Use capped exponential backoff with full jitter: sleep = random(0, min(cap, base * 2^attempt)) with a maximum attempt count. Jitter stops all workers from retrying in lockstep after a shared outage.
- Make retries safe. GET, PUT and DELETE are idempotent by HTTP semantics, but POST is not, so I send an idempotency key derived from the business operation. After max attempts the message goes to a dead-letter queue, not an endless loop.
- Add a circuit breaker per provider: open it on a sustained error rate, fail fast while it is open, and let a half-open probe close it. Emit metrics for 429 rate, retry count, queue depth and p99 latency.
Follow-up
- Ten workers on different hosts share one provider quota. Where does the token bucket live, and what happens when that store is down?
- The provider doesn't support idempotency keys and a POST timed out. How do you find out whether it was applied before you retry?
- How do you keep retries from turning a partial provider outage into a retry storm?
Describe a scenario where you had to refactor a monolithic service into distributed microservices. What challe
Medium · Architecture & System Design
Describe a scenario where you had to refactor a monolithic service into distributed microservices. What challenges did you face?
Approach
- Frame it as a strangler-fig migration: put a routing layer or facade in front of the monolith, pull out one bounded context at a time, and send its traffic to the new service while everything else stays in place.
- Choose the first seam by data ownership and change frequency, not by code layout. A module that owns its own tables and changes often is a good first candidate. One that joins across half the schema is not.
- Name the hardest problem directly: splitting the shared database. Cover how you moved data ownership (change data capture or a transactional outbox instead of ad hoc dual writes) and how you replaced cross-boundary joins with API calls or a read model.
- Cover what in-process calls turn into over a network: timeouts, retries, partial failure, extra latency and versioned contracts. Say what you added for it, such as distributed tracing, contract tests and independent deploy pipelines.
- Close with a measured outcome (deploy frequency, incident count, latency) and one thing you would not split again, which shows you know when microservices aren't worth their cost.
Follow-up
- How did you handle a business transaction that used to be one database commit and now spans two services?
- How did you run old and new paths side by side and prove the new service returned the same results?
- Which part of the system would you have left in the monolith, and why?
How do you ensure data consistency across multiple distributed databases in a real-time transactional system?
Medium · Architecture & System Design
Approach
- Start by listing the invariants. Some must never be violated, such as never selling the same unit twice or never debiting without a matching record. Others can tolerate eventual consistency, such as reporting views or caches. Strong consistency is only worth paying for on the first group.
- Keep each invariant inside one database's local ACID transaction where possible, by choosing service boundaries and partition keys so the rows that must change together live together.
- For cross-database workflows, prefer a saga (orchestrated or choreographed) with a compensating action for each step over two-phase commit. 2PC holds locks across systems and blocks if the coordinator fails while participants are prepared.
- Publish changes with a transactional outbox: the business row and the event row commit in one local transaction, and a relay publishes the events. Delivery is at-least-once, so consumers deduplicate by event id and apply changes idempotently.
- Handle concurrent writers with optimistic concurrency: UPDATE ... SET version = version + 1 WHERE id = ? AND version = ?, then treat zero affected rows as a conflict. Back it all with a reconciliation job that detects drift between stores and alerts on it.
Follow-up
- A compensating action in the saga fails too. What state is the system in, and how does it recover?
- How do you guarantee that events for the same entity are processed in order?
- When would you accept two-phase commit despite its costs?
Design a rate-limiting middleware for an API gateway serving millions of requests per day.
Medium · Architecture & System Design
Approach
- Turn the load into a rate first: one million requests a day averages about 11.6 per second (1,000,000 / 86,400). The design is driven by peak burst, the number of distinct keys and the latency budget the middleware adds, not by the daily total.
- Compare algorithms. A fixed window is cheap but allows up to twice the limit across a window boundary. A sliding-window log is exact but stores one entry per request. A sliding-window counter weights the previous window to approximate it in O(1) memory. A token bucket allows bursts up to its capacity and then holds a steady refill rate.
- Key limits by API key or user, plus route, falling back to IP for anonymous traffic. Keep counters in a shared store such as Redis so every gateway node sees the same count. Make the update atomic with a Lua script or SET NX EX followed by INCR, because a separate INCR and EXPIRE can leave a key with no TTL if the process dies between them.
- Reject with HTTP 429 and a Retry-After header, and expose the remaining quota in response headers so clients can back off.
- Decide fail-open or fail-closed for when the counter store is unreachable, per route. To cut the per-request round trip, an in-process local bucket can absorb most checks and sync to the shared store, at the cost of slight over-admission.
Follow-up
- Redis goes down for five minutes. What does the gateway do, and why did you choose that?
- How would you support different limits per customer tier without redeploying the gateway?
- The gateway runs in two regions. Do you share counters across them or split the quota, and what does each cost?
How do you structure and modularize Terraform configurations for multi-environment deployments?
Medium · Infrastructure & Automation
Approach
- Split reusable modules (network, database, service) from thin per-environment root configurations, for example envs/dev, envs/staging and envs/prod. Each root calls the same modules with its own variable values and outputs.
- Give every environment its own remote state with locking, and its own credentials, so a dev apply can't touch prod state. Workspaces share one configuration and backend. They suit near-identical copies but isolate credentials and blast radius less well than separate roots.
- Pin versions: Terraform and providers in required_version and required_providers, commit the .terraform.lock.hcl file, and reference modules by tag or registry version so prod can stay on a module version that has already run in dev.
- Split state by blast radius and change rate: networking in one state, data stores in another, apps in a third. Pass values between them through outputs read with terraform_remote_state or data sources, so one plan never covers everything.
- Run terraform fmt -check, terraform validate and a plan on every pull request, and apply only after review, with a manual approval gate for prod.
Follow-up
- How do you roll a module change through dev, staging and prod without prod picking it up early?
- You need to move a resource from one module or state to another without destroying it. How?
- When would you choose workspaces over separate directories?
What are the best practices for managing secrets and sensitive environment variables within a CI/CD workflow?
Medium · Infrastructure & Automation
Approach
- Never commit secrets. Keep them in the CI platform's secret store or an external manager (Vault, AWS Secrets Manager, GCP Secret Manager), and add secret scanning in pre-commit and CI (for example gitleaks) to catch slips.
- Prefer short-lived credentials over stored keys: OIDC federation from the CI job to a cloud role (for example, a GitHub Actions job assuming an AWS IAM role) means there is no long-lived access key to leak or rotate.
- Apply least privilege per environment: separate credentials for dev and prod, with prod secrets only available to protected branches or environments that need approval. Don't expose secrets to builds from untrusted forks, and be careful with pull_request_target in GitHub Actions.
- Don't rely on log masking. It only redacts exact matches, so a base64-encoded or split value can still leak. Avoid echoing secrets, set -x in shell steps and verbose debug flags in jobs that hold credentials.
- Remember infrastructure state. Terraform writes many secret values to state in plain text, and sensitive = true only hides them in CLI output. Encrypt the remote state backend, restrict who can read it, and plan for rotation with an audit trail.
Follow-up
- A token shows up in a public CI log. What do you do first, and in what order?
- How do you rotate a database password that running services use, without downtime?
- How do secrets reach the running application, as opposed to the build job?
Walk through a time when you had to troubleshoot a failing deployment pipeline. How did you isolate the root c
Medium · Infrastructure & Automation
Walk through a time when you had to troubleshoot a failing deployment pipeline. How did you isolate the root cause?
Approach
- Pick a real incident and name the failing stage exactly (dependency install, build, tests, image push, apply or deploy) along with the actual error, not just 'the pipeline was red'.
- Show the isolation step: diff the failing run against the last green run on commit range, dependency lockfile, runner or base image tag, tool versions, and secrets or environment variables. Most pipeline breaks come from one of those changing.
- Separate flaky from deterministic by re-running the same commit. Then reproduce locally in the same container image. If the cause is a commit, bisect the range (git bisect) instead of reading diffs by guesswork.
- Separate the unblock from the fix: revert or pin to get main green for everyone, then fix the root cause, for example pinning a floating image tag or dependency, or correcting a cache key.
- End with the guard you added so it can't silently recur, such as pinned versions, a lockfile check or an alert, and with the measurable effect, for example how long main was blocked before and after.
Follow-up
- What evidence told you it was deterministic and not a flaky test or runner?
- What did you do for the rest of the team while main was broken?
- What change stops the same class of failure from reaching the pipeline again?
How would you debug a syntax or state mismatch error in a complex Terraform plan?
Medium · Infrastructure & Automation
Approach
- Classify the error first. Syntax and type errors appear in terraform fmt and terraform validate with a file and line number. Init and provider errors point to terraform init or the lock file. State mismatches only appear in plan or apply.
- For drift, where infrastructure changed outside Terraform, run terraform plan -refresh-only to see what differs. Then either bring the code in line or accept the update into state. Use lifecycle ignore_changes only for attributes another system legitimately owns.
- If a rename or move in the code makes plan want to destroy and recreate a resource, add a moved block (or use terraform state mv) so the address changes without touching real infrastructure. If apply fails with 'already exists', bring the object in with an import block or terraform import.
- Inspect state directly with terraform state list and terraform state show <address>. Read the plan for '# forces replacement' markers to find the attribute triggering the replacement. Use terraform graph to find dependency cycles.
- For a stale state lock, confirm no other run is active before using terraform force-unlock with the lock ID. If messages are unclear, rerun with TF_LOG=DEBUG. Add prevent_destroy on stateful resources so a bad plan can't delete them.
Follow-up
- After a module refactor, plan wants to destroy and recreate the production database. How do you stop that safely?
- Someone changed a security group in the cloud console. How do you detect it and decide which version wins?
- validate passes but apply fails. What kinds of error only show up at apply time?
Walk through a live debugging exercise where you need to identify and fix syntax errors in an infrastructure c
Medium · Coding & Language-Specific Concepts
Walk through a live debugging exercise where you need to identify and fix syntax errors in an infrastructure configuration file.
Approach
- Let the tools find errors before you read line by line. terraform fmt -check reports parse errors, and terraform validate (after terraform init, or terraform init -backend=false when no backend is available) reports undeclared references, missing required arguments and type mismatches, each with a file and line number.
- Fix the first error, then rerun. A missing closing brace or an unterminated string often causes a cascade of later errors that disappear once the first one is fixed. Narrate each fix as you make it.
- Check the common HCL mistakes: unbalanced braces or brackets, a map attribute written as a block (tags { ... } instead of tags = { ... }), references to undeclared variables or misspelled resource addresses, a string where a list is expected, duplicate resource names, and each.key used with count instead of for_each.
- Then check dependency errors. A cycle means two resources reference each other, and terraform graph helps trace it. Prefer the implicit dependencies created by references over depends_on, and use depends_on only for dependencies Terraform cannot see.
- Once validate passes, run terraform plan to catch what only the provider can check, such as invalid argument values, credentials and quota. For YAML pipeline files, check indentation and tabs, which YAML forbids for indentation, with a linter such as yamllint.
Follow-up
- validate passes but plan fails. What kinds of problems explain that?
- How would you keep this class of error from reaching the main branch?
- The file has a dependency cycle between two resources. How do you break it without losing the relationship?
Suggested practice checklist
The plan follows the reported loop: fundamentals for the screen, then Terraform and pipelines, async coding, design, and consistency, and a final day of behavioral stories and a mock panel. Questions are attached to the days that practise them.
Day 1 — Screening fundamentals and language trade-offs
- Write a short career walkthrough that names your services, cloud platform and any Terraform or Node.js work
- Write three-sentence answers on strongly typed versus dynamic languages for rapid automation tools, with one example from your own work
- Decide your remote-work and location constraints and list questions to ask the recruiter about the loop
Deliverable: A written career walkthrough, a three-sentence answer on typed versus dynamic languages, and a list of recruiter questions.
Day 2 — Terraform structure and live debugging
- Lay out a Terraform repository with a shared module and dev, staging and prod environments that differ by variables and remote state
- Take a config and plant five faults (missing argument, bad reference, undeclared variable, dependency cycle, state drift) and fix each using validate, plan and the state commands
- Explain aloud how you would debug a syntax or state mismatch error in a complex plan
Deliverable: A repository layout sketch, a list of five planted faults with the command and fix for each, and a spoken answer on plan debugging.
Day 3 — Pipelines and secrets
- Write a triage sequence for a failing deployment pipeline: what changed, which stage failed, how to reproduce locally, how to bisect
- Outline how secrets reach a CI/CD job: where they are stored, how they are scoped per environment, how they are rotated and kept out of logs
- Write a real failed-pipeline story from your own experience in a situation, action, result outline
Deliverable: A pipeline triage checklist, a secrets handling outline and one written troubleshooting story.
Day 4 — Async coding and runtime performance
- Implement retry with exponential backoff, cap and jitter in TypeScript, with tests using a fake timer
- Refactor a blocking synchronous block into async code with limited concurrency and explain event loop ordering of promises and timers
- Write down three memory and speed measures for a constrained runtime, such as streaming instead of buffering and avoiding retained references
Deliverable: A tested retry helper, a refactored async example with an event loop explanation, and a three-item optimisation list.
Day 5 — API integration and rate limiting design
- Design the high-throughput API integration: queue, worker pool, idempotency keys, retry policy, and handling of rate limit responses
- Design the gateway rate limiter: compare token bucket and sliding window, choose where counters live, and say what happens when the counter store is down
- Rehearse each design aloud, beginning with the simplest working version
Deliverable: Two design diagrams with a spoken walkthrough of each, including a list of failure cases.
Day 6 — Consistency and the monolith split
- Compare options for consistency across databases: single writer, transactions within one store, sagas, outbox pattern, and where eventual consistency is acceptable
- Outline a monolith-to-services refactor story: what you extracted first, how you kept both running and what went wrong
Deliverable: A one-page comparison of consistency approaches, two completed practice exercises and a refactor story outline.
Day 7 — Behavioral stories and a mock panel
- Write four stories for the disagreement, prioritisation, bug communication and code review prompts, each with the evidence and the outcome
- Run a mock panel with a friend: one design prompt, one Terraform fault to fix on a shared screen, clarifying questions before starting
- Review where you lost the thread and rewrite one answer
Deliverable: Four written stories, a recorded or notes-based mock panel and one rewritten answer.
Behavioral stories to rehearse for the panel
The reported behavioral prompts cover disagreeing with a lead or CTO, prioritising when requirements shift, explaining a bug to non-technical stakeholders and approaching code review. Each story should include a technical decision, the evidence behind it and what happened afterwards. Candidates describe a team spread across time zones, so include how you communicated in writing where you can.
- Tell me about a time you moved a manual process into automation and how you showed it was safe.
Describe a time when you disagreed with a technical decision made by a team lead or CTO. How did you resolve t
Medium · Behavioral & Collaboration
Describe a time when you disagreed with a technical decision made by a team lead or CTO. How did you resolve the conflict?
Approach
- Choose a real technical decision, such as build versus buy, a migration order or a release approach, where the lead's choice was reasonable and the stakes were concrete.
- State the lead's position fairly first, then your concern and the evidence behind it: a benchmark, a prototype, incident history or a one-page comparison of options.
- Show how you raised it: privately with the lead first, then in the design review or written proposal for the wider team. Distributed teams rely on written arguments, so mention a document.
- Give the outcome honestly, whether your view won, partly won, or you committed to the decision, and what you measured afterwards.
- The mistake that sinks this story is making the lead a villain or claiming you were right with no evidence. Show that you stayed collaborative.
Follow-up
- What would you have done if the lead still said no?
- If the decision had turned out right, what would you have learned?
- How would you handle the same disagreement asynchronously across time zones?
How do you prioritize your tasks when working in a fast-paced environment with shifting product requirements?
Medium · Behavioral & Collaboration
Approach
- Open with a concrete method, not a slogan: list the work, mark what is committed, what is urgent, and what is speculative, and rank by user or business impact against cost of delay.
- Give one real example where two requests collided or a requirement changed mid-build, and say what you stopped, what you shipped as a smaller slice and who you told.
- Show that you renegotiate scope in the open: you tell the product manager what moves later because of the new request, rather than silently absorbing it.
- Protect reliability work such as on-call fixes, tests and pipeline health, and say how you stop technical debt from being deprioritised every time.
- The mistake is answering that you work harder or do everything. Show a trade-off you made and its cost.
Follow-up
- What do you do when two stakeholders each insist their request is first?
- How do you handle a requirement that changes after you are half done?
- How do you keep a backlog of refactoring work from being pushed out indefinitely?
Talk about a challenging technical bug you resolved. How did you communicate the impact and the fix to non-tec
Medium · Behavioral & Collaboration
Talk about a challenging technical bug you resolved. How did you communicate the impact and the fix to non-technical stakeholders?
Approach
- State the symptom in terms of impact, for example duplicate or delayed updates or failed deployments, and who was affected, before any technical detail.
- Walk the debugging path in order: reproduce, narrow with logs or bisection, find the root cause, fix, and add a regression test or alert so it cannot return silently.
- Spend half the answer on communication: the first message you sent while still investigating, the update cadence, and the final explanation without jargon.
- Give a line you actually said in first person, such as: I told support that some customers saw updates twice, that it was limited to one integration, and that a fix was in review.
- The mistake is a tour of the stack trace with no stakeholder. Another is hiding uncertainty; say what you did not yet know.
Follow-up
- How would you explain the root cause to an executive in two sentences?
- How did you decide when to tell stakeholders before you had the root cause?
- What changed in your tests, monitoring or process so it could not recur?
How do you approach code reviews, and what steps do you take to ensure high-quality feedback?
Medium · Behavioral & Collaboration
Approach
- Describe your order of reading: the description and tests first, then correctness, failure handling and edge cases, then naming and structure. For infrastructure changes, say you read the Terraform plan output as well as the diff.
- Explain how you make comments useful: mark blocking issues apart from optional nits, give the reason, suggest an alternative or snippet, and ask a question when you are unsure of intent.
- Say what you automate so humans review logic, such as formatters, linters and CI checks, and how you keep your own pull requests small and well described.
- Bring one specific review where you caught something real, for example an unbounded retry, an unhandled promise rejection or a hardcoded secret, and what the author did with the feedback.
- With a team across time zones, write comments that stand alone so the author is not blocked waiting for a call. The mistake is a generic answer about being thorough and kind.
Follow-up
- How do you review a very large change you cannot ask to be split?
- What do you do when the author pushes back on your comment?
- How would you review a change that rewrites a Terraform module used by several environments?