Atlassian

Real Atlassian Interview Experience

4 Rounds: Karat Screening, DSA, Component Design & System Design

4
Rounds
5-7
Weeks Prep
60-80 LPA
Expected Package
Offer
Vasanth

By Vasanth Bhat

Staff Software Engineer @ Walmart Global Tech

Mentored 100+ frontend developers through successful interviews

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

What's the output?
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
3
Explanation:
  • var is function-scoped, not block-scoped
  • The loop completes before any function is called
  • After the loop, i = 3
  • All functions reference the same i variable
  • When functions are called, they access the final value of i
✅ How to fix (Use let):
for (let i = 0; i < arr.length; i++) {
  funcs.push(function() {
    return i; // Each iteration creates a new binding
  });
}
// Output: 0, 1, 2

2️⃣ Event Loop & setTimeout (Hard)

Difficulty: Hard

What's the output and order?
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
5
Explanation (Event Loop Order):
  1. Synchronous code first: 1, 7
  2. Microtasks (Promises): 4, 6 (Promise chain executes)
  3. Macrotasks (setTimeout): 2 (first setTimeout)
  4. Microtasks after macrotask: 3 (Promise in setTimeout)
  5. Macrotasks: 5 (second setTimeout)

3️⃣ This Binding & Arrow vs Regular Functions (Hard)

Difficulty: Hard

What's the output?
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
Atlassian
Explanation:
  • Regular function: this is window, so this.name is undefined
  • Arrow function: Inherits this from parent scope (the object), so this is obj

4️⃣ Async/Await Error Handling (Medium-Hard)

Difficulty: Medium-Hard

What's the output?
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

Problem Statement:
Given a string s, find the length of the longest substring without repeating characters. 📋 Constraints:
• 0 ≤ s.length ≤ 5 × 10⁴
• s consists of English letters, digits, symbols, spaces
• Time Complexity: O(n) required
• Space Complexity: O(min(m, n)) where m=charset size
📝 Examples:
• Input: "abcabcbb" → Output: 3 ("abc")
• Input: "bbbbb" → Output: 1 ("b")
• Input: "pwwkew" → Output: 3 ("wke")
• Input: " " → Output: 1 (single space)
🔄 Step-by-Step Approach (Sliding Window):
Intuition:
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.
📊 Visual Example:
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

📋 Requirements Checklist:
✓ Click to open/close
✓ Keyboard navigation (↑↓↵Esc)
✓ Search/filter functionality
✓ Click outside to close
✓ ARIA accessibility labels
✓ Keyboard selection support
🎯 Live Demo (Click to try):

Try typing to search, use arrow keys to navigate, press Enter to select, Escape to close

Select a framework...

Selected: None

🔑 Key Implementation Details:
State Management
  • isOpen - dropdown visibility
  • searchTerm - filter text
  • highlightedIndex - keyboard focus
  • selectedValue - chosen item
Event Handlers
  • onClick - toggle dropdown
  • onKeyDown - keyboard nav
  • onClickOutside - auto-close
  • onChange - search filtering
💻 React Implementation:
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
♿ Accessibility Considerations (WCAG 2.1):
ARIA Attributes
  • aria-haspopup="listbox"
  • aria-expanded={isOpen}
  • aria-label on inputs
  • role="listbox"
  • role="option"
  • aria-selected
Keyboard Support
  • Arrow Up/Down - Navigate
  • Enter - Select item
  • Escape - Close
  • Tab - Standard focus
  • Type to search - Filter
💡 Evaluated on:
  • ✓ 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)

🎯 Phase 1: Requirements Clarification (5 minutes)
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"
📊 Phase 2: High-Level Architecture
┌─────────────────────────────────────────────────────────────┐ │ CLIENT (Browser/Mobile) │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ Custom Select + Filters │ Issue Board │ Pagination │ │ │ └──────────┬───────────────────────────────────────────┘ │ └─────────────┼──────────────────────────────────────────────┘ │ HTTP (REST) + WebSocket ┌─────────────┴──────────────────────────────────────────────┐ │ API GATEWAY (Load Balancer) │ │ │ │ ┌──────────────────┐ ┌──────────────────┐ │ │ │ REST API Service │ │ WebSocket Service│ │ │ └────────┬─────────┘ └────────┬─────────┘ │ └───────────┼─────────────────────┼──────────────────────────┘ │ Read/Write │ Real-time Push ┌─────┴─────────┬──────────┴─────────┐ │ │ │ ┌─────▼──────┐ ┌────▼──────┐ ┌─────────▼────┐ │ Database │ │ Cache │ │ Message Que │ │ (MySQL) │ │ (Redis) │ │ (RabbitMQ) │ └────────────┘ └───────────┘ └──────────────┘ 🗄️ Phase 3: Database Design
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;
                            
🎨 Phase 4: Frontend Implementation
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
📱 Frontend Code - React with Pagination:
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
🔍 Phase 7: Design Questions & Answers
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
📊 Performance Metrics
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
💡 Evaluated on:
  • ✓ 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 2 starts June 2026. Limited to 25 seats. Early birds get exclusive pricing.