System Design + Java Program for Tic-Tac-Toe (TicTac)

 

๐ŸŽฎ System Design + Java Program for Tic-Tac-Toe (TicTac)

In this mini system design, we’ll build a console-based Tic-Tac-Toe game in Java.

We’ll keep it:

  • Simple

  • Readable

  • Data-structure focused


๐Ÿง  High-Level Design

We’ll design 3 main parts:

  1. Board – stores the grid and game state

  2. Player – represents X and O

  3. Game – controls turns, input, and winning logic

Data Structures Used

  • char[][] board = new char[3][3];
    → 3x3 grid for the game

  • char currentPlayer = 'X';
    → Tracks who is playing now

  • Simple methods to:

    • printBoard()

    • makeMove(row, col)

    • checkWin()

    • isDraw()

No complex frameworks. Just core Java + basic data structures.


๐Ÿ“ฆ Class Design Overview

1️⃣ Board Class

Responsibilities:

  • Initialize empty board

  • Print the board

  • Validate and update moves

  • Check winner / draw

2️⃣ TicTacToeGame Class

Responsibilities:

  • Run the game loop

  • Switch players

  • Take input from console

  • Use Board to perform actions


✅ Full Java Program – Simple Tic-Tac-Toe

import java.util.Scanner; /** * Simple Tic-Tac-Toe game using basic OOP and array data structure. * * Design: * - Board: handles the grid and game state * - TicTacToeGame: controls the game flow */ class Board { private char[][] grid; // 3x3 board public Board() { grid = new char[3][3]; // Initialize board with spaces for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { grid[i][j] = ' '; } } } // Print board in a simple format public void printBoard() { System.out.println("-------------"); for (int i = 0; i < 3; i++) { System.out.print("| "); for (int j = 0; j < 3; j++) { System.out.print(grid[i][j] + " | "); } System.out.println(); System.out.println("-------------"); } } // Try to place a move on the board public boolean placeMove(int row, int col, char playerSymbol) { // Check if row and col are in range and cell is empty if (row < 0 || row >= 3 || col < 0 || col >= 3) { System.out.println("Invalid position! Choose row and col between 0 and 2."); return false; } if (grid[row][col] != ' ') { System.out.println("Cell already taken! Choose another position."); return false; } grid[row][col] = playerSymbol; return true; } public boolean hasWinner(char playerSymbol) { // Check rows for (int i = 0; i < 3; i++) { if (grid[i][0] == playerSymbol && grid[i][1] == playerSymbol && grid[i][2] == playerSymbol) { return true; } } // Check columns for (int j = 0; j < 3; j++) { if (grid[0][j] == playerSymbol && grid[1][j] == playerSymbol && grid[2][j] == playerSymbol) { return true; } } // Check diagonals if (grid[0][0] == playerSymbol && grid[1][1] == playerSymbol && grid[2][2] == playerSymbol) { return true; } if (grid[0][2] == playerSymbol && grid[1][1] == playerSymbol && grid[2][0] == playerSymbol) { return true; } return false; } public boolean isFull() { // If any cell is an empty space, board is not full for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { if (grid[i][j] == ' ') { return false; } } } return true; } } public class TicTacToeGame { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); Board board = new Board(); char currentPlayer = 'X'; boolean gameFinished = false; System.out.println("Welcome to Tic-Tac-Toe!"); System.out.println("Player X and Player O take turns."); System.out.println("Enter row and column (0, 1, or 2)."); System.out.println(); while (!gameFinished) { board.printBoard(); System.out.println("Player " + currentPlayer + ", it's your turn."); System.out.print("Enter row (0-2): "); int row = scanner.nextInt(); System.out.print("Enter col (0-2): "); int col = scanner.nextInt(); boolean moveSuccess = board.placeMove(row, col, currentPlayer); if (!moveSuccess) { // Invalid move, ask again continue; } // Check win if (board.hasWinner(currentPlayer)) { board.printBoard(); System.out.println("Player " + currentPlayer + " wins! ๐ŸŽ‰"); gameFinished = true; } // Check draw else if (board.isFull()) { board.printBoard(); System.out.println("It's a draw! ๐Ÿค"); gameFinished = true; } else { // Switch player currentPlayer = (currentPlayer == 'X') ? 'O' : 'X'; } } scanner.close(); System.out.println("Game over. Thanks for playing!"); } }

๐Ÿงฉ How This Relates to System Design (in a Simple Way)

Even though this is a small console program, it still uses good design ideas:

  • Separation of Concerns

    • Board → game state & logic

    • TicTacToeGame → input + game loop

  • Encapsulation

    • Board hides its internal grid and exposes only methods

  • Data Structures

    • 2D array char[][] is the core structure

What is Object-Oriented Programming (OOP)?

 

What is Object-Oriented Programming (OOP)?

A Complete Interview-Oriented Guide with Real-World Examples (Java)


✅ Introduction

Object-Oriented Programming (OOP) is one of the most important programming paradigms used in enterprise applications, backend systems, game engines, mobile apps, financial platforms, and modern software design.

In simple words:

OOP is a programming approach where software is designed using objects.
Each object contains:

  • State (Data) → variables / attributes

  • Behavior (Actions) → methods / functions

Think of a “Car”:

  • State → color, model, speed

  • Behavior → start(), stop(), accelerate()

That’s OOP!


๐Ÿ”ฅ Why OOP?

✔ Better code reusability
✔ Cleaner and modular structure
✔ Easier to maintain and extend
✔ Closer to real-world modelling
✔ Supports scalability

No wonder Java, C++, Python, C#, Kotlin, Swift, etc., are heavily OOP-driven.


๐Ÿงฑ Core Concepts of OOP (4 Pillars)

Every interviewer expects clear understanding of:
1️⃣ Encapsulation
2️⃣ Inheritance
3️⃣ Polymorphism
4️⃣ Abstraction

Let’s understand each with simple language + Java code + interview clarity.


๐Ÿ”’ 1) Encapsulation — Data Protection + Controlled Access

Definition (Interview Answer):
Encapsulation is wrapping data and methods together inside a class and restricting direct access to the data using access modifiers like private, public, etc.
Data is accessed through getters & setters.

๐Ÿฆ Real-World Example

A bank does not allow directly changing balance.
You must deposit() / withdraw() through proper rules.

✅ Java Example

class BankAccount { private double balance; // data hidden public double getBalance() { return balance; } public void deposit(double amount) { if (amount <= 0) { System.out.println("Invalid deposit"); return; } balance += amount; System.out.println("Deposited: " + amount); } }

✔ Data security
✔ Validation
✔ Controlled modification

Interview Tip:
Encapsulation supports data hiding, maintainability, and security.


๐Ÿงฌ 2) Inheritance — Code Reusability + IS-A Relationship

Definition (Interview Answer):
Inheritance allows one class (child/subclass) to acquire properties and behaviors of another class (parent/superclass).
This represents IS-A relationship.

Example:

  • Car IS-A Vehicle

  • Dog IS-A Animal

๐Ÿš— Java Example

class Vehicle { void drive() { System.out.println("Vehicle is moving"); } } class Car extends Vehicle { void playMusic() { System.out.println("Playing music in car"); } }

Output

Vehicle is moving Playing music in car

✔ Code reuse
✔ Logical hierarchy
✔ Reduces duplication


๐ŸŽญ 3) Polymorphism — Same Action, Different Behavior

Definition (Interview Answer):
Polymorphism means one name, many forms.
In Java, it occurs in two ways:

✔ Compile-Time Polymorphism (Method Overloading)

Same method name, different parameters.

class MathUtil { int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } }

✔ Runtime Polymorphism (Method Overriding)

Child class provides its own implementation of a parent method.

class Payment { void pay(double amount) { System.out.println("General Payment"); } } class UpiPayment extends Payment { void pay(double amount) { System.out.println("Paying using UPI"); } }

Key Interview Line:
Runtime polymorphism works using dynamic binding where the decision is made at runtime based on object type.


๐Ÿงฉ 4) Abstraction — Hide Internal Complexity

Definition (Interview Answer):
Abstraction means hiding internal implementation and exposing only essential functionality.

We achieve abstraction using:
✔ Abstract Classes
✔ Interfaces

๐Ÿ”” Real-World Example

When you use a Washing Machine:

  • You press Start()

  • You don't know internal motor logic
    That is abstraction.


✅ Java Example — Interface Based

interface Notification { void send(String message); } class EmailNotification implements Notification { public void send(String message) { System.out.println("Email Sent: " + message); } }

Caller only calls send() — internal working is hidden.


๐Ÿข Real-World Style Example — Food Delivery Mini-System

This simple mini-example shows multiple OOP concepts together:

✔ Encapsulation → order data protected
✔ Polymorphism → multiple payment modes
✔ Abstraction → notification channel hidden
✔ Composition → Restaurant HAS-A Menu

abstract class PaymentMethod { abstract void pay(double amount); } class CardPayment extends PaymentMethod { void pay(double amount) { System.out.println("Paid using Card: " + amount); } } interface Notification { void send(String msg); } class EmailNotification implements Notification { public void send(String msg) { System.out.println("EMAIL: " + msg); } } class Order { private double amount; public Order(double amount) { this.amount = amount; } public double getAmount() { return amount; } }

๐ŸŽฏ OOP Interview Q&A — Quick Punch Answers


❓ What is OOP?

Programming style based on objects that combine data and behavior.


❓ Why OOP?

  • Reusability

  • Maintainability

  • Real-world modelling

  • Security

  • Scalability


❓ What are the 4 pillars of OOP?

Encapsulation, Inheritance, Polymorphism, Abstraction


❓ Difference between Abstraction and Encapsulation?

AbstractionEncapsulation
Hides implementationHides data
Focus on “what” system doesFocus on “how” data is accessed
Done using abstract classes & interfacesDone using private variables + getters/setters

❓ What is IS-A vs HAS-A relationship?

IS-A (Inheritance)
Car IS-A Vehicle

HAS-A (Composition)
Car HAS-A Engine


❓ What is Runtime Polymorphism?

When method call is decided at runtime using overriding.


❓ Can static methods be overridden?

No. They are hidden, not overridden.


๐Ÿ Final Summary

Object-Oriented Programming helps developers build structured, modular, and real-world like applications.

  • Encapsulation → protects data

  • Inheritance → reuses behavior

  • Polymorphism → flexibility

  • Abstraction → hides complexity

Java Queue & Deque — Explained with Simple Examples

 

Java Queue & Deque — Explained with Simple Examples

In Java, Queue and Deque are important data structures widely used in real-world systems like messaging, scheduling, caching, undo/redo, browser history, task execution, and more. They come from the java.util package and follow FIFO / LIFO styles of processing.


✅ 1. What is a Queue in Java?

A Queue stores elements in the order they arrive and processes them in the same order.

๐Ÿ‘‰ Works on: FIFO (First In – First Out)
๐Ÿ“ฆ Example in real life: People standing in a line at a ticket counter.

Queue Interface Hierarchy

Collection | ---> Queue

Common Queue Implementations in Java

ImplementationDescription
LinkedListSimple queue (FIFO)
PriorityQueueOrders elements by priority (NOT FIFO)
ArrayDequeFaster queue without capacity limit

๐Ÿ”น Basic Queue Operations

MethodDescription
add(e) / offer(e)Insert element
remove() / poll()Remove head
element() / peek()View head

๐Ÿ’ก Difference between add() & offer()

  • add() → throws exception if queue is full

  • offer() → returns false if queue is full


๐Ÿงช Example 1 — Simple Queue using LinkedList

import java.util.Queue; import java.util.LinkedList; public class SimpleQueueExample { public static void main(String[] args) { Queue<String> queue = new LinkedList<>(); queue.offer("A"); queue.offer("B"); queue.offer("C"); System.out.println("Queue: " + queue); System.out.println("Peek: " + queue.peek()); System.out.println("Removed: " + queue.poll()); System.out.println("After Removal: " + queue); } }

Output

Queue: [A, B, C] Peek: A Removed: A After Removal: [B, C]

๐Ÿงช Example 2 — PriorityQueue (NOT FIFO)

PriorityQueue orders elements by natural order or custom comparator.

import java.util.PriorityQueue; public class PriorityQueueExample { public static void main(String[] args) { PriorityQueue<Integer> pq = new PriorityQueue<>(); pq.add(30); pq.add(5); pq.add(20); System.out.println(pq.poll()); System.out.println(pq.poll()); System.out.println(pq.poll()); } }

Output

5 20 30

๐Ÿ‘‰ Smallest element gets highest priority.


๐Ÿงช Example 3 — Max Priority Queue

import java.util.*; public class MaxPriorityQueueExample { public static void main(String[] args) { PriorityQueue<Integer> maxPQ = new PriorityQueue<>(Collections.reverseOrder()); maxPQ.add(10); maxPQ.add(50); maxPQ.add(30); while(!maxPQ.isEmpty()) { System.out.println(maxPQ.poll()); } } }

Output

50 30 10

✅ 2. What is a Deque in Java?

Deque = Double Ended Queue
You can insert and remove from BOTH ends.

๐Ÿ‘‰ Works as:

  • Queue (FIFO)

  • Stack (LIFO)

Deque Interface Hierarchy

Collection | Deque

Common Deque Implementation

ImplementationDescription
ArrayDequeMost common + fastest
LinkedListAlso supports Deque

Deque Operations

Insert

Method
addFirst()
addLast()
offerFirst()
offerLast()

Remove

Method
pollFirst()
pollLast()
removeFirst()
removeLast()

Peek

Method
peekFirst()
peekLast()

๐Ÿงช Example 4 — Deque as Queue (FIFO)

import java.util.Deque; import java.util.ArrayDeque; public class DequeAsQueue { public static void main(String[] args) { Deque<String> dq = new ArrayDeque<>(); dq.offerLast("A"); dq.offerLast("B"); dq.offerLast("C"); System.out.println(dq); System.out.println("Removed: " + dq.pollFirst()); System.out.println(dq); } }

Output

[A, B, C] Removed: A [B, C]

๐Ÿงช Example 5 — Deque as Stack (LIFO)

import java.util.ArrayDeque; import java.util.Deque; public class DequeAsStack { public static void main(String[] args) { Deque<Integer> stack = new ArrayDeque<>(); stack.push(10); stack.push(20); stack.push(30); System.out.println(stack); System.out.println("Popped: " + stack.pop()); System.out.println(stack); } }

Output

[30, 20, 10] Popped: 30 [20, 10]

๐Ÿ‘‰ ArrayDeque is recommended instead of Stack class
because Stack is synchronized & slower.


Queue vs Deque — Quick Comparison

FeatureQueueDeque
OrderFIFOFIFO + LIFO
Insert EndsRear onlyBoth ends
Remove EndsFront onlyBoth ends
Common ImplLinkedList, PriorityQueueArrayDeque

When to Use What?

Use CaseBest Choice
Simple FIFO tasksQueue (LinkedList)
Priority-based processingPriorityQueue
Stack replacementDeque
Both stack + queue flexibilityArrayDeque

Conclusion

Java provides powerful Queue and Deque implementations that are widely used in real-world systems for task scheduling, background processing, messaging, caching, and more.

  • Use Queue when order matters (FIFO)

  • Use PriorityQueue when priority matters

  • Use Deque / ArrayDeque when you need both stack + queue behavior

System Design + Java Program for Tic-Tac-Toe (TicTac)

  ๐ŸŽฎ System Design + Java Program for Tic-Tac-Toe (TicTac) In this mini system design, we’ll build a console-based Tic-Tac-Toe game in Java...

Featured Posts