Articles
GitLab Software Engineer Interview Guide 2026: Coding, Remote Collaboration, Values, and Timeline
Prepare for GitLab software interviews with a reproducible merge-request mock, prioritized comments, a tested patch and an asynchronous handoff.
GitLab’s published technical-interview format for applicable roles centers on reviewing a self-contained merge request and improving code with an interviewer. Prepare the whole work sample: actionable review comments, a bounded patch, tests and an asynchronous handoff. The original pagination mock below makes that workflow reproducible.
Use code-review practice to rehearse prioritization, then distinguish a correctness defect from a naming suggestion in this guide’s fixture.
Confirm which published format applies
GitLab’s technical handbook describes advance merge-request review and a live review-and-code session for roles using that format. Check your invitation for preparation deadlines, the stack, permitted tools and the live schedule. A job-family example does not establish a single sequence for every specialty. GitLab technical interview handbook.
For backend roles, practice the framework, data access and service contracts in the posting. For frontend roles, add browser behavior, UI state, accessibility and the actual test setup. A Python fixture can teach review reasoning, but it should not replace hands-on work in a required stack.
The handbook and GitLab’s code-review guidelines are useful because they connect feedback to an implementable change. Keep comments concise and specific: the failure case, its impact and the proposed direction. Do not use a stylistic preference to obscure a defect that changes the output. GitLab code review guidance.
A reproducible review-and-improve mock
This original Python exercise models the work of reviewing a small change, explaining a defect and making a bounded fix. It is not a GitLab interview question. Give yourself 90 minutes and work in a local scratch file. The fixture contains public, fictional records.
The ticket says: “Return up to limit issues for the requested tenant, after after_id, ordered by increasing ID. Inputs have unique integer IDs, a tenant string and a title. Do not mutate the supplied list. The cursor is an exclusive issue ID, not an array index.” The proposed implementation is:
def list_issues(issues, tenant, after_id, limit):
page = sorted(issues, key=lambda issue: issue["id"])[after_id:after_id + limit]
return [issue for issue in page if issue["tenant"] == tenant]
Before reading the patch, identify an example that disproves the implementation. A tenant with IDs 10 and 30 should receive ID 30 after cursor 10. Slicing from position 10 returns nothing in a four-record list. Filtering after slicing can also make another tenant’s records consume the page allowance. Both are correctness problems. This function does not itself implement authentication; the service calling it must obtain a trusted tenant context rather than accept an arbitrary user-supplied tenant as authorization.
Write review comments that help someone act
Blocking correctness comment: “after_id is an issue ID, but the slice interprets it as a list index. Could we select records with id > after_id, restrict them to this tenant, and then apply the limit? For tenant a, IDs 10 and 30 with cursor 10 should return 30.”
Blocking test gap: “Please add an interleaved two-tenant fixture. It should establish that another tenant cannot consume this tenant’s page capacity and that the input remains unchanged.”
Non-blocking maintainability comment: “Could we name the intermediate collection eligible? That would make the order of filtering, sorting and limiting easier to review.”
These comments separate behavior that must change from a naming preference. Avoid a long list of stylistic remarks that hides the defect.
The bounded patch and checks
def list_issues(issues, tenant, after_id, limit):
if isinstance(limit, bool) or not isinstance(limit, int) or limit < 0:
raise ValueError("limit must be a non-negative integer")
eligible = [
dict(issue) for issue in issues
if issue["tenant"] == tenant and issue["id"] > after_id
]
eligible.sort(key=lambda issue: issue["id"])
return eligible[:limit]
issues = [
{"id": 30, "tenant": "a", "title": "Third"},
{"id": 20, "tenant": "b", "title": "Other"},
{"id": 10, "tenant": "a", "title": "First"},
{"id": 40, "tenant": "a", "title": "Fourth"},
]
before = [dict(issue) for issue in issues]
assert [x["id"] for x in list_issues(issues, "a", 10, 2)] == [30, 40]
assert [x["id"] for x in list_issues(issues, "b", 0, 2)] == [20]
assert list_issues(issues, "a", 40, 2) == []
assert list_issues(issues, "a", 0, 0) == []
result = list_issues(issues, "a", 0, 1)
result[0]["title"] = "Changed in response"
assert issues == before
for bad in (-1, True, 1.5):
try:
list_issues(issues, "a", 0, bad)
except ValueError:
pass
else:
raise AssertionError("invalid limit accepted")
The copy is shallow and sufficient for this fixture’s scalar fields. It would not isolate nested mutable values. With n input records and e eligible records, filtering is O(n), sorting is O(e log e), and the copied eligible collection uses O(e) space. A production endpoint should push the trusted tenant filter and keyset pagination into an indexed database query; sorting all records in the application is a teaching model, not a scaling recommendation.
Finish with an asynchronous handoff
Use minutes 0–15 for the contract and review, 15–45 for the patch, 45–65 for tests, 65–80 for a follow-up, and 80–90 for the handoff. In the follow-up, ask what happens if an issue is deleted between pages. A cursor based on a stable ID still means “greater than this ID”; it does not depend on the old item’s array position. A consistent snapshot requires additional product and storage decisions.
Your handoff should state the defect, changed behavior, executed checks and remaining assumptions. For example: “The cursor is now compared with issue IDs and the tenant filter precedes the limit. The fixture covers interleaved tenants, an exclusive cursor, zero limit and input isolation. IDs are unique, titles are scalar, and authorization remains the caller’s responsibility.”
GitLab’s current AI guidance welcomes transparent AI use for technical assessments when candidates explain their prompts and decisions; it distinguishes that from reading AI-generated answers during an interview. Check the instructions for your assessment, disclose use and be ready to explain the patch yourself. GitLab AI interview guidance.
Show the failing test before the patch
Create the four-record fixture and call the proposed function with tenant a, cursor 10 and limit 2. The expected IDs are 30 and 40; the broken code returns an empty list because it starts slicing at position ten. Keep that assertion in the test file so the reason for the change remains visible. A test suite that only runs after the patch can be harder to assess if nobody knows whether it distinguished the original defect.
For the tenant-order bug, use cursor zero and a limit of two. Sorting produces IDs 10, 20, 30, 40, but ID 20 belongs to tenant b. The broken function takes the first two records and then removes 20, returning only 10. The correct function filters first and returns 10 and 30. This separate fixture isolates the defect rather than letting the cursor bug hide it.
For input isolation, mutate the returned title and compare the original list with its saved copy. Explain the shallow-copy assumption: all fields in this exercise are scalar. Nested labels or comments would still require a copy or immutability policy. A test of scalar dictionaries is not proof of deep isolation.
Explain the production-shaped follow-up
The local patch reads and sorts an in-memory list because that makes the exercise reproducible. A database endpoint should filter trusted tenant identity and id > cursor in storage, order by the stable key and limit there. Discuss an appropriate index for that access path and validate the real query plan instead of asserting a latency result from the Python fixture.
If the product wants newest creation time first, ID ordering is no longer automatically the contract. Use a stable composite ordering such as time plus ID, encode both cursor values and define how ties are handled. A cursor must contain enough information to continue the chosen order. Renaming after_id to cursor does not solve that problem by itself.
Also decide what consistency the user expects across pages. A newly inserted record or a deletion can change what is visible between requests. Keyset pagination can preserve progress through the order, but a fixed snapshot is a separate requirement. State the product trade-off before adding storage mechanisms.
Keep the final review concise
Finish with three parts: the corrected behavior, executed evidence and remaining assumptions. “Tenant filtering now precedes the limit; cursor values are compared with IDs. The fixtures cover both original defects, input isolation and boundaries. Authorization is supplied by the caller, and a production query plan remains to be implemented.” Avoid listing every line you edited. Another engineer needs the reason and result, not the chronology of every keystroke.
Remote collaboration needs enough context
A reviewer should be able to understand the concern without a private conversation. Quote the affected contract, give a small counterexample and separate required fixes from suggestions. If you disagree with feedback, compare the alternatives against the requirement and update your view when the evidence changes.
At the end of a live change, say what ran and what did not. “The fixture passed” is more precise than “everything is tested.” Explain database, authorization or concurrency assumptions that a small local test cannot establish. Clear handoffs reduce the work another engineer needs to do to understand your patch. GitLab engineering communication.
Seven preparation sessions
Read the current role and invitation first. Then review a small diff without editing it, implement the highest-priority fix, add a test that fails on the original code, discuss one stack-specific issue, rehearse a real collaboration story and run the ninety-minute practice mock. Keep the defect log from the mock and repeat the weakest part.
For a project story, include the problem, personal contribution, alternatives, collaboration and outcome. Avoid turning a previous team into the villain. If a process failed, explain what you changed or learned. Check the employer’s current principles rather than relying on a remembered acronym or an old interview report.
Is the mock a real GitLab task?
No. It is original practice for the published review-and-improve workflow. It does not reveal GitLab’s curated exercise bank.
Does a ninety-minute session mean the entire process takes ninety minutes?
No. It refers to a particular technical session or, here, the practice schedule. Confirm the number of stages and the interviews within each stage.
What should I do if an assistant makes an unrelated change?
Reject or revert it, narrow the request and rerun relevant tests. Explain your decision rather than accepting a large patch because it compiles.
Sources and Further Reading
- GitLab technical interview handbook
- GitLab code review guidance
- GitLab AI interview guidance
- GitLab engineering communication
Editorial revision: 8 October 2026. Current hiring details remain role-specific; unavailable source pages and unverified timings are identified in the text.