Atlassian Frontend Interview Questions
Structured loop with a values interview. TypeScript and design systems.
Atlassian builds Jira, Confluence, Trello and Bitbucket: large, long-lived web applications that teams use all day. Its frontend work is heavily React and TypeScript, and it maintains a public design system. Interviews reflect that with questions on component design, typing and code that other people will maintain.
The process is structured, and the company describes much of it publicly. Alongside the technical rounds there is a dedicated values interview, and it is a real gate.
- Questions
- 15, 5 with free answers
- Interview rounds
- 5
- Last reviewed
- October 2026
Atlassian frontend interview process
The usual shape of the loop. A guide to what to prepare, not a promise of what you will get.
- 1
Coding screen
An online assessment or a live coding interview.
- 2
Code design
Build something small and extend it as requirements change. Readability and structure are marked, not only correctness.
- 3
Frontend or system design
Design a feature such as a board, a comment thread or an editor component.
- 4
Values interview
Behavioural questions mapped to Atlassian's company values.
- 5
Management interview
Experience, collaboration and career goals.
What interviewers tend to look for
- Code that stays readable when the requirements change halfway through.
- TypeScript: generics, utility types and narrowing.
- Component APIs and accessibility, in the spirit of a design system.
- Concrete stories for each company value.
Your Atlassian mock interview
15 questions. Type an answer under each one and an AI reviewer scores it and tells you what you missed. Stuck on one? Ask for a hint. A free account gets 3 AI replies a day.
Your mock interview
0 of 15 answered
Answer a question and your score shows up here.
JavaScript
3 questions- Question 1 of 15JavaScript
Atlassian interviewer
Compare Promise.all, allSettled, race and any. How do you run async calls in parallel with async/await?
Model answer
Promise.all: fulfils with every value, rejects on the first rejection.Promise.allSettled: never rejects. It reports the outcome of each promise.Promise.race: settles with whichever promise settles first, fulfilled or rejected.Promise.any: fulfils with the first success, and rejects with anAggregateErroronly if all fail.
A common mistake is awaiting inside a loop, which runs the calls one after another. Start them first, then await them together:
// Sequential: the second call waits for the first const user = await getUser(id); const orders = await getOrders(id); // Parallel: both start immediately const [user, orders] = await Promise.all([getUser(id), getOrders(id)]);Wrap awaited calls in
try/catch. UseallSettledwhen one failure should not throw away the other results. - Question 2 of 15JavaScript
Atlassian interviewer
Run a list of async tasks with at most K running at the same time.
Model answer
async function runWithLimit(tasks, limit) { const results = new Array(tasks.length); let next = 0; async function worker() { while (next < tasks.length) { const index = next++; results[index] = await tasks[index](); } } const workers = Array.from( { length: Math.min(limit, tasks.length) }, worker ); await Promise.all(workers); return results; }tasksmust be functions that return promises. A promise has already started by the time you hold it, so an array of promises cannot be limited.Each worker pulls the next index until none are left.
next++is safe because JavaScript is single-threaded: nothing else runs between reading and incrementing.Follow-ups: keep going when one task fails (catch inside the worker and store the error), add a per-task timeout, and support adding tasks while it runs.
- Question 3 of 15JavaScript
Atlassian interviewer
Implement pipe and compose.
Model answer
Both combine functions into one.
piperuns them left to right,composeright to left.const pipe = (...fns) => (input) => fns.reduce((value, fn) => fn(value), input); const compose = (...fns) => (input) => fns.reduceRight((value, fn) => fn(value), input); const slugify = pipe( (text) => text.trim(), (text) => text.toLowerCase(), (text) => text.split(' ').join('-') ); slugify(' Hello World '); // "hello-world"Follow-ups: let the first function take several arguments, and write an async version that awaits each step (
reduceover a promise chain).
TypeScript
4 questions- Question 4 of 15TypeScript
Atlassian interviewer
What are generics, and how do you constrain one?
Model answer
A generic is a type parameter. It lets a function or type work with many types while keeping the link between its inputs and its output, which
anythrows away.function first<T>(items: T[]): T | undefined { return items[0]; } first([1, 2, 3]); // number | undefined first(['a', 'b']); // string | undefinedA constraint with
extendslimits what the parameter can be, and lets you use the properties that the constraint guarantees:function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] { return items.map((item) => item[key]); } pluck([{ id: 1, name: 'Asha' }], 'name'); // string[] pluck([{ id: 1, name: 'Asha' }], 'age'); // compile errorGood to mention: type arguments are usually inferred, generics can have defaults (
<T = string>), and one that is used only once in a signature is usually not needed. - Question 5 of 15TypeScript
Atlassian interviewer
Which utility types do you use, and can you implement Pick and Omit yourself?
Model answer
The everyday ones:
Partial,Required,Readonly,Pick,Omit,Record,ReturnType,Parameters,AwaitedandNonNullable.They are built from mapped types, which loop over keys with
in:type MyPartial<T> = { [K in keyof T]?: T[K] }; type MyPick<T, K extends keyof T> = { [P in K]: T[P] }; type MyOmit<T, K extends PropertyKey> = MyPick<T, Exclude<keyof T, K>>; type MyRecord<K extends PropertyKey, V> = { [P in K]: V };A typical use is deriving one type from another so they cannot drift apart:
interface User { id: string; name: string; email: string; } type NewUser = Omit<User, 'id'>; type UserPatch = Partial<NewUser>;A common harder follow-up is
DeepPartialorDeepReadonly, which apply the same mapping recursively. - Question 6 of 15TypeScript
Atlassian interviewer
What are conditional types, and what does infer do?
Model answer
A conditional type chooses between two types with a test:
T extends U ? X : Y.inferdeclares a type variable inside that test and captures whatever matches there.type MyReturnType<T> = T extends (...args: never[]) => infer R ? R : never; type ElementOf<T> = T extends readonly (infer E)[] ? E : never; type MyAwaited<T> = T extends Promise<infer V> ? MyAwaited<V> : T; type A = MyReturnType<() => number>; // number type B = ElementOf<string[]>; // string type C = MyAwaited<Promise<Promise<boolean>>>; // booleanOne behaviour to know: when
Tis a bare type parameter and you pass a union, the condition is applied to each member separately. That is howExclude<T, U>works. Wrapping both sides in a tuple,[T] extends [U], turns that off. - Question 7 of 15TypeScript
Atlassian interviewer
What is type narrowing? What is a discriminated union?
Model answer
Narrowing is TypeScript refining a broad type inside a branch, based on a check it understands:
typeof,instanceof,in, equality, truthiness, or a user-defined guard that returnsvalue is T.A discriminated union is a union of object types that share one literal field. Checking that field narrows to the right member:
type RequestState = | { status: 'loading' } | { status: 'success'; data: string[] } | { status: 'error'; message: string }; function render(state: RequestState) { switch (state.status) { case 'loading': return 'Loading'; case 'success': return state.data.join(', '); // data exists here case 'error': return state.message; // message exists here } }The reason to prefer this over one object with optional
dataanderrorfields: impossible combinations, such as loading with an error message, cannot be written at all.
React
3 questions- Question 8 of 15React
Atlassian interviewer
Which component composition patterns do you use? How do you avoid prop drilling?
Model answer
Composition means building a component out of the pieces it is given instead of configuring it with a growing list of flags.
childrenand slot props:<Card header={<Title />}>...</Card>beats<Card title showIcon iconPosition>.- Compound components:
<Tabs>,<Tabs.List>and<Tabs.Panel>share state through a private context, and the caller arranges them freely. - Custom hooks for sharing logic. They have replaced most higher-order components and render props.
- Controlled and uncontrolled modes: accept
valueandonChange, and fall back to internal state when they are absent.
Composition also fixes most prop drilling. If
PagepassesuserthroughLayoutandHeaderonly so thatAvatarcan read it, havePagerender<Avatar user={user} />and pass that element down. The components in between no longer know aboutuser.Use Context for what is left.
- Question 9 of 15React
Atlassian interviewer
When would you use Context, and what is its performance problem?
Model answer
Context passes a value to any component below a provider without threading it through props. It suits values that many components read and that change rarely: theme, locale, the signed-in user.
The problem: when the provider's value changes, every component that reads that context re-renders, even if the part it uses is the same. Context has no selectors.
Ways to limit that:
- Memoise the value object, so the provider's own re-render does not create a new one each time.
- Split one large context into several, and put state and its setter functions in separate contexts.
- Keep fast-changing values, such as a text input or a mouse position, out of context.
- When many components need different slices of shared state, use a store with selectors (Zustand, Redux Toolkit, or
useSyncExternalStoredirectly).
Context is a way to deliver a value, not a state manager. The state still lives in a
useStateoruseReducersomewhere. - Question 10 of 15React
Atlassian interviewer
How do you test React components?
Model answer
The usual stack is React Testing Library with Vitest or Jest, and Playwright or Cypress for end-to-end tests.
The principle: test what a user can see and do, not how the component is built. Find elements by role and label, interact with them, and assert on what appears.
test('shows an error for an invalid email', async () => { const user = userEvent.setup(); render(<SignupForm />); await user.type(screen.getByLabelText('Email'), 'not-an-email'); await user.click(screen.getByRole('button', { name: 'Sign up' })); expect(await screen.findByRole('alert')).toHaveTextContent('valid email'); });- Querying by role also checks that the markup is accessible.
- Use
findByqueries for anything that appears after an async update. - Mock the network at the request level with a tool such as MSW, not by mocking your own modules.
- Do not assert on state, on hooks being called or on class names.
A reasonable split: unit tests for pure functions and hooks, component tests for forms and other interactive pieces, and a few end-to-end tests for the paths that earn money.
CSS
1 question- Question 11 of 15CSS
Atlassian interviewer
Compare CSS Modules, utility classes such as Tailwind, and CSS-in-JS.
Model answer
- CSS Modules: ordinary CSS files whose class names are made unique at build time. No runtime cost and no naming collisions. You still write and name every class.
- Utility classes (Tailwind): you compose small single-purpose classes in the markup. Values come from a fixed scale, which keeps spacing and colour consistent, and the CSS file stops growing because classes are reused. The markup gets long and there is a vocabulary to learn.
- Runtime CSS-in-JS (styled-components, Emotion): styles are written in JavaScript and can depend on props. Styles sit next to the component. The cost is work in the browser on every render to generate and inject them, and they do not fit React Server Components, which cannot run that code.
- Build-time CSS-in-JS (vanilla-extract and similar): styles are written in TypeScript and extracted to static CSS files, which keeps type safety without the runtime cost.
There is no single right answer. The interviewer wants the trade-offs: runtime cost, how well it works with server rendering, how it scales to a large team, and how design tokens are enforced. Say what you have used and what went wrong with it.
Browser, performance and system design
2 questions- Question 12 of 15Browser, performance and system design
Atlassian interviewer
How do you make a web page accessible?
Model answer
- Use the right HTML element. A
buttonis focusable, works with Enter and Space and is announced as a button. Adivwith a click handler does none of that. The same goes fornav,main, headings in order, and alabelfor every input. - Keyboard. Everything must be reachable and usable without a mouse, in a sensible order, with a visible focus ring. When a dialog opens, move focus into it, keep it there, and return it to the trigger on close.
- Text alternatives. Images need
alttext, or an emptyaltif they are decorative. Icon-only buttons need an accessible name. - Colour. Body text needs a contrast ratio of at least 4.5 to 1, and colour should never be the only signal.
- ARIA, when HTML is not enough.
aria-expandedon a toggle,aria-livefor updates that appear without a page load, roles for custom widgets. No ARIA is better than wrong ARIA. - Motion and zoom. Respect
prefers-reduced-motion, and make sure the page works at 200% zoom.
For testing: use the page with the keyboard only, run an automated checker such as axe or Lighthouse, and try a screen reader on the main flows. Automated tools catch only part of the problems.
- Use the right HTML element. A
- Question 13 of 15Browser, performance and system design
Atlassian interviewer
How would you design a reusable component library or design system?
Model answer
- Tokens first. Colour, spacing, type and radius are defined once, as CSS custom properties, and components use only those. Theming and dark mode then mean swapping token values.
- Layers. Unstyled, accessible primitives at the bottom (button, dialog, popover), styled components on top, and product-specific patterns outside the library.
- A consistent API. The same prop names everywhere (
size,variant,disabled). Composition over configuration. Both controlled and uncontrolled use. Refs and native props forwarded to the underlying element. - Accessibility built in. Keyboard behaviour and ARIA follow the WAI-ARIA patterns, so product teams get it without extra work.
- Packaging. ES modules that tree-shake, TypeScript types included, and no global CSS side effects.
- Documentation and tests. Storybook with live examples, unit tests for behaviour, visual regression tests for appearance and automated accessibility checks.
- Change management. Semantic versioning, a changelog, deprecation warnings before removal, and codemods for breaking changes.
The hardest part is adoption, not code. Talk about who owns the library, how teams ask for a new component, and how you avoid a second button being built somewhere else.
Machine coding
2 questions- Question 14 of 15Machine coding
Atlassian interviewer
Build a nested comments section with replies.
Model answer
Comments form a tree, so the display is a recursive component: a comment renders its replies with the same component, indented one level.
The design decision is how to store the tree:
- Nested (
{ id, text, replies: [...] }): easy to render, awkward to update, because adding a reply three levels down means rebuilding every level above it. - Flat map (
{ [id]: { id, text, parentId, childIds } }): adding a reply touches the new record and its parent. This is usually the better answer, and it matches how an API returns comments.
function addReply(comments, parentId, text) { const id = crypto.randomUUID(); return { ...comments, [id]: { id, text, parentId, childIds: [] }, [parentId]: { ...comments[parentId], childIds: [...comments[parentId].childIds, id], }, }; }- Reply opens an inline form under that comment only.
- Edit and delete. Deleting a comment that has replies usually leaves a "deleted" placeholder so that the thread stays intact.
- Collapse and expand a thread.
- Stop indenting after a few levels on narrow screens.
Follow-ups: votes and sorting, loading deep threads on demand, and optimistic posting with rollback on failure.
- Nested (
- Question 15 of 15Machine coding
Atlassian interviewer
Build a todo list or a Kanban board with drag and drop.
Model answer
Keep one flat list of tasks, each with an
id,titleandstatus. The columns are derived by filtering; they are not separate pieces of state.function reducer(tasks, action) { switch (action.type) { case 'add': return [...tasks, { id: crypto.randomUUID(), title: action.title, status: 'todo' }]; case 'move': return tasks.map((t) => (t.id === action.id ? { ...t, status: action.status } : t)); case 'remove': return tasks.filter((t) => t.id !== action.id); default: return tasks; } } // Card <li draggable onDragStart={(e) => e.dataTransfer.setData('text/plain', task.id)} /> // Column <ul onDragOver={(e) => e.preventDefault()} // required, or drop never fires onDrop={(e) => dispatch({ type: 'move', id: e.dataTransfer.getData('text/plain'), status })} />- Add, edit in place, delete, and mark done. Ignore empty titles.
- Save to
localStorageand load on start. - Highlight the column being dragged over.
- Give a non-drag way to move a card, such as a menu or buttons, for keyboard and touch users. HTML5 drag and drop does not work on most touch screens.
Follow-ups: reordering within a column (store an order index and work out the drop position), undo, and filtering.
Practise for Atlassian
Reading answers is the easy half. These problems run in the browser against real tests.
