{"id":22814354,"url":"https://github.com/devinterview-io/stack-data-structure-interview-questions","last_synced_at":"2026-02-27T18:05:51.778Z","repository":{"id":108758183,"uuid":"332979550","full_name":"Devinterview-io/stack-data-structure-interview-questions","owner":"Devinterview-io","description":"🟣 Stack Data Structure interview questions and answers to help you prepare for your next data structures and algorithms interview in 2024.","archived":false,"fork":false,"pushed_at":"2024-01-06T19:36:00.000Z","size":47,"stargazers_count":11,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-30T22:16:24.727Z","etag":null,"topics":["algorithms","algorithms-and-data-structures","algorithms-and-data-structures-interview-questions","algorithms-interview-questions","coding-interview-questions","data-structures","data-structures-and-algorithms","data-structures-and-algorithms-interview-questions","data-structures-interview-questions","software-architecture-interview","software-architecture-interview-questions","software-developer-interview","software-engineer-interview","stack-data-structure","stack-data-structure-interview-questions","stack-data-structure-questions","stack-data-structure-tech-interview"],"latest_commit_sha":null,"homepage":"https://devinterview.io/","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Devinterview-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-01-26T05:15:25.000Z","updated_at":"2025-03-05T10:30:58.000Z","dependencies_parsed_at":"2024-12-12T13:08:14.026Z","dependency_job_id":"d2ece528-e2e5-43c8-8016-7de4959bc137","html_url":"https://github.com/Devinterview-io/stack-data-structure-interview-questions","commit_stats":null,"previous_names":["devinterview-io/stack-data-structure-interview-questions","devinterview-io/stacks-interview-questions"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Devinterview-io%2Fstack-data-structure-interview-questions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Devinterview-io%2Fstack-data-structure-interview-questions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Devinterview-io%2Fstack-data-structure-interview-questions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Devinterview-io%2Fstack-data-structure-interview-questions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Devinterview-io","download_url":"https://codeload.github.com/Devinterview-io/stack-data-structure-interview-questions/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246385415,"owners_count":20768672,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["algorithms","algorithms-and-data-structures","algorithms-and-data-structures-interview-questions","algorithms-interview-questions","coding-interview-questions","data-structures","data-structures-and-algorithms","data-structures-and-algorithms-interview-questions","data-structures-interview-questions","software-architecture-interview","software-architecture-interview-questions","software-developer-interview","software-engineer-interview","stack-data-structure","stack-data-structure-interview-questions","stack-data-structure-questions","stack-data-structure-tech-interview"],"created_at":"2024-12-12T13:08:21.458Z","updated_at":"2026-02-27T18:05:51.772Z","avatar_url":"https://github.com/Devinterview-io.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# 46 Fundamental Stack Data Structure Interview Questions in 2026\n\n\u003cdiv\u003e\n\u003cp align=\"center\"\u003e\n\u003ca href=\"https://devinterview.io/questions/data-structures-and-algorithms/\"\u003e\n\u003cimg src=\"https://firebasestorage.googleapis.com/v0/b/dev-stack-app.appspot.com/o/github-blog-img%2Fdata-structures-and-algorithms-github-img.jpg?alt=media\u0026token=fa19cf0c-ed41-4954-ae0d-d4533b071bc6\" alt=\"data-structures-and-algorithms\" width=\"100%\"\u003e\n\u003c/a\u003e\n\u003c/p\u003e\n\n#### You can also find all 46 answers here 👉 [Devinterview.io - Stack Data Structure](https://devinterview.io/questions/data-structures-and-algorithms/stack-data-structure-interview-questions)\n\n\u003cbr\u003e\n\n## 1. What is a _Stack_?\n\nA **stack** is a simple data structure that follows the **Last-In, First-Out (LIFO)** principle. It's akin to a stack of books, where the most recent addition is at the top and easily accessible.\n\n### Core Characteristics\n\n- **Data Representation**: Stacks can hold homogeneous or heterogeneous data.\n- **Access Restrictions**: Restricted access primarily to the top of the stack, making it more efficient for certain algorithms.\n\n### Stack Operations\n\n1. **Push**: Adds an element to the top of the stack.\n2. **Pop**: Removes and returns the top element.\n3. **Peek**: Returns the top element without removing it.\n4. **isEmpty**: Checks if the stack is empty.\n5. **isFull** (for array-based stacks): Checks if the stack is full.\n\nAll the above operations typically have a time complexity of $O(1)$, making stack operations highly efficient.\n\n### Visual Representation\n\n![Stack Data Structure](https://firebasestorage.googleapis.com/v0/b/dev-stack-app.appspot.com/o/stacks%2Fstack.png?alt=media\u0026token=74633f0a-83f7-4038-8b82-e10f0d6006b9\u0026_gl=1*1uzhlk1*_ga*OTYzMjY5NTkwLjE2ODg4NDM4Njg.*_ga_CW55HF8NVT*MTY5NjYwNzMxNS4xNDcuMS4xNjk2NjA3NzE2LjUwLjAuMA..)\n\n### Practical Applications\n\n1. **Function Calls**: The call stack keeps track of program flow and memory allocation during method invocations.\n2. **Text Editors**: The undo/redo functionality often uses a stack.\n3. **Web Browsers**: The Back button's behavior can be implemented with a stack.\n4. **Parsing**: Stacks can be used in language processing for functions like balanced parentheses, and **binary expression evaluation**.\n\n5. **Memory Management**: Stacks play a role in managing dynamic memory in computer systems.\n\n6. **Infix to Postfix Conversion**: It's a crucial step for evaluating mathematical expressions such as `2 + 3 * 5 - 4` in the correct precedence order. Stack-based conversion simplifies parsing and involves operators such as `push` and `pop` until the correct order is achieved.\n\n7. **Graph Algorithms**: Graph traversal algorithms such as Depth First Search (DFS) deploy **stacks** as a key mechanism to remember vertices and explore connected components.\n\n### Code Example: Basic Stack\n\nHere is the Python code:\n\n```python\nclass Stack:\n    def __init__(self):\n        self.stack = []\n\n    def push(self, item):\n        self.stack.append(item)\n\n    def pop(self):\n        if not self.is_empty():\n            return self.stack.pop()\n\n    def peek(self):\n        if not self.is_empty():\n            return self.stack[-1]\n\n    def is_empty(self):\n        return len(self.stack) == 0\n\n    def size(self):\n        return len(self.stack)\n```\n\u003cbr\u003e\n\n## 2. Why _Stack_ is considered a _Recursive_ data structure?\n\nA **stack** is considered a **recursive data structure** because its definition is self-referential. At any given point, a stack can be defined as a top element combined with another stack (the remainder).\n\nWhenever an element is pushed onto or popped off a stack, what remains is still a stack. This **self-referential nature**, where operations reduce the problem to smaller instances of the same type, embodies the essence of **recursion**\n\u003cbr\u003e\n\n## 3. What are the primary operations performed on a _Stack_ and their time complexities?\n\nLet's look into the fundamental operations of a **stack** and their associated time complexities.\n\n### Stack Operations and Complexity\n\n- **Push** (Time: $O(1)$): New elements are added at the top of the stack, making this a $O(1)$.\n- **Pop** (Time: $O(1)$): The top element, and the only one accessible, is removed during this $O(1)$ operation.\n- **Peek** (Time: $O(1)$): Viewing the top of the stack doesn't alter its structure, thus taking $O(1)$.\n- **Size** (Time: $O(1)$): Stacks typically keep track of their size, ensuring $O(1)$ performance.\n- **isEmpty** (Time: $O(1)$): Checks for stack emptiness and usually completes in $O(1)$ time.\n\n### Code Example: Stack\n\nHere is the Python code:\n\n  ```python\n  stack = []\nstack.append(1)  # Pushes 1 onto the stack\nstack.append(3)  # Pushes 3 onto the stack\nprint(stack.pop())  # Outputs 3; removes the top element from the stack\nprint(stack[-1])  # Outputs 1; peek at the top element\nprint(len(stack))  # Outputs 1; returns the size of the stack\nprint(not stack)  # Outputs False; checks if the stack is empty\n\u003cbr\u003e\n\n## 4. When should I use _Stack_ or _Queue_ data structures instead of _Arrays/Lists_?\n\n**Queues** and **Stacks** provide structured ways to handle data, offering distinct advantages over more generic structures like **Lists** or **Arrays**.\n\n### Key Features\n\n#### Queues \n\n- **Characteristic**: First-In-First-Out (FIFO)\n- **Usage**: Ideal for ordered processing, such as print queues or BFS traversal.\n\n#### Stacks\n\n- **Characteristic**: Last-In-First-Out (LIFO)\n- **Usage**: Perfect for tasks requiring reverse order like undo actions or DFS traversal.\n\n#### Lists/Arrays\n\n- **Characteristic**: Random Access\n- **Usage**: Suitable when you need random access to elements or don't require strict order or data management.\n\u003cbr\u003e\n\n## 5. What are _Infix_, _Prefix_, and _Postfix_ notations?\n\nIn computer science, **infix**, **prefix**, and **postfix** notations are methods of writing mathematical expressions. While humans generally use infix notation, machines can more efficiently parse prefix and postfix notations.\n\n### Infix, Prefix and Postfix Notations\n\n- **Infix**: Operators are placed between operands. This is the most common notation for humans due to its intuitiveness.\n  \n  Example: $1 + 2$\n\n- **Prefix**: Operators are placed before operands. The order of operations is determined by the position of the operator rather than parentheses.\n  \n  Example: $+ 1 \\times 2 3$ which evaluates to $1 + (2 \\times 3) = 7$\n\n- **Postfix**: Operators are placed after operands. The order of operations is determined by the sequence in which operands and operators appear.\n  \n  Example: $1 2 3 \\times +$ which evaluates to $1 + (2 \\times 3) = 7$\n\n### Relation to Stacks\n\n- **Conversion**: Stacks can facilitate the conversion of expressions from one notation to another. For instance, the Shunting Yard algorithm converts infix expressions to postfix notation using a stack.\n  \n- **Evaluation**: Both postfix and prefix expressions are evaluated using stacks. For postfix:\n  1. Operands are pushed onto the stack.\n  2. Upon encountering an operator, the required operands are popped, the operation is executed, and the result is pushed back.\n\n  For example, for the expression $1 2 3 \\times +$:\n  - 1 is pushed onto the stack.\n  - 2 is pushed.\n  - 3 is pushed.\n  - $\\times$ is encountered. 3 and 2 are popped, multiplied to get 6, which is then pushed.\n  - $+$ is encountered. 6 and 1 are popped, added to get 7, which is then pushed. This 7 is the result.\n\nEvaluating prefix expressions follows a similar **stack-based method** but traverses the expression differently.\n\n### Code Example: Postfix Evaluation\n\nHere is the Python code:\n\n```python\ndef evaluate_postfix(expression):\n    stack = []\n    tokens = expression.split()  # Handle multi-digit numbers\n    for token in tokens:\n        if token.isdigit():\n            stack.append(int(token))\n        else:\n            operand2 = stack.pop()\n            operand1 = stack.pop()\n            stack.append(perform_operation(operand1, operand2, token))\n    return stack[0]\n\ndef perform_operation(operand1, operand2, operator):\n    operations = {\n        '+': operand1 + operand2,\n        '-': operand1 - operand2,\n        '*': operand1 * operand2,\n        '/': operand1 / operand2\n    }\n    return operations[operator]\n\n# Example usage\nprint(evaluate_postfix('1 2 + 3 4 * -'))  # Output: -7\n```\n\u003cbr\u003e\n\n## 6. Explain how _Stacks_ are used in _Function Call management_ in programming languages.\n\nWhen **functions** are called in programming languages, the system typically uses a **call stack** to manage the call sequence and memory allocation. Let's take a look at how this process works.\n\n### The Call Stack\n\nThe **call stack** maintains a record of all the active function calls that a program makes. When a new function is called, it's added to the top of the stack. Once a function finishes its execution, it's removed from the stack, and control returns to the calling function.\n\nThis \"last in, first out\" behavior is well-suited to **stack** data structures.\n\n### How the Call Stack Works\n\n1. **Function Call**: When a function is called, a **stack frame** is created and pushed onto the call stack. This frame contains important information about the state of the function, such as local variables and the return address, which points to the instruction after the function call.\n\n2. **Local Execution**: The CPU executes the instructions within the called function. The function accesses its inputs, processes data, and calls other functions as needed.\n\n3. **Return**: If the called function doesn't make any further function calls, it exits, and its stack frame is removed. Alternatively, if the function makes additional calls, the call stack grows further.\n\n4. **Stack Unwinding**: Once the initial (or other topmost) function call is finished, there are no more functions to execute. The stack then shrinks, starting with the top frame, until it's empty.\n\n### Code Example: Using the Call Stack\n\nHere is code written in Python:\n\n```python\ndef multiply(a, b):\n    result = a * b\n    return result\n\ndef calculate(a, b, c):\n    temp = multiply(b, c)\n    return a + temp\n\nresult = calculate(2, 3, 4)\nprint(result)  # Output: 14\n```\n\nIn this example, when `calculate` is called, it first calls `multiply` and then performs addition. The call stack looks like this during the execution of `calculate`:\n\n1. `calculate` with parameters (2, 3, 4)\n2. `multiply` with parameters (3, 4)\n\nOnce `multiply` completes, its stack frame is removed, and `calculate` continues with the next line of code.\n\n### Benefits and Limitations of Call Stack\n\n#### Benefits\n\n- **Automatic Memory Management**: The call stack automatically allocates memory for local variables and function parameters, simplifying memory management for developers.\n- **Efficiency**: Its simple structure makes it efficient for managing function calls in most programming scenarios.\n\n#### Limitations\n\n- **Size Limitations**: The stack has a fixed memory size allocated at program startup, which can lead to stack overflow errors if the stack grows too large.\n- **No Random Access**: Elements in the stack can only be accessed or removed in a last-in-first-out manner, limiting its use in some algorithms and data structures.\n\u003cbr\u003e\n\n## 7. Describe an application where _Stacks_ are naturally suited over other data structures.\n\n**Stacks** find natural utility in various practical use-cases, such as in text editors for tracking actions and providing the \"undo\" and \"redo\" functionalities.\n\n### Code Example: Undo and Redo Stack\n\nHere is the Python code:\n\n```python\nclass UndoRedoStack:\n    def __init__(self):\n        self._undo_stack = []\n        self._redo_stack = []\n\n    def push(self, action):\n        self._undo_stack.append(action)\n        # When a new action is pushed, the redo stack needs to be reset\n        self._redo_stack = []\n\n    def undo(self):\n        if self._undo_stack:\n            action = self._undo_stack.pop()\n            self._redo_stack.append(action)\n            return action\n\n    def redo(self):\n        if self._redo_stack:\n            action = self._redo_stack.pop()\n            self._undo_stack.append(action)\n            return action\n```\n\n### Stack-Based Undo and Redo Workflow\n\nIn a typical text editor, the user can:\n\n- **Type Text**: Each time new text is entered, it represents an **action** that can be **undone** or **redone**.\n- **Perform Undo/Redo**: The editor navigates through previous actions, whether to reverse or reinstate them.\n\u003cbr\u003e\n\n## 8. Compare _Array-based_ vs _Linked List_ stack implementations.\n\n**Array-based stacks** excel in time efficiency and direct element access. In contrast, **linked list stacks** are preferable for dynamic sizing and easy insertions or deletions.\n\n### Common Features\n\n- **Speed of Operations**: Both `pop` and `push` are $O(1)$ operations.\n- **Memory Use**: Both have $O(n)$ space complexity.\n- **Flexibility**: Both can adapt their sizes, but their resizing strategies differ.\n\n### Key Distinctions\n\n#### Array-Based Stack\n\n- **Locality**: Consecutive memory locations benefit CPU caching.\n- **Random Access**: Provides direct element access.\n- **Iterator Needs**: Preferable if indexing or iterators are required.\n- **Performance**: Slightly faster for top-element operations and potentially better for time-sensitive tasks due to caching.\n- **Push**: $O(1)$ on average; resizing might cause occasional $O(n)$.\n\n#### Linked List Stack\n\n- **Memory Efficiency**: Better suited for fluctuating sizes and limited memory scenarios.\n- **Resizing Overhead**: No resizing overheads.\n- **Pointer Overhead**: Requires extra memory for storing pointers.\n\n### Code Example: Array-Based Stack\n\nHere is the Python code:\n\n```python\nclass ArrayBasedStack:\n    def __init__(self):\n        self.stack = []\n    def push(self, item):\n        self.stack.append(item)\n    def pop(self):\n        return self.stack.pop() if self.stack else None\n```\n\n### Code Example: Linked List Stack\n\nHere is the Python code:\n\n```python\nclass Node:\n    def __init__(self, data=None):\n        self.data = data\n        self.next = None\nclass LinkedListStack:\n    def __init__(self):\n        self.head = None\n    def push(self, item):\n        new_node = Node(item)\n        new_node.next = self.head\n        self.head = new_node\n    def pop(self):\n        if self.head:\n            temp = self.head\n            self.head = self.head.next\n            return temp.data\n        return None\n```\n\u003cbr\u003e\n\n## 9. Implement a _Dynamic Stack_ that automatically resizes itself.\n\n### Problem Statement\n\nImplement a **Dynamic Stack** that automatically resizes itself when it reaches its capacity.\n\n### Solution\n\nResizing a stack involves two main operations: **shrinking** and **expanding** the stack when needed. A common strategy is to **double the stack's size** each time it reaches full capacity and **halve** it when it becomes 25% full, as this provides efficient amortized performance.\n\n#### Key Operations\n\n1. `push(item)`: Add an item to the stack.\n2. `pop()`: Remove and return the top item from the stack.\n3. `is_full()`: Check if the stack is full.\n4. `is_empty()`: Check if the stack is empty.\n5. `expand()`: Double the stack's capacity.\n6. `shrink()`: Halve the stack's capacity.\n\n#### Algorithm Steps\n\n1. Start with an initial capacity for the stack. In this example, it's `2`.\n2. Whenever a `push` operation encounters a full stack, call the `expand` method before the addition.\n3. Whenever a `pop` operation leaves the stack 25% full, call the `shrink` method.\n\nThis ensures the stack dynamically adjusts its size based on the current number of elements.\n\n#### Complexity Analysis\n\n- **Time Complexity**:\n  - `push`: $O(1)$ amortized. Although `expand` can take up to $O(n)$ time, it is only triggered once every $n$ `push` operations, resulting in an average of $O(1)$ per `push`.\n  - `pop`, `is_full`, and `is_empty`: $O(1)$.\n  - `expand` and `shrink`: $O(n)$ in the worst case, but they are infrequently called, so their amortized time is $O(1)$ per operation.\n  \n- **Space Complexity**: $O(n)$ where $n$ is the number of elements in the stack. This accounts for the stack itself and any additional overhead such as the temporary arrays used during resizing.\n\n#### Implementation\n\nHere is the Python code:\n\n```python\nclass DynamicStack:\n    # Initialize the stack with an initial capacity\n    def __init__(self, capacity=2):\n        self.capacity = capacity\n        self.stack = [None] * capacity\n        self.top = -1\n\n    # Push an element to the stack\n    def push(self, item):\n        if self.is_full():\n            self.expand()\n        self.top += 1\n        self.stack[self.top] = item\n\n    # Pop the top element from the stack and return it\n    def pop(self):\n        if self.is_empty():\n            raise IndexError('Stack is empty')\n        item = self.stack[self.top]\n        self.top -= 1\n        if self.top \u003c self.capacity // 4:\n            self.shrink()\n        return item\n\n    # Check if the stack is full\n    def is_full(self):\n        return self.top == self.capacity - 1\n\n    # Check if the stack is empty\n    def is_empty(self):\n        return self.top == -1\n\n    # Double the stack's capacity\n    def expand(self):\n        self.capacity *= 2\n        new_stack = [None] * self.capacity\n        for i in range(self.top + 1):\n            new_stack[i] = self.stack[i]\n        self.stack = new_stack\n\n    # Halve the stack's capacity\n    def shrink(self):\n        self.capacity //= 2\n        new_stack = [None] * self.capacity\n        for i in range(self.top + 1):\n            new_stack[i] = self.stack[i]\n        self.stack = new_stack\n```\n\u003cbr\u003e\n\n## 10. What are the performance implications of a _Fixed-size Array Stack Implementation_?\n\nWhile **fixed-size array stacks** offer simplicity and often better performance for certain operations, such as data caching and real-time processing, several limitation are to be considered.\n\n### Space and Memory Management\n\n- **Limited Capacity**: Fixed-size arrays impose a maximum capacity for stacks, introducing the potential for overflow.\n- **Pre-allocated Memory**: Fixed-size arrays require memory to be allocated in advance for the maximum capacity, leading to potential inefficiencies if this capacity is not fully utilized.\n- **Consistent Size**: Stacks using fixed-size arrays do not auto-resize, leading to inefficient memory use if the actual size of the stack varies significantly from the allocated size.\n\n### Time Complexity of Fixed-Size Array Stacks\n\n- **Push Operation**: $O(1)$ (constant time), until the array is full and a resizing operation is initiated which leads to $O(n)$ in the worst case.\n- **Pop Operation**: $O(1)$ - simple memory deallocation or index decrement.\n- **Peek Operation**: $O(1)$ - equivalent to pop.\n- **Search Operation**: $O(n)$ - in the worst case, when the element is at the top of the stack or not present.\n\n### Data Structure Sensitivity\n\n- **Space Sensitivity**: Stacks using a fixed-size array have predictable, constant memory requirements.\n- **Performance Sensitivity**: While operations on non-fixed size stacks might have $O(1)$ average-case time complexity parameters, certain operations on fixed-size stacks can degrade in worst-case scenarios, justifying the $O(n)$ worst-case complexity.\n\n### Practical Applications\n\n- **Real-Time Systems**: Fixed-size arrays can be preferable for applications with strict timing requirements, as memory operations are more deterministic.\n- **Embedded Systems**: In resource-constrained environments, using fixed-size arrays can help manage memory more efficiently due to their predictable memory requirements.\n- **Cache Systems**: The use of fixed-size arrays is significant in caches, where the predictability of space requirements is essential.\n\n### Code Example: Fixed-Size Stack\n\nHere is the Python code:\n\n```python\nclass FixedSizeStack:\n    def __init__(self, capacity=10):\n        self.stack = [None] * capacity\n        self.top = -1\n\n    def push(self, value):\n        if self.top == len(self.stack) - 1:\n            print(\"Stack is full, cannot push.\")\n            return\n        self.top += 1\n        self.stack[self.top] = value\n\n    def pop(self):\n        if self.top == -1:\n            print(\"Stack is empty, cannot pop.\")\n            return\n        value = self.stack[self.top]\n        self.top -= 1\n        return value\n\n    def peek(self):\n        if self.top == -1:\n            print(\"Stack is empty, no top element.\")\n            return\n        return self.stack[self.top]\n\n    def is_empty(self):\n        return self.top == -1\n\n    def is_full(self):\n        return self.top == len(self.stack) - 1\n```\n\u003cbr\u003e\n\n## 11. Design a _Stack_ that supports _Retrieving_ the min element in _O(1)_.\n\n### Problem Statement\n\nThe goal is to design a **stack** data structure that can efficiently retrieve both the minimum element and the top element in $O(1)$ time complexity. \n\n### Solution\n\nTo meet the time complexity requirement, we'll maintain two stacks:\n\n1. **Main Stack** for standard stack functionality.\n2. **Auxiliary Stack** that keeps track of the minimum element up to a given stack position.\n\n#### Algorithm Steps\n\n1. **Pop** and **Push**\n   - For each element $e$ in the **Main Stack**, check if it's smaller than or equal to the top element in the **Auxiliary Stack**. If $e$ is the new minimum, push it onto both stacks.\n\n2. **Minimum Element Retrieval**: The top element of the **Auxiliary Stack** will always be the minimum element of the main stack.\n\n#### Complexity Analysis\n\n- **Time Complexity**: $O(1)$ for all operations.\n- **Space Complexity**: $O(N)$, where $N$ is the number of elements in the stack.\n\n#### Implementation\n\nHere is the Python code:\n\n```python\nclass MinStack:\n    def __init__(self):\n        self.stack = []\n        self.min_stack = []\n\n    def push(self, element):\n        self.stack.append(element)\n        if not self.min_stack or element \u003c= self.min_stack[-1]:\n            self.min_stack.append(element)\n\n    def pop(self):\n        if not self.stack:\n            return None\n        top = self.stack.pop()\n        if top == self.min_stack[-1]:\n            self.min_stack.pop()\n        return top\n\n    def top(self):\n        return self.stack[-1] if self.stack else None\n\n    def getMin(self):\n        return self.min_stack[-1] if self.min_stack else None\n```\n\u003cbr\u003e\n\n## 12. How can you design a _Stack_ to be thread-safe?\n\nEnsuring **thread safety** in a traditional stack, where operations are based on a last-in, first-out (LIFO) approach, can be achieved through a variety of techniques. I will give examples of three different approaches here:\n\n1. **Locking Mechanism**: Where a thread synchronizes access through synchronization techniques like locks.\n\n2. **Non-Blocking Mechanism**: Using atomic operations without explicit locks.\n\n3. **Data Structure Selection**: Choosing inherently thread-safe data structures that mimic stack operations.\n\n### Locking Mechanism\n\nThis approach synchronizes operations on the stack using a lock. While a thread holds the lock, other threads are blocked from entering critical sections. \n\nHere is the Python code:\n\n  ```python\n  import threading\n  \n  class LockedStack:\n      def __init__(self):\n          self.stack = []\n          self.lock = threading.Lock()\n  \n      def push(self, item):\n          with self.lock:\n              self.stack.append(item)\n  \n      def pop(self):\n          with self.lock:\n              if self.stack:\n                  return self.stack.pop()\n              return None\n  ```\n\n### Non-Blocking Mechanism\n\nThis approach uses atomic operations on **primitive data types**, which are guaranteed to happen without interruption.\n\nHere is the Python code for this approach:\n\n  ```python\n  import queue\n  import threading\n  \n  def non_blocking_push(q, item):\n      while True:\n          old_q = q.queue\n          new_q = old_q.copy()\n          new_q.append(item)\n  \n          if q.queue == old_q:\n              q.queue = new_q\n              return\n\t\t  \n  def non_blocking_pop(q):\n      while True:\n          old_q = q.queue\n          new_q = old_q.copy()\n          if q.queue:\n              new_q.pop()\n              if q.queue == old_q:\n                  q.queue = new_q\n                  return q.queue[-1] \n      return None\n\t\t  \n  q = queue.LifoQueue()\n  \n  threading.Thread(target=non_blocking_push, args=(q, 1)).start()\n  threading.Thread(target=non_blocking_push, args=(q, 2)).start()\n  threading.Thread(target=non_blocking_pop, args=(q,)).start()\n  ```\n\n\nFor the **Multi-threaded** Setup, you can run this Python code:\n\n  ### Data Structure Selection\n\n  Some **container classes**, like the LifoQueue in Python, are inherently designed to be thread-safe, thus making their contents and operations secure in a multi-threaded environment.\n\n  Here is the Python code:\n\n  ```python\n  import queue\n  import threading\n  \n  q = queue.LifoQueue()\n  \n  q.put(1)\n  q.put(2)\n  print(q.get())\n  print(q.get())\n  ```\n\u003cbr\u003e\n\n## 13. Implement a _Stack_ with a _Find-Middle_ operation in _O(1)_ time.\n\n### Problem Statement\n\nThe task is to design a **_stack_** data structure that supports **`push`**, **`pop`**, **`findMiddle`**, and **`deleteMiddle`** operations, all in constant $O(1)$ time complexity.\n\n### Solution\n\nWe can solve this challenge using a doubly linked list where each node also includes a pointer to the middle node. This solution, though not built on arrays, has a clear control flow and keeps a consistent time complexity.\n\n#### Algorithm Steps\n\n1. Initialize an empty stack and set `middle` to `NULL`.\n2. During the push operation, update the `middle` pointer based on the current number of nodes. If the number of nodes is odd, `middle` moves up, otherwise, it stays at the same position.\n3. The pop operation, on top of removing the item, also adjusts the `middle` pointer if the removed node was pointing to it.\n4. `findMiddle` and `deleteMiddle` simply involve accessing or manipulating the node that `middle` points to.\n\n#### Complexity Analysis\n\n- **Time Complexity**:\n  - `push`: $O(1)$ -  Constant time for every node insertion.\n  - `pop`: $O(1)$ -  Always removes the top node in a constant time regardless of the stack size.\n  - `findMiddle` and `deleteMiddle`: $O(1)$ - Directly accessed through the `middle` pointer.\n- **Space Complexity**: $O(n)$ - Additional space is required for the pointers associated with each node.\n\n#### Implementation\n\nHere is the Python code:\n\n```python\nclass DLLNode:\n    def __init__(self, x):\n        self.data = x\n        self.next = None\n        self.prev = None\n\nclass MyStack:\n    def __init__(self):\n        self.head = None\n        self.mid = None\n        self.count = 0\n\n    def push(self, x):\n        new_node = DLLNode(x)\n        new_node.next = self.head\n\n        if self.count == 0:\n            self.mid = new_node\n        else:\n            self.head.prev = new_node\n            if self.count % 2 != 0:\n                self.mid = self.mid.prev\n\n        self.head = new_node\n        self.count += 1\n\n    def pop(self):\n        if self.count == 0:\n            print(\"Stack is empty\")\n            return\n\n        item = self.head.data\n        self.head = self.head.next\n\n        if self.head is not None:\n            self.head.prev = None\n\n        self.count -= 1\n\n        # Update mid if the removed node was the middle one\n        if self.count % 2 == 0:\n            self.mid = self.mid.next\n\n        return item\n\n    def findMiddle(self):\n        if self.count == 0:\n            print(\"Stack is empty\")\n            return\n        return self.mid.data\n\n    def deleteMiddle(self):\n        if self.count == 0:\n            print(\"Stack is empty\")\n            return\n        temp = self.mid\n        self.mid.prev.next = self.mid.next\n        self.mid.next.prev = self.mid.prev\n        if self.count % 2 != 0:\n            self.mid = self.mid.prev\n        else:\n            self.mid = self.mid.next\n        del temp\n```\n\u003cbr\u003e\n\n## 14. Implement a _Linked List_ using _Stack_.\n\n### Problem Statement\n\nThe task is to implement **linked list** using a **stack**.\n\n### Solution\n\n**Linked lists** are usually built with nodes, each containing a value and a pointer to the next node. However, you can also **simulate linked lists using stacks**, which follow a \"last in, first out\" (LIFO) mechanism.\n\n#### Algorithm Steps\n\n1. **Initialize**: Two stacks: `list_stack` and `temp_stack`.\n2. **Add to Head**: Push to `list_stack`.\n3. **Remove from Head**: Pop from `list_stack`.\n4. **Insert**: Pop items to `temp_stack` until the insertion point, then push the new item and the `temp_stack` items back to `list_stack`.\n5. **Delete**: Similar to insert but pop the node to be deleted before pushing items back.\n\n#### Complexity Analysis\n\n- **Time Complexity**: \n  - Add to Head: $O(1)$\n  - Remove from Head: $O(1)$\n  - Insert at position $k$: $O(k)$\n  - Delete at position $k$: $O(k)$\n\n- **Space Complexity**: \n  - $O(n)$ for `list_stack`, where $n$ is the number of elements.\n  - $O(k)$ for `temp_stack` during insert or delete operations at position $k$, with an overall space complexity still being $O(n)$ in the worst case (when $k = n$).\n\n#### Implementation\n\nHere is the Python code:\n\n```python\nclass LinkedListStack:\n    def __init__(self):\n        self.list_stack, self.temp_stack = [], []\n\n    def push(self, data):\n        self.list_stack.append(data)\n\n    def pop(self):\n        return self.list_stack.pop()\n\n    def insert(self, data, pos):\n        while pos:\n            self.temp_stack.append(self.list_stack.pop())\n            pos -= 1\n        self.list_stack.append(data)\n        while self.temp_stack:\n            self.list_stack.append(self.temp_stack.pop())\n\n    def delete(self, pos):\n        while pos:\n            self.temp_stack.append(self.list_stack.pop())\n            pos -= 1\n        self.list_stack.pop()\n        while self.temp_stack:\n            self.list_stack.append(self.temp_stack.pop())\n\n    def display(self):\n        print(self.list_stack)\n\n# Example\nll_stack = LinkedListStack()\nll_stack.push(1)\nll_stack.push(2)\nll_stack.push(3)\nll_stack.insert(10, 1)\nprint(\"Before deletion:\")\nll_stack.display()\nll_stack.delete(2)\nprint(\"After deletion:\")\nll_stack.display()\n```\n\u003cbr\u003e\n\n## 15. Implement Doubly Linked List using Stacks with min complexity.\n\n### Problem Statement\n\nThe task is to implement a **Doubly LinkedList** using **Stacks**.\n\n### Solution\n\nUsing two stacks, `forwardStack` and `backwardStack`, we can emulate a **doubly linked list**.\n\n- **Insertion**:\n  - Beginning: Transfer elements from `backwardStack` to `forwardStack`, then push the new element onto `backwardStack`.\n  - End: Transfer from `forwardStack` to `backwardStack` and push the new element onto `backwardStack`.\n\n- **Traversal**:\n  - Forward: Pop from `backwardStack`, push onto `forwardStack`.\n  - Backward: Pop from `forwardStack`, push onto `backwardStack`.\n\n- **Deletion**: Traverse the needed stack to locate and remove the desired element, whether it's a specific item or the first/last entry.\n\n#### Complexity Analysis\n\n- **Time Complexity**: \n  - Operations like `insertInBeginning`, `insertAtEnd`, `moveForward`, `moveBackward`, `delete`, `deleteFirst`, and `deleteLast` have a worst-case time complexity of $O(n)$ due to the potential full traversal of a stack.\n  \n- **Space Complexity**: $O(n)$, where $n$ is the number of nodes in the doubly linked list, primarily occupied by the two stacks.\n\n#### Implementation\n\nHere is the Java code:\n\n```java\nimport java.util.Stack;\n\nclass DoubleLinkedList {\n    private Stack\u003cInteger\u003e forwardStack;\n    private Stack\u003cInteger\u003e backwardStack;\n\n    public DoubleLinkedList() {\n        forwardStack = new Stack\u003c\u003e();\n        backwardStack = new Stack\u003c\u003e();\n    }\n\n    public void insertInBeginning(int data) {\n        // Move all elements to forwardStack to insert at the beginning\n        while (!backwardStack.isEmpty()) {\n            forwardStack.push(backwardStack.pop());\n        }\n        backwardStack.push(data);\n    }\n\n    public void insertAtEnd(int data) {\n        // Move all elements to backwardStack to insert at the end\n        while (!forwardStack.isEmpty()) {\n            backwardStack.push(forwardStack.pop());\n        }\n        backwardStack.push(data);\n    }\n\n    public void moveForward() {\n        if (backwardStack.isEmpty()) {\n            System.out.println(\"No more elements to move forward.\");\n            return;\n        }\n        System.out.println(\"Moving forward: \" + backwardStack.peek());\n        forwardStack.push(backwardStack.pop());\n    }\n\n    public void moveBackward() {\n        if (forwardStack.isEmpty()) {\n            System.out.println(\"No more elements to move backward.\");\n            return;\n        }\n        System.out.println(\"Moving backward: \" + forwardStack.peek());\n        backwardStack.push(forwardStack.pop());\n    }\n\n    public void delete(int data) {\n        Stack\u003cInteger\u003e tempStack = new Stack\u003c\u003e();\n        boolean deleted = false;\n\n        while (!backwardStack.isEmpty()) {\n            if (backwardStack.peek() == data \u0026\u0026 !deleted) {\n                backwardStack.pop();\n                deleted = true;\n                break;\n            }\n            tempStack.push(backwardStack.pop());\n        }\n\n        while (!tempStack.isEmpty()) {\n            backwardStack.push(tempStack.pop());\n        }\n\n        if (!deleted) {\n            System.out.println(\"Element not found.\");\n        }\n    }\n\n    public void deleteFirst() {\n        if (backwardStack.isEmpty()) {\n            System.out.println(\"List is empty.\");\n            return;\n        }\n        backwardStack.pop();\n    }\n\n    public void deleteLast() {\n        while (!forwardStack.isEmpty()) {\n            backwardStack.push(forwardStack.pop());\n        }\n        if (!backwardStack.isEmpty()) {\n            backwardStack.pop();\n        } else {\n            System.out.println(\"List is empty.\");\n        }\n    }\n}\n\n```\n\u003cbr\u003e\n\n\n\n#### Explore all 46 answers here 👉 [Devinterview.io - Stack Data Structure](https://devinterview.io/questions/data-structures-and-algorithms/stack-data-structure-interview-questions)\n\n\u003cbr\u003e\n\n\u003ca href=\"https://devinterview.io/questions/data-structures-and-algorithms/\"\u003e\n\u003cimg src=\"https://firebasestorage.googleapis.com/v0/b/dev-stack-app.appspot.com/o/github-blog-img%2Fdata-structures-and-algorithms-github-img.jpg?alt=media\u0026token=fa19cf0c-ed41-4954-ae0d-d4533b071bc6\" alt=\"data-structures-and-algorithms\" width=\"100%\"\u003e\n\u003c/a\u003e\n\u003c/p\u003e\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevinterview-io%2Fstack-data-structure-interview-questions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevinterview-io%2Fstack-data-structure-interview-questions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevinterview-io%2Fstack-data-structure-interview-questions/lists"}