Automattic Code Test: Debugging an Existing Plugin and Responding to Review
Quick Overview
Understand the official practical-test boundary, repair an original plugin selector, prove the regression checks, and respond constructively when review exposes another defect.
For the Automattic code test, prepare to improve unfamiliar code and explain your decisions in writing. A useful rehearsal starts with an existing behavior, exposes a small defect, adds a regression test, and then responds to a reviewer who finds something your first patch missed. Building a large new application is not the only way to demonstrate engineering judgment.
This guide uses an original JavaScript selector from a small plugin-style project. Start with PracHub's pull-request review and improvement exercise to practice tracing code beyond the changed lines, then work through the repair below.
Evidence boundary: Automattic's official hiring page establishes the broad practical-test format. A historical candidate account supplies context, not current rules. All code, fixtures, review comments, and expected outputs here are original practice material, not an Automattic assessment solution.

What Automattic says about the practical test
Official information: Automattic describes a paid practical project for some roles. Engineering applicants work through a code test with an assigned buddy who reviews their work and answers questions. The page names modifications to existing code, such as WordPress plugins, and emphasizes problem solving, design, and communication rather than specific platform knowledge. How Automattic hires
The same page describes the later trial separately as a paid project of 5–40 hours. Its indicative practical-test schedule is not your personal deadline. Confirm the scope, delivery method, payment terms, and timing in your invitation; do not combine the two stages into one assumed assignment.
Historical candidate report: Mehedi Hasan Masum's February 2022 account describes an unfamiliar repository, PHP and JavaScript changes, unit tests, and a further fix after review. Its older payment description differs from the current official page. Use the account to understand collaborative iteration, not to predict today's terms. Candidate account
Two independent same-cycle reports were not established for this review. The sections below therefore teach an original preparation method rather than a universal current round sequence or grading rubric.
Read enough of the plugin to locate the contract
Before editing, find the entry point, the function that owns the behavior, its callers, and the nearest tests. Record the supported runtime and the documented test command. Run the baseline before attributing every existing failure to your changes.
Official WordPress context: Scripts are normally registered or enqueued through the appropriate WordPress hooks. The plugin's PHP integration and its browser-side JavaScript are different layers; a pure JavaScript test cannot prove that the script was correctly loaded on the intended page. WordPress script enqueuing
For our original exercise, a plugin asset selects published article cards for a topic. The rendering layer consumes the selected cards, while another component still holds the original array. That second consumer makes input ownership part of correctness even if the visible card list looks right.
Assume each card has a unique integer ID, a finite numeric day, a boolean publication flag, and an exact topic string. The selector should return at most the requested number of matching published cards, newest first. It must not reorder the caller's array. These are exercise requirements, not WordPress-wide data conventions.
Ask about missing requirements before silently choosing behavior. If two cards have the same day, which comes first? Does zero mean no cards or a default number? Questions that change an output or test are more useful than asking for an unrestricted tour of the codebase.
Reproduce a filtering bug with five cards
Use this complete original fixture. The array's initial order is intentional, because a later test will check whether the caller's input is preserved.
| ID | Day | Published | Topic |
|---|---|---|---|
| 2 | 9 | true | guide |
| 1 | 12 | false | guide |
| 3 | 11 | true | news |
| 4 | 10 | true | guide |
| 5 | 8 | true | guide |
The request is: select two published guide cards. The correct IDs are [4, 2]. The newest item is a draft, and the next item belongs to another topic, so neither should consume one of the two matching-card slots.
Here is the defective selector:
function selectCards(items, tag, limit = 2) {
return items.sort((a, b) => b.day - a.day)
.slice(0, limit)
.filter(x => x.published && x.tag === tag);
}
It sorts all cards, takes IDs 1 and 3, and then removes both. The returned array is empty even though three matching published cards exist. This input distinguishes “two matching cards” from “matching cards among the first two items.”
An assertion expecting [4, 2] proves the defect more clearly than a screenshot showing an empty area. State the mismatch in behavior before describing the line you plan to change. That gives your reviewer an independent reason to agree with the repair.
Make a first patch, then inspect what it still breaks
Moving the filter before slice repairs the empty-result symptom. But a patch that still calls items.sort(...) first continues to reorder the array supplied by the caller. Another component may now see a different order without having requested a change.
JavaScript behavior: Array.prototype.sort() sorts its array in place. Filtering into a new array before sorting avoids changing the original array's order, though the card objects themselves remain shared references. MDN Array.sort reference
For the exercise, snapshot the input before calling the selector and compare it afterward. This is a separate assertion from checking the returned IDs. A patch can satisfy one contract while violating the other.
Do not respond by deep-cloning every object unless the contract requires independent objects. Here the function selects references and never changes card fields. Creating a new outer array is sufficient for the stated ownership boundary. Explain that limitation so a future caller does not assume edits to returned objects are isolated.
The important review habit is to inspect side effects outside the return value. A formatter, cache, shared object, event listener, or global setting can remain broken even after the visible result becomes correct.
Respond to a reviewer with a testable revision
Original reviewer comment: “The returned IDs are now correct, but this still sorts the input array. Another view owns that ordering. Also, please define the tie order and reject invalid limits rather than silently accepting a negative slice.”
A useful response identifies the oversight and names the verification you added. “Thanks, fixed” forces the reviewer to reconstruct your reasoning. A defensive explanation of why the original test did not mention mutation does not resolve the ownership problem.
Original author response: “I missed that the caller retains the input array. I moved filtering before sorting so the selector sorts a new array, added a no-mutation regression, and clarified ascending ID as the tie-breaker. Negative and fractional limits now raise an error; zero remains a valid request for no cards.”
Tie-breaking is a clarified exercise requirement, not an arbitrary production change. If the reviewer has not established that policy, ask before implementing it. A new deterministic order can be a behavior change for existing consumers even when it looks cleaner.

Read the final selector as a set of promises
The revised function makes its responsibilities visible:
function selectCards(items, tag, limit = 2) {
if (!Number.isInteger(limit) || limit < 0) {
throw new RangeError(
"limit must be a nonnegative integer"
);
}
return items
.filter(x => x.published && x.tag === tag)
.sort((a, b) => b.day - a.day || a.id - b.id)
.slice(0, limit);
}
For the supplied fixture, it returns IDs [4, 2] and preserves the original ID sequence [2, 1, 3, 4, 5]. With two matching cards on the same day, IDs 8 and 6 return as [6, 8]. With a limit of zero, it returns an empty array.
The comparator relies on the stated numeric-field assumptions. This is not a complete validator for arbitrary API payloads. If data crosses an untrusted boundary, determine which layer validates shape and types; do not imply that a limit check validates the entire record.
For n input cards and m matches, filtering scans the input and sorting operates on the matches. It uses additional array storage rather than sorting the caller's array in place. Do not redesign it around a heap unless data size or measured performance justifies the extra complexity.
A small patch should make the behavior easier to inspect. Adding a general query language or unrelated rendering framework would make this particular repair harder to review.
Choose regression tests that can reject a bad patch
Executed practice checks: The final JavaScript implementation passed five test groups covering filtering before limiting, unchanged input order, the ID tie-breaker, empty and unmatched inputs with zero limit, and rejection of negative or fractional limits. The test runner used Node's built-in test API. Node test runner
The original and intermediate implementations are retained as negative examples. The original fails the matching-card assertion. The intermediate repair returns the expected cards but still fails the no-mutation check. Keeping those results demonstrates that the tests discriminate between the two defects.
Do not compute the expected answer by calling the production selector again. Expected IDs are small enough to derive directly from the fixture. Likewise, an input-preservation check needs a snapshot taken before the call, not a reference to the same array afterward.
These are unit-level checks of an original plugin asset. The minimal PHP enqueue skeleton was not executed inside WordPress, so this article does not claim a fully validated plugin installation. Actual integration review must still cover activation, page scoping, asset loading, rendering, and any relevant server-side boundaries.
That distinction is useful in an assessment submission: state exactly what passed, what you inspected, and what remains untested. “All tests pass” is incomplete if the only tests exercise a pure helper while the visible feature depends on other layers.
Package the work so review can continue asynchronously
Your submission should let another engineer reproduce the problem and understand the repair without arranging a call. Give the initial failing input, the intended behavior, the changed responsibility, the test command, and the remaining limitations.
Original submission note: “The card selector limited the unfiltered list and could return no matches despite eligible cards. Filtering now precedes sorting and limiting. Sorting occurs on the filtered array, preserving caller order. Tests cover the original empty result, mutation, ties, and limit boundaries. PHP activation and browser integration are not covered by this unit run.”
When review arrives, address each substantive point and distinguish completed changes from open questions. If a suggestion expands the task, explain the trade-off and ask whether it belongs in this patch. Preserve a coherent diff instead of mixing the repair with unrelated formatting or dependency upgrades.
Follow the actual invitation's rules for documentation, external help, and AI tools. A role page's general enthusiasm for tools is not permission to use them during an assessment. If assistance is allowed, you still need to understand and verify every submitted change.
Five PracHub questions for the next rehearsal
These records develop review, debugging, testing, and feedback skills across companies. They are not Automattic test questions or evidence of a guaranteed assessment format.
| PracHub question | Practice focus |
|---|---|
| Review and Improve Four AI-Assisted TypeScript Pull Requests | Trace intent and surrounding code before improving a patch. |
| Debug and Harden a Ticketing Backend API | Connect a reproduced bug to a focused repair. |
| Respond Constructively to Negative Feedback | Turn a criticism into observable improvement. |
| Implement a simple service with tests | Separate core behavior from clarified extensions. |
| Validate Unit-Test Coverage and Identify Missing Scenarios | Identify what a passing test does not establish. |
Use the pull-request improvement exercise to write one evidence-backed review comment and implement its smallest justified correction. Include the test that would reject your previous version.
Sources and Further Reading
- Automattic: How We Hire — practical test, collaboration, and the separate trial stage.
- Mehedi Hasan Masum: Interview experience at Automattic — historical 2022 candidate account.
- WordPress: Server Side PHP and Enqueuing — script-loading integration context.
- MDN: Array.prototype.sort — in-place sorting behavior.
- Node.js: Test runner — the API used for the original unit checks.
Sources checked September 9, 2026. Your own invitation controls the assignment, timing, compensation, and permitted tools.
Comments (0)