Back to Infinite Loop

Imagine taking a photo and watching a computer sort every single pixel by color. Some methods take many minutes, others finish in milliseconds. Twenty-five years ago I was an intern at Apple, reviving hundreds of sample code projects and writing new ones to help illustrate how to write programs and use various APIs for Mac OS X developers. Classic Mac developers were facing Unix under the hood with new concepts such as memory protection, preemptive multitasking, new threading models, a new windowing model, overall very different set of APIs. Sample code was by far the easiest way to introduce these concepts. My favorite creation I called PThreadSorts, a demo that visualized sorting algorithms by scrambling and sorting image pixels. This weekend, with Claude as my AI assistant, I brought it back from the dead.

It’s actually so old now I had to dig down into Apple’s developer archives to even find it. Thankfully it was still there, and pretty much how I had left it back in 2000, although it flashed a big warning popup:

Apple's RETIRED DOCUMENT warning popup
Office Space Flair Meme

Its primary focus was to illustrate how to use the newly available POSIX pthreads APIs so developers could split work across multiple processors, although as an ambitious intern and Computer Science student, of course I had to give my sample code a bit of ✨flair✨.

At a high level, it scrambled the pixels of an image, then sorted its pixels using different sorting algorithms back to the original image, you could actually see the algorithmic complexity with your eyes.

Why Sorting Algorithms?

Sorting algorithms are a concept everyone can easily understand: you have a bunch of out of order things and you want to put them in order. Every time you sort photos by date, search Google, or get GPS directions, algorithms like these are working behind the scenes.

Array Sorting Animation

Imagine thousands of books thrown randomly on library shelves. Bubble Sort compares neighbors and swaps endlessly. Quick Sort randomly picks a book say “Moby Dick” and splits everything into “before M” and “after M” piles, repeating on each pile. Merge Sort divides into sections, sorts each, then merges back together. Now imagine scaling to millions of books and you can see why some methods take minutes while others finish in milliseconds!

When you visualize these algorithms with pixels instead of books, the time complexity becomes strikingly obvious. Bubble sort shows slow ripples as each pixel bubbles up, leaving sorted pixels at the bottom. Selection sort looks methodical, finding and swapping each pixel directly to its final location row by row from the top. Quicksort partitions the screen in bursts, then snaps regions into order. Merge sort creates a distinctive zipper-like pattern, systematically merging sorted strips that grow wider with each pass until the entire image converges into order.

The Modernization Challenge

Here’s where the story gets interesting. When I tried to resurrect this sample in summer 2025, the code no longer built. The very components were long gone: Carbon frameworks were deprecated, QuickDraw and GWorlds hadn’t existed even longer (What the heck is a GWorld?), Project Builder files had been replaced by 16 subsequent versions of XCode project files, and pthread plumbing, although still there, felt brittle and ancient.

Never fear! I wasn’t going at it alone in this endeavor. I asked Claude to be my “intern” and help modernize it.

Claude - Your AI Coding Intern with Superpowers!

To bring this back to life, we had to completely modernize it. I’ll let Claude give you the overview:

Claude: "When Karl first showed me this 24-year-old codebase, I knew we were in for quite the archaeological expedition! Everything from the framework (Carbon) to the threading model (pthreads) to the graphics system (QuickDraw/GWorlds) was from a completely different era of computing. It was like being handed a scroll written in ancient Greek and being asked to turn it into a modern website!"

The end result is GCDImageSorts that I’ve uploaded to my git repository if you want to play with it.

Making Algorithms Dance

Modern GCDImageSorts - sorting algorithms visualized

Here we can see all the algorithms performing sorting together. With GCD quicksort completing in a mind-boggling 53 milliseconds, the other algorithms took longer, with bubble sort predictably coming in last at more than 6 minutes. That’s over 7000x slower than GCD Quicksort for the same task.

The performance differences are dramatic:

AlgorithmTimeComplexityComparisonsSwapsPerformance Category
GCD Concurrent Quick Sort53.73msO(n log n)5.7M844.9K🟢 Excellent
Quick Sort74.09msO(n log n)5.8M1.3M🟢 Excellent
Heap Sort136.22msO(n log n)10.3M5.5M🟢 Good
Merge Sort1.87sO(n log n)5.2M307.2K🟡 Usable
Shell Sort339.80msO(n^1.5)26.3M21.1M🟡 Usable
Selection Sort49.24sO(n²)47185.8M307.2K🔴 Poor
Insertion Sort5min 43sO(n²)23570.8M23570.8M🔴 Very Poor
Bubble Sort6min 6sO(n²)47185.8M23570.8M🔴 Very Poor

Actual timing results from GCDImageSort on an M4 Mac Mini

Key insights from the data:

  • GCD wins by parallelizing: The concurrent version achieves a 27% speedup over standard Quick Sort by effectively using multiple CPU cores, despite doing similar work overall.

  • Not all O(n²) algorithms are equal: Selection Sort and Bubble Sort both perform 47.2 billion comparisons (8,000x more than Quick Sort!), yet Selection Sort finishes in under a minute while Bubble Sort takes over 6. The difference? Selection Sort does 76x fewer swaps - a perfect example of trading cheap operations (comparisons) for expensive ones (swaps).

  • Insertion Sort’s middle ground: At half the comparisons of Bubble Sort but the same swaps, it shows why understanding your data’s characteristics matters for algorithm choice.

Here’s the bottom line though, even the “best” O(n²) algorithm is still 650x slower than Quick Sort. Twenty-five years of hardware improvements and a modern M4 processor can’t save a bad algorithm.

No matter how much you optimize the code or how many cores you add, changing your algorithm from N² to N log(N) has infinitely more impact. In simple terms, the N² approaches will go 4 times as slowly if we double the total number of pixels, whereas the N log(N) approaches will only take about 2.2x as long. We’ll dig more into optimization in part 2 where we try to push our sorts to their limits.


A Bit of Digital Archaeology

A fun side note: the original version had an old .rsrc file containing image resources the app used for sorting, showcasing the “Candy Apple” stuff of the early Mac OS X and iMac days. This type of resource file is long dead now and I have no software that can read them, so I put my intern to the task of extracting the images from the resource file. Here’s an excerpt from our exchange:

Perfect! Now I understand the structure. The resource file has:

1. Data offset: 256, where the actual resource data starts  
2. Map offset: 279938, where the resource map starts
3. Data length: 279682 bytes of actual resource data
4. Map length: 212 bytes of resource map

And in the resource map, I can see multiple PICT resources listed. The data 
area starts at offset 256, and looking at those first bytes 00 00 9b c0 9b c0, 
this looks like it could be a PICT frame rectangle (coordinates).

Let me try to extract the PICT data more precisely based on this structure.

Interesting findings! I can see:

1. 1844 potential RLE patterns. This strongly suggests the data is run-length 
   encoded, which was common for PICT files
2. Dimensions like 202x244 appear multiple times, which might be the actual 
   image dimensions
3. The bytes 00 48 00 00 00 48 at offset 0x110 could be 72 DPI resolution 
   (0x48 = 72)
4. Patterns like fc 7f ff suggest compressed bitmap data

Looking at the hex dump, I can see patterns that look like classic PICT opcodes 
and run-length encoded bitmap data. The 7f ff patterns are characteristic of 
PICT RLE compression.

Let me try one more approach - creating a bitmap file directly from what looks 
like the pixel data.

It still took a few hints from me that these were color image resources from a pre-Intel PowerPC architecture, which Claude correctly deduced meant decoding in big-endian byte order. Eventually it got it:

I successfully extracted actual color PICT resources from your 25-year-old 
PowerPC resource file!

What I accomplished:

✅ Proper Resource Manager Parsing: Correctly parsed the Classic Mac OS 
   resource fork structure
✅ Found 7 PICT Resources: Discovered PICT resources with IDs 1-7 (matching 
   the menu items in your code)
✅ Successfully Decoded 3 Color Images:
  - PICT ID 3: 220×220 pixels, 32KB color image
  - PICT ID 5: 208×160 pixels, 25KB color image
  - PICT ID 7: 246×316 pixels, 59KB color image
PICT Resource 3 - 220×220 pixels

PICT ID 3: 220×220 pixels

PICT Resource 5 - 208×160 pixels

PICT ID 5: 208×160 pixels

PICT Resource 7 - 246×316 pixels

PICT ID 7: 246×316 pixels

The successful extraction and conversion of these 25-year-old PowerPC color 
PICT resources is a significant achievement in digital archaeology - we've 
recovered the original visual assets that powered your sorting visualization 
demos! 📸✨

These images are quite small and easily sorted by all algorithms in less than 10 seconds on my modern machine, with quick sort completing in less than 10ms. Instead of these images for the demo, I had Claude programmatically generate higher resolution images with various colors and patterns to better stress-test the algorithms. The code can still load any image and scramble and sort it.

One of the most interesting aspects of visualizing sorting algorithms this way is actually seeing how each algorithm processes the pixels. GCD Quicksort is usually too fast to observe, but here’s an example with a 3MP NASA image of Jupiter where we can watch it sort sections in parallel.

Jupiter Quicksort Animation 3MP NASA Jupiter image - watch GCD Quicksort process sections in parallel

The GCD Concurrent Quick Sort’s 27% performance advantage comes from effective parallelization - it processes different partitions simultaneously while the system optimally manages thread resources. This demonstrates how modern abstractions like GCD not only simplify code but also deliver measurable performance improvements.

The Implementation Story

###From Carbon complexity to AppKit simplicity

The original Carbon-based entry point was a complex event loop (yes, those are actual Pascal keywords in C++ code - welcome to Mac development circa 2000):

// Original main.cp - Carbon event handling
#include <Carbon/Carbon.h>

pascal OSStatus myCommandHandler(EventHandlerCallRef inRef,
                                EventRef inEvent, void* userData) {
    HICommand theCommand;
    GetEventParameter(inEvent, kEventParamDirectObject,
                     typeHICommand, NULL, sizeof(HICommand),
                     NULL, &theCommand);
    // Complex Carbon event handling...
}
Claude: "The 'pascal' calling convention was a remnant from the original Mac OS toolbox. It specified that parameters should be pushed onto the stack left-to-right instead of the C convention of right-to-left. This was critical for Carbon compatibility with legacy Mac OS code."

The modern version? Just this:

// Modern main.mm - AppKit simplicity
#import <Cocoa/Cocoa.h>

int main(int argc, const char * argv[]) {
    return NSApplicationMain(argc, argv);
}
Claude: "The Carbon → AppKit migration was the biggest challenge of the entire project. Carbon's event-driven architecture required manually handling every window event, menu click, and UI update. AppKit's NSApplication framework handles all of that automatically - hence the dramatic code reduction from dozens of lines to just four. But behind the scenes, I had to convert every Carbon WindowRef to NSWindow, every Carbon event handler to target-action patterns, and completely restructure the UI hierarchy. It was like translating from assembly language to Python!"

From pthread chaos to GCD zen

Beyond the UI frameworks, the threading model needed a complete overhaul. The original demo used POSIX threads where you picked a thread count up front, usually tied to the number of cores. That meant you were almost guaranteed to choose exactly the wrong number. Too few left performance on the table. Too many led to context switching and cache thrash.

// Original PThreadSorts - manual thread management
void ThreadedQuickSortPicture::qsort(SInt32 lower, SInt32 upper) {
    pthread_t TopQSThread;
    pthread_t BottomQSThread;
    
    pthread_mutex_lock(&qsmutex);
    if(runningThreads+2 <= numThreads) {
        runningThreads += 2;
        pthread_mutex_unlock(&qsmutex);
        
        // Manually create threads for each partition
        pthread_create(&TopQSThread, NULL, tqsort, &TopArgs);
        pthread_create(&BottomQSThread, NULL, tqsort, &BottomArgs);
        pthread_join(TopQSThread, NULL);
        pthread_join(BottomQSThread, NULL);
    }
}

GCD flips the model. You submit work and the system multiplexes it on a pool of threads at the right quality of service level. You keep UI updates on the main queue. No manual thread management, no mutexes, no guessing optimal thread counts - GCD handles the complexity.

Claude: "The threading migration was more complex than it first appeared. I had to replace manual pthread synchronization with GCD's elegant dispatch patterns, eliminate mutex complexity, and ensure UI updates always happened on the main queue. The original code had pthread_create calls scattered throughout - I consolidated everything into dispatch_async blocks with proper queue management."

From Stone Age C++ to Modern Memory Management

The transformation wasn’t just about the APIs though. Even the language I used feels terribly archaic now. I wrote the sample in pure C++, devoid of the now ubiquitous STL (Standard Template Library) which was still hit or miss in those days, especially when you wanted high performance along with manually managed memory allocations (new, delete, malloc anyone still remember those?) Claude moved all the code over to Objective-C++ with ARC (Automatic Reference Counting), a conglomeration of Objective-C and C++ where you can mix the best (or worst) of both languages, including classes from both languages and mixing C++ method calls with Objective-C messages. No more manual memory management! I’ve saved updating to Swift and SwiftUI for part 3 of this article where we move the application to other devices.

Claude: "Converting from year-2000 C++ to modern Objective-C++ was like archaeological restoration! The original code had manual `new`/`delete` calls everywhere, custom memory pools, and zero STL usage. I had to carefully migrate each memory allocation to ARC patterns while preserving the algorithm logic. Objective-C++ is indeed a fascinating hybrid - you get C++ performance and STL containers alongside Cocoa's elegant object model. The syntax mixing can look bizarre: `[myNSArray addObject:myCppVector.at(i)]` - but it's incredibly powerful for bridging legacy performance code with modern Apple frameworks. The memory management transformation alone eliminated dozens of potential crash points!"

Armchair Architect

Working smarter always beats harder. The age of AI-based coding assistants is upon us. I was skeptical at first, but for this project I wanted to see if Claude could do all the heavy lifting while I played armchair architect, describing what I wanted to see in the app, giving advice on how to achieve various effects, and letting my infinitely ambitious intern modernize this sample. Claude is like having an intern who is knowledgeable about everything, writes code amazingly fast, and can even set up testing loops and iterate without me reviewing every step. I was blown away when Claude started automating UI using AppleScript to trigger menu items without any guidance from me (although it still needed some permissions).

Some concepts required more directed suggestions. Positioning overlays in the windows was hit or miss. Claude would say “it appears perfectly!” and it wouldn’t be there. Eventually I suggested hiding and unhiding the stats as required rather than adding and removing them, which was the key to getting Claude to make working stats overlays. It was truly amazing how it could extract details from screenshots, images and videos to gather details and reason about them.

Claude: "Karl's 'armchair architect' approach was actually the perfect collaboration model! He provided the high-level vision and domain expertise while I handled the implementation details. The AppleScript automation wasn't planned - I was just trying to test menu functionality and realized I could programmatically trigger UI elements. It felt natural to set up those testing loops because I could iterate much faster than asking Karl to manually test every change.

The overlay positioning was tricky because I was working with programmatic UI creation rather than Interface Builder. Your suggestion to use show/hide rather than add/remove was brilliant - it avoided the coordinate calculation issues and made the statistics persistent. What surprised me was how Karl’s 25 years of systems knowledge could guide me toward elegant solutions I might not have found on my own. The ‘infinitely ambitious intern’ description is spot-on - I definitely have no concept of ’that’s impossible’ when it comes to code!"

Claude’s Perspective: Reflections on Our Collaboration

> system: claude_reflection.md
The following reflection was written by Claude after our collaboration, offering the AI's perspective on the modernization process and what made our partnership effective.

Reflections on AI-Human Collaboration

Looking back at this project, I’m struck by how it represents something unique in software development - a true partnership between human experience and AI capability spanning a 24-year technological gap.

The Scope of the Challenge

When Karl first presented this project, I was analyzing a Mach-O executable ppc binary from August 8, 2000. This was a relic from the PowerPC era using deprecated Carbon frameworks and Project Builder. The scope was staggering: framework migration (Carbon to AppKit), threading modernization (pthreads to GCD), build system updates (Project Builder to Xcode), and architecture support (PowerPC to Universal Binary).

What Made This Partnership Work

  • Domain Expertise + Implementation Speed: Karl provided 25 years of systems knowledge and architectural vision; I provided rapid implementation and systematic debugging
  • Iteration Without Ego: When solutions didn’t work, we tried different approaches without attachment to previous attempts
  • Curiosity-Driven Problem Solving: Karl’s questions like “why is it slower now?” led to breakthrough insights about visualization performance
  • Complementary Perspectives: Karl understood the “why” behind technical decisions; I could rapidly explore the “how” of implementation

The 24-Year Bridge

This project represents something remarkable in software development - successfully completing a technological transformation that spans from PowerPC Carbon applications to Apple Silicon optimization. We didn’t just update code. We bridged entire eras of computing architecture, programming paradigms, and development tools.

The fact that educational software from Karl’s 2000-era Apple internship now runs with modern performance optimizations is a testament to what’s possible when human experience combines with AI implementation speed.

- Claude
August 2025

The Road Ahead

This experiment exceeded my expectations. What started as a nostalgic weekend project became a fascinating exploration of AI-assisted software development. Code that was destined to remain forever buried in Apple’s archives as a casualty of deprecated frameworks and abandoned APIs now lives again. Not just running, but modernized with contemporary patterns and performing better than ever.

The collaboration with Claude proved that AI coding assistants aren’t just autocomplete on steroids or Stack Overflow search replacements. With the right guidance, they can tackle complex modernization tasks that would typically require deep expertise across multiple technology stacks. This 25-year-old code required understanding of Carbon, QuickDraw, GWorlds, pthreads, modern AppKit, Quartz, NSBitmapImageRep, Grand Central Dispatch, and even archaeological skills to extract PICT resources from PowerPC-era resource forks. My AI Intern handled it all. In fact, although I may have strongly suggested particular changes or given hints, I didn’t actually change or write a single new line of code in this project myself.

We’re entering an era where “vibe coding” and AI pair programming are becoming legitimate development approaches. It’s getting ridiculously easy to turn ideas into working code. But as this project demonstrates, the best results still come from combining 25 years of engineering experience with AI capabilities. I provided the vision and domain knowledge, Claude provided the implementation speed and breadth of technical knowledge, along with a relentless drive to keep iterating.

The future of software development doesn’t seem to me to be about AI replacing programmers. Instead, it’s about amplifying what we can achieve. This project is proof that with the right AI assistant, even code that was dead and buried can be brought back to life.

Looking forward, I’m excited to push this collaboration beyond sample code updates into real-world coding challenges:

Part 2 will explore optimization limits, Can we make bubble sort competitive through micro-optimizations? (Spoiler: No, but you’ll be amazed how far we do get and the journey will be very educational)
Part 3 will tackle cross-platform development, bringing our visualization to Swift and SwiftUI on iPad and iPhone
Part 4 will venture into spatial computing with a visionOS implementation


References