liveBook · Manning

Chapter 2: Immutable stacks and queues

What you will learn in chapter 2 of Fabulous Adventures in Data Structures and Algorithms

This chapter introduces a progression from immutable linked lists to stacks, queues, undo-redo systems, mutable wrappers, and Hughes lists while comparing their performance characteristics.

After this chapter you will be able to

What it builds on

Section by section

Introduction

This chapter introduces a progression from immutable linked lists to stacks, queues, undo-redo systems, mutable wrappers, and Hughes lists while comparing their performance characteristics.

Key terms: immutable linked list, immutable stack, immutable queue, undo-redo, mutable interface, Hughes list, interface, implementation, time complexity, space complexity

2.1 Why immutability?

This brief introduction poses the question of why immutable data structures are often preferable to otherwise equivalent mutable ones.

Key terms: mutable, immutable data structures

2.1.1 Correctness

This subsection explains that immutability preserves previously deduced facts, preventing stale-state bugs and making code easier to test, debug, and maintain.

Key terms: immutable data structures, immutable object, mutated data structure, cached fact, out-of-date fact, Roslyn

2.1.2 Historical preservation

This subsection shows how immutability preserves trustworthy past states and histories.

Key terms: immutability, ledger, historical record, source-control systems, rollback

2.1.3 Security

This subsection shows how mutable inputs enable a time-of-check/time-of-use security attack and why immutability is a useful defense-in-depth mitigation.

Key terms: mutable data, TOCTOU, time of check/time of use, high-trust code, low-trust code, ResourceName, immutable, mutable, defense in depth, in flight

2.1.4 Safer multithreading

This subsection explains why immutable objects make shared data safer in multithreaded code.

Key terms: shared mutable state, races, immutable objects, locks, thread scheduling

2.1.5 Memoization for time performance

This subsection explains why memoizing expensive computations on immutable data structures can trade memory for faster repeated queries.

Key terms: immutability, memoization, cache, function arguments, results

2.1.6 Persistence for space performance

This section explains why immutable data structures can use less memory than mutable ones when both old and changed versions must coexist, through persistence and internal-state reuse.

Key terms: persistent, immutable data structures, internal state, mutable data structure, array, changed version, unchanged version

2.1.7 The functional programming attitude

This subsection frames immutable data structures within the functional programming mindset and argues for pragmatically mixing functional and imperative styles in languages such as C#.

Key terms: immutable data structures, purely functional, functional programming, mutations, side effects, imperative programming, line-of-business languages, C#, functional mindset, object-oriented programming

2.2 An immutable stack

This section designs and implements an immutable stack ADT as a persistent linked structure, then analyzes its behavior and performance.

Key terms: abstract data type, ADT, immutable stack, Push, Peek, Pop, null object pattern, singleton, empty stack, nonempty stack

2.3 A covariant immutable stack

This section shows why mutable generic collections cannot safely be covariant and how an immutable stack can gain covariance by moving Push out of its interface.

Key terms: generic covariance, covariant interface, output positions, reference type, IEnumerable<T>, IList<T>, Add, IList<Tiger>, IList<Animal>, Tiger

2.4 A queue, a queue, an immutable queue

This section implements a persistent immutable FIFO queue with two immutable stacks and analyzes its ordinary, worst-case, and claimed amortized costs.

Key terms: queue, Enqueue, Dequeue, Peek, IsEmpty, least recently enqueued, enqueue stack, dequeue stack, invariant, persistent

2.5 Mutable wrappers

This section shows how a mutable wrapper can provide convenient updates while retaining immutable snapshots of an underlying data structure.

Key terms: mutable wrapper, immutable data structure, private field, underlying immutable object, initial immutable object, Freeze method, immutable snapshot, mutable builder

2.6 Undo and redo

This introduction frames undo-redo for a mutable queue as a nontrivial problem and previews alternative approaches before presenting the preferred mutable-over-immutable solution.

Key terms: undo-redo logic, mutable queue, enqueue, dequeue, peek, mutable-over-immutable wrapper, command pattern

2.6.1 Create an army of clones

This subsection presents clone-based undo/redo for a mutable queue and explains why its time and space costs make it impractical.

Key terms: working queue, undo stack, redo stack, clone operation, O(n) time, O(n) extra space, O(n²)

2.6.2 Use a different data structure and the command pattern

This subsection implements undo and redo over mutable state by using a deque plus stacks of inverse commands, then explains why this command-based approach becomes complex.

Key terms: deque, EnqueueLeft, DequeueLeft, EnqueueRight, DequeueRight, working deque, undo stack, redo stack, command, command pattern

2.6.3 Undo and redo with mutable-over-immutable data structures

This section shows how a reusable mutable undo-redo helper can add cheap history navigation to immutable wrappers, while analyzing the persistence benefits and repeated worst-case costs of an immutable queue.

Key terms: UndoRedo<T>, mutable-over-immutable wrapper, undo stack, redo stack, immutable type, O(1), O(n), stack nodes, persistence, immutable queue

2.7 The Hughes list: Build it cheap, pay for it later

Introduces the Hughes list as a John Hughes–invented difference list whose cheap pushes at either end trade off against expensive top access and removal.

Key terms: Hughes list, pushing, peeking, popping, time complexity, John Hughes, d list, difference list

2.7.1 Reverse redux

This subsection refactors stack reversal around ReverseOnto, derives concatenation and tail append from it, and explains their linear costs.

Key terms: ReverseOnto, Reverse, tail, stack, Concatenate, Append, one-item stack, O(n), right stack, left stack

2.7.2 Currying and partial application

This subsection introduces currying and partial application in C#, including the idea that a curried function can retain a bound value for later use.

Key terms: currying, curried function, higher-order function, partial application, bound, left argument, bound value, curried, adder, add4

2.7.3 Implementing the Hughes list

This section implements a Hughes list as a delegate that represents deferred stack concatenation and derives its basic list operations from function composition.

Key terms: Hughes list, Concat, delegate, partial application, immutable stack, concatenation, function construction, deferred work, identity function, empty Hughes list

2.7.4 Complexity of the Hughes list

This section analyzes the Hughes list’s constant-time construction operations and linear-time costs when its deferred delegate is evaluated.

Key terms: Hughes list, Make, IsEmpty, FromStack, Concatenate, Single, Peek, Pop, enumeration, ToStack

2.7.5 Where is the data in this data structure?

This subsection reveals that a Hughes list’s delegate reference hides a closure-based binary graph whose traversal builds the resulting stack.

Key terms: Hughes list, delegate, closure class, lambda body, captured values, concatenation, hl1, hl2, binary graph, left child

2.7.6 A couple more performance considerations

This section evaluates Hughes-list performance beyond ordinary time and memory complexity, explaining its stack-depth risk, why Pop remains linear, and when its deferred construction is worthwhile.

Key terms: stack depth, call stack, ToStack, stack-overflow exception, Hughes list, Pop, IsEmpty, reference comparison, deferred Pop, HList<int>.Empty

2.7.7 Reverse a linked list

Shows that a Hughes list can represent reversing a stack in constant time by deferring the actual reversal until conversion back to a stack.

Key terms: Hughes list, ReverseOnto, FromStack, ToStack, delegate

2.8 Summary

This summary recaps why immutable data structures are safer and can be efficient, how they simplify undo-redo, and how Hughes lists defer costs with trade-offs.

Where this is used later

Concepts explained in this chapter

← Chapter 1: Starting a fabulous adventure Chapter 3: An immutable deque →

These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.