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:


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.
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.

To bring this back to life, we had to completely modernize it. I’ll let Claude give you the overview:
The end result is GCDImageSorts that I’ve uploaded to my git repository if you want to play with it.
Making Algorithms Dance

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:
Algorithm | Time | Complexity | Comparisons | Swaps | Performance Category |
---|---|---|---|---|---|
GCD Concurrent Quick Sort | 53.73ms | O(n log n) | 5.7M | 844.9K | 🟢 Excellent |
Quick Sort | 74.09ms | O(n log n) | 5.8M | 1.3M | 🟢 Excellent |
Heap Sort | 136.22ms | O(n log n) | 10.3M | 5.5M | 🟢 Good |
Merge Sort | 1.87s | O(n log n) | 5.2M | 307.2K | 🟡 Usable |
Shell Sort | 339.80ms | O(n^1.5) | 26.3M | 21.1M | 🟡 Usable |
Selection Sort | 49.24s | O(n²) | 47185.8M | 307.2K | 🔴 Poor |
Insertion Sort | 5min 43s | O(n²) | 23570.8M | 23570.8M | 🔴 Very Poor |
Bubble Sort | 6min 6s | O(n²) | 47185.8M | 23570.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 ID 3: 220×220 pixels

PICT ID 5: 208×160 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.
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...
}
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);
}
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.
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.
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.
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
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
Apple Developer: Grand Central Dispatch (Dispatch)
https://developer.apple.com/documentation/dispatchApple Developer archive: Concurrency Programming Guide (dispatch queues vs threads)
https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.htmlApple Developer: Threading Programming Guide
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/Introduction/Introduction.htmlApple Developer: NSBitmapImageRep
https://developer.apple.com/documentation/appkit/nsbitmapimagerepApple Developer: NSWindow and
backingScaleFactor
https://developer.apple.com/documentation/appkit/nswindow
https://developer.apple.com/documentation/appkit/nswindow/backingscalefactorApple Developer archive: High Resolution on OS X
https://developer.apple.com/library/archive/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Explained/Explained.htmlApple Developer archive: Mac Automation Scripting Guide (UI scripting)
https://developer.apple.com/library/archive/documentation/LanguagesUtilities/Conceptual/MacAutomationScriptingGuide/AutomatetheUserInterface.htmlApple Developer archive: Carbon Core deprecation context
https://developer.apple.com/documentation/coreservices/carbon_core