-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
121: Whiteboard Practice: Implementing a Linked List in Ruby
I remember a junior developer I mentored a few years back who completely froze during a technical interview at a high-frequency trading firm. The interviewer asked him to build a basic undo-redo buffer. He instinctively started with a Ruby Array, but the interviewer stopped him immediately, pointing out that shifting elements from the front of a massive array is a performance killer—$O(n)$ time complexity for every single operation. That's when the "Linked List" conversation started. He knew the theory from a textbook, but translating "nodes and pointers" into actual Ruby code on a whiteboard is a completely different beast. That's why we're doing this; it's not about replacing Ruby's powerful Array class in your daily work, but about understanding how to manage memory and references manually when performance constraints demand it.
Building the Node Foundation
At its core, a linked list isn't a single object, but a collection of independent objects called nodes that point to one another. Think of it like a scavenger hunt: you don't have a map of every location, you just have the first clue, and that clue tells you where to find the next one.
In Ruby, we represent this with a simple class. Each node needs two things: the actual data it's holding and a reference to the next node in the sequence. If there is no next node, we just set it to nil.
class Node
attr_accessor :value, :next_node
def initialize(value)
@value = value
@next_node = nil
end
end
I prefer using attr_accessor here because, during a whiteboard session, you want to keep your boilerplate to a minimum. It gives you the getters and setters you need to rewire the list without writing ten different methods.
Managing the Chain
Now that we have a node, we need a way to manage them. The LinkedList class itself doesn't actually hold a list of items; it only holds a reference to the head (the first node). From the head, you can reach any other element in the list by following the next_node references.
Adding an element to the front (prepending) is incredibly efficient—$O(1)$—because you just create a new node and tell it to point to the current head. Appending to the end, however, requires you to "walk" the entire list until you find the node that points to nil.
class LinkedList
attr_reader :head
def initialize
@head = nil
end
def prepend(value)
new_node = Node.new(value)
new_node.next_node = @head
@head = new_node
end
def append(value)
return prepend(value) if @head.nil?
current = @head
current = current.next_node while current.next_node
current.next_node = Node.new(value)
end
end
Notice the while loop in the append method. This is the bread and butter of linked list manipulation. You're essentially surfing through the objects until you hit the end of the line.
Walking the List and Extracting Data
Since you can't access linked list elements by index like you do with array[4], you have to implement your own traversal. Whether you're printing the list or searching for a specific value, the pattern is always the same: start at the head and move to the next_node until you hit nil.
I often see developers try to use recursion for this, which is elegant, but in a production environment (or a strict interview), an iterative approach is safer to avoid stack overflow errors on very long lists.
def print_list
current = @head
while current
print "#{current.value} -> "
current = current.next_node
end
puts "nil"
end
When you're practicing this on a whiteboard, the most common mistake is forgetting to update the current variable inside your loop. If you forget current = current.next_node, you've just created an infinite loop that will crash your program. Always double-check your pointer updates.
📋 Practical Task
Exercise: Implementing a "Remove by Value" Method for a Singly Linked List
In this exercise, you will extend the LinkedList class implemented in the lesson. Your goal is to create a method called remove(value) that searches for the first occurrence of a specific value and removes that node from the list by "stitching" the previous node directly to the next one.
Requirements:
- If the
headcontains the value, theheadshould be updated to the second node. - If the value is in the middle or end of the list, the node preceding the target must have its
next_nodeupdated to skip over the target node. - If the value is not found, the list should remain unchanged.
- The method should return
trueif an element was removed, andfalseotherwise.
# Starter Code
class Node
attr_accessor :value, :next_node
def initialize(value)
@value = value
@next_node = nil
end
end
class LinkedList
attr_reader :head
def initialize
@head = nil
end
def append(value)
return prepend(value) if @head.nil?
current = @head
current = current.next_node while current.next_node
current.next_node = Node.new(value)
end
def prepend(value)
new_node = Node.new(value)
new_node.next_node = @head
@head = new_node
end
# TODO: Implement the remove(value) method here
end
# Test your implementation:
list = LinkedList.new
list.append("A")
list.append("B")
list.append("C")
# list is A -> B -> C -> nil
list.remove("B")
# list should now be A -> C -> nil
There are no comments for now.