
Real Atlassian Interview Experience
4 Rounds: Karat Screening, DSA, Component Design & System Design
Round 1: Karat Screening - Guess the Output (45 minutes)
What is Karat? A third-party technical screening platform used by major tech companies to conduct remote coding interviews.
Medium to Hard difficulty JavaScript output tracing questions. Focus on understanding JavaScript semantics, async behavior, and closure concepts.
1️⃣ Closure & Variable Scope (Medium)
Difficulty: Medium
const arr = [1, 2, 3];
const funcs = [];
for (var i = 0; i < arr.length; i++) {
funcs.push(function() {
return i;
});
}
console.log(funcs[0]());
console.log(funcs[1]());
console.log(funcs[2]());Output:3
3
3Explanation:varis function-scoped, not block-scoped- The loop completes before any function is called
- After the loop,
i = 3 - All functions reference the same
ivariable - When functions are called, they access the final value of
i
for (let i = 0; i < arr.length; i++) {
funcs.push(function() {
return i; // Each iteration creates a new binding
});
}
// Output: 0, 1, 22️⃣ Event Loop & setTimeout (Hard)
Difficulty: Hard
console.log('1');
setTimeout(() => {
console.log('2');
Promise.resolve().then(() => console.log('3'));
}, 0);
Promise.resolve()
.then(() => {
console.log('4');
setTimeout(() => console.log('5'), 0);
})
.then(() => console.log('6'));
console.log('7');Output:1
7
4
6
2
3
5Explanation (Event Loop Order):- Synchronous code first: 1, 7
- Microtasks (Promises): 4, 6 (Promise chain executes)
- Macrotasks (setTimeout): 2 (first setTimeout)
- Microtasks after macrotask: 3 (Promise in setTimeout)
- Macrotasks: 5 (second setTimeout)
3️⃣ This Binding & Arrow vs Regular Functions (Hard)
Difficulty: Hard
const obj = {
name: 'Atlassian',
regularFunc: function() {
setTimeout(function() {
console.log(this.name);
}, 0);
},
arrowFunc: function() {
setTimeout(() => {
console.log(this.name);
}, 0);
}
};
obj.regularFunc();
obj.arrowFunc();Output:undefined
AtlassianExplanation:- Regular function:
thisiswindow, sothis.nameis undefined - Arrow function: Inherits
thisfrom parent scope (the object), sothisisobj
4️⃣ Async/Await Error Handling (Medium-Hard)
Difficulty: Medium-Hard
async function fetchData() {
try {
const result = await Promise.reject('Error!');
console.log('Success:', result);
} catch (e) {
console.log('Caught:', e);
}
}
fetchData().then(() => {
console.log('Done');
});
console.log('Start');Output:Start
Caught: Error!
Done💡 Karat Round Tips:
- Think aloud and explain your reasoning step by step
- Trace through the code execution mentally before answering
- Practice similar problems on Codewars (4-5 kyu level)
Closures trap you. Async puzzles you. Between you and this round is understanding how JavaScript actually works—not just what it does. Get clarity with us →
Round 2: Data Structures & Algorithms (60 minutes)
1 medium LeetCode problem with optimization focus.
🔤 Longest Substring Without Repeating Characters (Sliding Window)
Difficulty: Medium | Time: 40 minutes
Given a string
s, find the length of the longest substring without repeating characters.📋 Constraints:• s consists of English letters, digits, symbols, spaces
• Time Complexity: O(n) required
• Space Complexity: O(min(m, n)) where m=charset size
• Input: "abcabcbb" → Output: 3 ("abc")
• Input: "bbbbb" → Output: 1 ("b")
• Input: "pwwkew" → Output: 3 ("wke")
• Input: " " → Output: 1 (single space)
Use two pointers (left, right) to maintain a window of unique characters. When we see a repeated character, shrink the window from the left until it becomes unique again.
s = "abcabcbb"
Step 1: window = "a", max = 1
Step 2: window = "ab", max = 2
Step 3: window = "abc", max = 3
Step 4: See 'a' again (duplicate!)
Shrink from left: remove 'a'
window = "bca", max = 3
Step 5: Shrink: remove 'b'
window = "cab"
Step 6: Shrink: remove 'c'
window = "ab"
Step 7: Add 'b', window = "abb"
Shrink: remove first 'b'
window = "bb"
Shrink: remove 'b'
window = "b", max = 3🎯 Optimal Solution (O(n)):function lengthOfLongestSubstring(s) {
const charMap = new Map();
let maxLen = 0;
let left = 0;
for (let right = 0; right < s.length; right++) {
const char = s[right];
if (charMap.has(char)) {
left = Math.max(left, charMap.get(char) + 1);
}
charMap.set(char, right);
maxLen = Math.max(maxLen, right - left + 1);
}
return maxLen;
}
// Test: lengthOfLongestSubstring("abcabcbb") → 3💡 DSA Tips:
- Explain brute force first, then optimize
- Walk through examples before coding
- Discuss time and space complexity clearly
Algorithms aren't formulas; they're perspectives. Seeing the pattern first, then coding it fast—that's the gap. Atlassian rewards sharp algorithmic thinking. Sharpen your edge →
Round 3: Component Design - Custom Select (90 minutes)
Build an accessible, reusable Custom Select with keyboard navigation and filtering.
🎨 Custom Select Component - Interactive Demo
Time: 90 minutes
State Management
isOpen- dropdown visibilitysearchTerm- filter texthighlightedIndex- keyboard focusselectedValue- chosen item
Event Handlers
onClick- toggle dropdownonKeyDown- keyboard navonClickOutside- auto-closeonChange- search filtering
import React, { useState, useRef, useEffect } from 'react';
function CustomSelect({ options, onSelect, placeholder }) {
const [isOpen, setIsOpen] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [highlightedIndex, setHighlightedIndex] = useState(0);
const [selectedValue, setSelectedValue] = useState(null);
const selectRef = useRef(null);
const dropdownRef = useRef(null);
// Filter options based on search term
const filteredOptions = options.filter(opt =>
opt.toLowerCase().includes(searchTerm.toLowerCase())
);
// Handle keyboard navigation
const handleKeyDown = (e) => {
if (!isOpen && e.key !== 'Enter') return;
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
setHighlightedIndex(prev =>
Math.min(prev + 1, filteredOptions.length - 1)
);
if (!isOpen) setIsOpen(true);
break;
case 'ArrowUp':
e.preventDefault();
setHighlightedIndex(prev => Math.max(prev - 1, 0));
break;
case 'Enter':
e.preventDefault();
if (filteredOptions[highlightedIndex]) {
handleSelect(filteredOptions[highlightedIndex]);
} else if (!isOpen && options.length > 0) {
setIsOpen(true);
}
break;
case 'Escape':
setIsOpen(false);
setSearchTerm('');
break;
default:
break;
}
};
// Handle option selection
const handleSelect = (option) => {
setSelectedValue(option);
setIsOpen(false);
setSearchTerm('');
setHighlightedIndex(0);
onSelect?.(option);
};
// Close dropdown when clicking outside
useEffect(() => {
const handleClickOutside = (e) => {
if (selectRef.current && !selectRef.current.contains(e.target)) {
setIsOpen(false);
}
};
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}
}, [isOpen]);
// Auto-focus search when dropdown opens
useEffect(() => {
if (isOpen && dropdownRef.current) {
dropdownRef.current.focus();
}
}, [isOpen]);
return (
<div ref={selectRef} className="select-wrapper">
{/* Trigger Button */}
<button
className="select-trigger"
onClick={() => setIsOpen(!isOpen)}
aria-haspopup="listbox"
aria-expanded={isOpen}
>
<span>{selectedValue || placeholder}</span>
<span className={`arrow ${isOpen ? 'open' : ''}`}>▼</span>
</button>
{/* Dropdown Menu */}
{isOpen && (
<div className="select-dropdown" role="listbox">
{/* Search Input */}
<div className="select-search">
<input
ref={dropdownRef}
type="text"
placeholder="Search options..."
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
setHighlightedIndex(0);
}}
onKeyDown={handleKeyDown}
aria-label="Search options"
/>
</div>
{/* Options List */}
<ul className="select-options">
{filteredOptions.length > 0 ? (
filteredOptions.map((option, idx) => (
<li
key={option}
className={`select-item ${
idx === highlightedIndex ? 'highlighted' : ''
} ${option === selectedValue ? 'selected' : ''}`}
onClick={() => handleSelect(option)}
onMouseEnter={() => setHighlightedIndex(idx)}
role="option"
aria-selected={option === selectedValue}
>
{option}
</li>
))
) : (
<li className="select-item" style={{ pointerEvents: 'none' }}>
No options found
</li>
)}
</ul>
</div>
)}
</div>
);
}
export default CustomSelect;📊 Edge Cases to Handle:- Empty search results - Show "No options found"
- Keyboard boundary - Don't go above 0 or below length-1
- Focus management - Auto-focus search input when opening
- Click outside - Use ref and event listener to detect
- Option not found - Reset search on selection
- Dynamic options - Reset highlighted index when search changes
ARIA Attributes
aria-haspopup="listbox"aria-expanded={isOpen}aria-labelon inputsrole="listbox"role="option"aria-selected
Keyboard Support
Arrow Up/Down- NavigateEnter- Select itemEscape- CloseTab- Standard focusType to search- Filter
- ✓ Code structure and readability
- ✓ State management patterns
- ✓ Keyboard navigation completeness
- ✓ Accessibility (WCAG compliance)
- ✓ Edge case handling
- ✓ Performance (filtering efficiency)
- ✓ Communication and explanation
💡 Component Design Interview Tips:
- Clarify requirements first - Ask about expected behavior, constraints, and edge cases
- Think accessibility from the start - Don't add ARIA as an afterthought
- Handle all edge cases - Empty results, single item, keyboard edge boundaries
- Write reusable, generic code - Use props for flexibility, not hardcoded values
- Test your implementation - Walk through with examples and explain state changes
- Discuss optimizations - Memoization, debouncing for search, virtual scrolling
Many can build. Few can build accessible, scalable, performant components that teams trust. This is where the craft shows. Master the craft →
Round 4: System Design - Jira Board Pagination (75 minutes)
Design a scalable issue board with efficient pagination, filtering, sorting, and real-time updates for millions of issues.
📋 Interview Approach
Time Allocation:5 min (Clarify) → 15 min (Design) → 30 min (Implementation) → 15 min (Optimization) → 10 min (Q&A)
Key Questions to Ask:
- ❓ How many projects? How many issues per project? → "Millions across all projects"
- ❓ How many concurrent users? → "10,000+ concurrent"
- ❓ What filters are required? → "Status, assignee, priority, created date, components"
- ❓ Do we need real-time updates? → "Yes, WebSocket preferred"
- ❓ Acceptable latency? → "< 200ms for pagination, < 1s for updates"
- ❓ Mobile support? → "Yes, responsive design required"
Schema Design
-- Issues Table
CREATE TABLE issues (
id BIGINT PRIMARY KEY,
project_id BIGINT NOT NULL,
key VARCHAR(50) UNIQUE,
title VARCHAR(255),
description TEXT,
status ENUM('OPEN', 'IN_PROGRESS', 'DONE', 'CLOSED'),
assignee_id BIGINT,
priority ENUM('CRITICAL', 'HIGH', 'MEDIUM', 'LOW'),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
parent_id BIGINT,
INDEX idx_project_status (project_id, status),
INDEX idx_project_created (project_id, created_at DESC),
INDEX idx_project_assignee (project_id, assignee_id),
INDEX idx_project_priority (project_id, priority),
INDEX idx_updated (updated_at DESC)
);
-- Efficient pagination query
SELECT * FROM issues
WHERE project_id = ?
AND status IN (?, ?, ?)
AND created_at > ?
ORDER BY created_at DESC
LIMIT 50;State Management
- Filters (status, assignee, priority)
- Sort options (field, direction)
- Current page cursor
- Loaded issues cache
- Total count (if needed)
Performance Optimizations
- Virtual scrolling (react-window)
- Debounced filter/sort changes
- Local caching layer
- Request deduplication
- Progressive loading
import React, { useState, useCallback, useEffect } from 'react';
import { FixedSizeList as List } from 'react-window';
function IssueBoard({ projectId }) {
const [issues, setIssues] = useState([]);
const [cursor, setCursor] = useState(null);
const [hasMore, setHasMore] = useState(true);
const [filters, setFilters] = useState({
status: [],
assignee: null,
priority: []
});
const [sortBy, setSortBy] = useState({ field: 'created', order: 'desc' });
const [loading, setLoading] = useState(false);
// Fetch issues with filters and pagination
const fetchIssues = useCallback(async (newCursor = null) => {
setLoading(true);
try {
const response = await fetch('/api/issues', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
projectId,
filters,
sortBy,
cursor: newCursor,
limit: 50
})
});
const data = await response.json();
if (newCursor) {
setIssues(prev => [...prev, ...data.issues]);
} else {
setIssues(data.issues);
}
setCursor(data.nextCursor);
setHasMore(data.hasMore);
} catch (error) {
console.error('Failed to fetch issues:', error);
} finally {
setLoading(false);
}
}, [projectId, filters, sortBy]);
// Fetch initial issues on mount or filter change
useEffect(() => {
fetchIssues(null);
}, [filters, sortBy]);
// Handle infinite scroll
const handleLoadMore = () => {
if (hasMore && !loading) {
fetchIssues(cursor);
}
};
// Row renderer for virtual list
const Row = ({ index, style }) => {
const issue = issues[index];
return (
<div style={style} className="issue-row">
<span className={`status-badge ${issue.status.toLowerCase()}`}>
{issue.status}
</span>
<span className="issue-key">{issue.key}</span>
<span className="issue-title">{issue.title}</span>
<span className={`priority ${issue.priority.toLowerCase()}`}>
{issue.priority}
</span>
</div>
);
};
return (
<div className="issue-board">
{/* Filter Section */}
<div className="filter-section">
<StatusFilter
value={filters.status}
onChange={(status) => setFilters({...filters, status})}
/>
<PriorityFilter
value={filters.priority}
onChange={(priority) => setFilters({...filters, priority})}
/>
<AssigneeFilter
value={filters.assignee}
onChange={(assignee) => setFilters({...filters, assignee})}
/>
<SortDropdown
value={sortBy}
onChange={setSortBy}
/>
</div>
{/* Virtual List */}
<List
height={600}
itemCount={issues.length}
itemSize={60}
width="100%"
onItemsRendered={({ visibleStopIndex }) => {
if (visibleStopIndex === issues.length - 10) {
handleLoadMore();
}
}}
>
{Row}
</List>
{loading && <div className="spinner">Loading...</div>}
</div>
);
}
export default IssueBoard;🔄 Phase 5: Real-time Updates with WebSocket// WebSocket for real-time updates
class IssueWebSocketManager {
constructor(projectId) {
this.projectId = projectId;
this.ws = null;
this.listeners = [];
}
connect() {
this.ws = new WebSocket(`wss://api.jira.com/issues/${this.projectId}`);
this.ws.onopen = () => {
console.log('Connected to issue updates');
this.ws.send(JSON.stringify({
action: 'subscribe',
projectId: this.projectId
}));
};
this.ws.onmessage = (event) => {
const update = JSON.parse(event.data);
// Notify all listeners of the update
this.listeners.forEach(callback => {
callback(update);
});
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
// Implement exponential backoff retry
setTimeout(() => this.connect(), 3000);
};
}
on(callback) {
this.listeners.push(callback);
return () => {
this.listeners = this.listeners.filter(l => l !== callback);
};
}
disconnect() {
if (this.ws) this.ws.close();
}
}⚡ Phase 6: Caching Strategy| Cache Layer | TTL | Use Case |
|---|---|---|
| Client Memory | Session | Recently viewed issues, current filters |
| LocalStorage | 7 days | Filter preferences, sort settings |
| Redis (Server) | 5 minutes | Filter query results, user preferences |
| Database | N/A | Source of truth for all data |
Q1: Why cursor-based pagination instead of offset?
- Offset-based: SELECT * FROM issues LIMIT 50 OFFSET 100000
- ❌ Problem: Scans all 100,000 rows before returning results
- ✅ Cursor-based: SELECT * FROM issues WHERE created_at > ? LIMIT 50
- ✅ Benefit: O(1) lookup, doesn't scan skipped rows
Q2: How do we handle concurrent edits?
- User A loads issue board at 10:00 AM
- User B updates issue status at 10:01 AM
- ✅ WebSocket notifies all connected clients in real-time
- ✅ Update notification includes issue ID and new status
- ✅ Client updates in-memory copy and re-renders affected row
Q3: How do we optimize for mobile with slow networks?
- ✅ Send issue summaries instead of full description
- ✅ Lazy load attachments and comments
- ✅ Use gzip compression for API responses
- ✅ Implement progressive image loading
- ✅ Cache aggressively with service workers
Q4: How do we handle filter combinations?
- Challenge: Millions of possible filter combinations
- ✅ Pre-compute frequent filter combinations
- ✅ Use composite indexes: (project_id, status, assignee, priority, created_at)
- ✅ Cache results for 5 minutes in Redis
- ✅ Invalidate cache on issue updates via pub/sub
| Metric | Target | How to Achieve |
|---|---|---|
| Initial Load | < 200ms | CDN + compression + caching |
| Pagination Load | < 150ms | Cursor pagination + indexes |
| Filter Change | < 300ms | Debouncing + Redis cache |
| WebSocket Update | < 1s | Direct connection + small payload |
| Search (100k results) | < 500ms | Elasticsearch or similar |
- ✓ Problem clarification and requirements understanding
- ✓ Scalability considerations
- ✓ Database optimization (indexing, query efficiency)
- ✓ Frontend performance (virtual scrolling, caching)
- ✓ Real-time functionality (WebSocket, pub/sub)
- ✓ Trade-off discussions
- ✓ Code quality and explanation
💡 System Design Interview Tips:
- Start broad, go deep - Begin with high-level architecture, then deep-dive into bottlenecks
- Clarify requirements first - Don't assume; ask about scale, latency, features
- Discuss trade-offs openly - Every decision has pros and cons
- Draw diagrams - Help the interviewer visualize your solution
- Think about scale - How does your solution handle 10x, 100x growth?
- Consider failure modes - What happens when database goes down? When WebSocket disconnects?
- Be ready to pivot - Interview feedback may change requirements; be flexible
Between you and Atlassian's system design lies thinking at scale: pagination, caching, trade-offs. They'll ask the hard questions. Answer with depth →
You Now Know What Atlassian Expects 👆
From JavaScript fundamentals to system design mastery—this is the complete Atlassian interview roadmap. But knowing what to expect and being able to deliver under pressure are two different things.
Our cohort has helped a few developers crack interviews at Atlassian, Microsoft, Uber, and Amazon. They didn't just learn the concepts—they learned to think like an Atlassian engineer.
What our cohort members get:
- ✅ Structured 8-week curriculum covering all 4 rounds
- ✅ 50+ mock interviews (real-time, recorded, feedback)
- ✅ 1-on-1 mentoring with engineers from Atlassian, Microsoft, etc.
- ✅ Private community (25 dedicated developers)
- ✅ Lifetime access to recordings & materials
- ✅ 100% money-back if you don't get offers
Cohort 3 starts October 2026. Limited to 25 seats. Join the waitlist now.
