The Day I Realized I Needed to Raise My Rates as a Coder

Hourly billing is nuts.

I learned that from Jonathan Starks. Charging by the hour doesn’t encourage productivity. To make more money, you have to work slowly or pad your time sheet. In either case, you’re not being honest.

Even knowing that, I agreed to work as a contractor for a small software agency charging by the hour.

After every task, I couldn’t resist the temptation of looking at my tracking software to see how much I had made in that working session. “I’ve made only $75, I need to pay my bills. Arrrggg!”

But that story started months earlier, after being “let go.”

After the initial moments of relief from a layoff, the anxiety of not having a paycheck made me go into panic mode. I sent my CV to as many places looking for coding skills as possible. Even to a FAANG, and my overconfidence and lack of preparation made failed.

Out of dozens of applications, only one got me a reply: “Thanks, but we’re not looking. Maybe later.” A polite no.

Months later, they wanted to chat with me.

I wasn’t used to charging by the hour. As a full-time employee, you receive your monthly paycheck and don’t run those calculations. Maybe HR does, not you. I picked a rate out of thin air based on my last salary.

One ex-coworker suggested that I should go with a lower rate since hiring in 2024 has been slow. AI, high interest rates, or who knows what made the demand for software engineers decrease. I followed that advice and lowered my rate.

The day of the interview came.

The usual questions: “what do you do?” and “tell me about yourself.” And of course, my hourly rate.

When I asked about my future project, I was told it was an app migration from an ancient tech stack to a newer one and they were looking for someone “cheap” like me to handle the repetitive part.

Ouch! That hurt. Directly in my ego. “Thanks for being honest.”

I knew I needed to set an aspirational rate (lesson from Naval Ravikant) and escape from the Matrix. That moment reinforced that decision. But my emergency fund was about to run out in a couple of months, so I had to swallow my pride and be the “cheap” one.

The next time you’re asked about your rates, go with the advice from “The Secrets of Consulting” by Gerald Weinberg and give a rate that makes you comfortable either if it’s accepted or not. Avoid the “oh no, I should have charged more” and the “damn, I charged too high.”

Or use a more practical approach: slap the other person in the face and say your rate. If he gets offended by the slap, you’re charging too low.

You need to raise your rates.

If a Language Learning Forum Were Like a Coding Forum:

Question: Guys, how do I say “I love you” in Japanese?

— Japanese? Why don’t you try German? It’s easier in German

— If you want to talk about love, you should try with French

— Yeah, go with French. That’s the language of love

…Question downvoted

— Wait, isn’t Spanish the language of love?

— Guys, the thing is I have this text at work and I need to say that in Japanese

— Really? What kind of job is that? You should talk to your boss and tell him to use a real language for that

— Do you call yourself a language lover and don’t know that?

…Question closed as duplicate.

Passive Learning Is Just Entertainment Unless You Follow These Two Strategies

Passive consumption is procrastination in disguise.

If you’re like me, you’ve been watching another YouTube video, reading another book, and consuming online content as procrastination.

Mindless consumption isn’t learning.

Learning should be active. We truly learn when we engage in our learning process. Instead of simply reading or watching pretending to learn, engage with what we’re learning.

Start taking notes by hand

That’s the easiest way to engage with your learning.

Yes, taking notes by hand! When we take notes by hand, we get “physical” like the 80s hit. Touching a pen and feeling the paper create brain connections that reinforce our learning.

Ditch your laptop and go back to pen and paper.

After taking notes, create something new

Taking notes is only the first step.

The best way to truly learn is to create something from what you consume.

Instead of reading to show off a book count, read with intention: answer a question or face a challenge. Once you read, write a summary or explain it to others in your own words.

If you’re a podcast lover, don’t simply listen to the next episode until the end. Write down 3 main points you learned, transcribe your handwritten notes, and share them online.

The next time you find a new programming concept, incorporate that concept into a small project. And, if you’re reading an open-source project, create a simplified version of its main feature to make sure you understand the main decisions and concepts behind it.

And, if you like writing, do copy work. Recreate a piece from a writer you want to imitate. Again by hand. This is an exercise attributed to Benjamin Franklin. After reading a piece, he looked away and tried to rewrite what he read.

Separate the exploration phase where you only gather resources from the engaging phase where you create something new after consuming.

If you listen, you will forget. If you write, you will remember. But if you practice, you won’t forget.

It's Not About Selling. It's About Helping

The other day I had a weird DM exchange.

A guy shared a cool LinkedIn tip. I rushed to apply it and DMed him to say thanks for that cool idea. You know I wanted to make friends in the DM.

He continued the conversation. I shared how my LinkedIn journey was going. I told him about my 100-post experiment and my future plans.

After a couple of exchanges, the guy went on sale mode. He had an offering and I could potentially be one client. I got that!

But, he wrote something in the lines of “Truth is I don’t need more clients and I’m fully booked…“ I stopped reading at that point. If he doesn’t need clients, why he was on salesy mode with that line in the first place?

People don’t like those mind tricks. At least I don’t. Those Jedi mind tricks don’t work on me.

I would have rephrased that message like “hey, maybe you don’t help with <insert service here> yet. Once you do, feel free to shoot me a message, I’d be glad to help you. That’s what I do for a living and I have helped more than <insert client count here> people with that.”

It would have booked a call after a message like that. Or at least I wouldn’t have ghosted him.

It’s not about selling, it’s about helping.

The Power of Function Composition — to Find If an Array Is Special

Passing the result of a function to another isn’t the only way to compose two functions.

From math classes, we’re used to composing two functions like this compose(f, g) = x => f(g(x)). But it turns out there are more ways.

That’s my main takeaway from the talk “The Power of Function Composition” by Conor Hoekstra at NDC Conference 2024.

Here’s the YouTube recording,

The talk shows how to find if an array is special using languages like Python, Haskell, and other obscure and esoteric ones to show function composition.

These are the lessons I learned, translating some code examples to C#.

A simple solution to find if an array is special

Let’s find if an array is special.

An array is special if every pair of consecutive elements contains two numbers with different parity. For example, [4,3,1,6] isn’t special. It has three pairs: 4,3, 3,1, and 1,6. The second pair has two odd numbers, which makes our array “not special.”

Here’s the LeetCode problem if you want to try it yourself.

And here’s my solution before using any of the new concepts from the talk,

IEnumerable<(int First, int Second)> Pairs(int[] array)
{
    for (int i = 0; i < array.Length - 1; i++)
    {
        yield return (array[i], array[i + 1]);
    }
    // Or simply:
    // return array.Zip(array.Skip(1));
}

bool HasDifferentParity((int First, int Second) pair)
    => pair.First % 2 != pair.Second % 2;

bool IsSpecial(int[] array)
    => Pairs(array).All(HasDifferentParity);

IsSpecial([1, 2, 3, 4]) // true
IsSpecial([4, 3, 1, 6]) // false

It uses Pairs() to generate the pairs of consecutive elements, HasDifferentParity() to find if both numbers in a pair are even or odd, and All() from LINQ. Nothing fancy!

Apart from the “usual” composition, there are other ways of composing functions

Here are some of them, in pseudo-code:

def i(x) = x
def k(x, y) = x
def ki(x, y) = y

def s(f, g) = \x => f(x, g(x))
def b(f, g) = \x => f(g(x)) // <--

def c(f) = \x, y => f(y, x)
def w(f) = \x => f(x, x)

def d(f, g) = \x, y => f(x, g(y))
def b1(f, g) = \x, y => f(g(x, y))
def psi(f, g) = \x, y => f(g(x), g(y)) // <--
def phi(f, g, h) = \x => g(f(x), h(x))

Let’s take a close look at two of them.

b is the composition we all know. It passes the result of the second function to the first one. And psi passes two values to the second function and then calls the first one with both results.

Let’s find if an array is special again, but composing functions

Let’s revisit IsSpecial() but using “psi” this time,

IEnumerable<(int First, int Second)> Pairs(int[] array)
    => array.Zip(array.Skip(1));

bool IsEven(int x) => x % 2 == 0;
bool NotEqual(bool x, bool y) => x != y;

// A more general signature would be:
// Func<T1,T1,T3> Psi<T1, T2, T3>(Func<T2,T2,T3> f, Func<T1,T2> g)
// But to make things easier:
Func<int, int, bool> Psi(Func<bool, bool, bool> f, Func<int, bool> g)
//                   ^^^
    => (x, y) => f(g(x), g(y));

var hasDifferentParity = Psi(NotEqual, IsEven); // <--
bool IsSpecial(int[] array)
    => Pairs(array).All(pair => hasDifferentParity(pair.First, pair.Second));

// or
bool IsSpecial(int[] array)
    => Pairs(array).All(pair => Psi(NotEqual, IsEven)(pair.First, pair.Second));
    //                          ^^^

IsSpecial([1, 2, 3, 4]) // true
IsSpecial([1, 2, 4, 3]) // false

Instead of HasDifferentParity(), it uses Psi(NotEqual, IsEven).

And again, here’s the pseudo-code of psi: psi(f, g) = \x, y => f(g(x), g(y)).

Psi(NotEqual, IsEven) receives two integers, calls IsEven with each of them, and passes both results to NotEqual.

That’s a new way of composition.

Let’s do it the Haskell way using MapAdjacent

From Haskell official docs, MapAdjacent transforms every pair of consecutive elements.

Here’s IsSpecial() one more time, but using MapAdjacent(),

// Pairs, IsEven, NotEqual, and Psi are the same as before...

bool IsSpecial(int[] array)
    => array.Select(IsEven).MapAdjacent(NotEqual).And();

// or
bool IsSpecial(int[] array)
    => array.MapAdjacent(Psi(NotEqual, IsEven)).And();

IsSpecial([1, 2, 3, 4]) // true
IsSpecial([1, 2, 4, 3]) // false

It transforms our array into an array of booleans–if every number is even–with Select(), then finds if every pair of booleans is different with MapAdjacent() and lastly collapses all the results with And().

As an alternative, it uses MapAdjacent() with Psi(NotEqual, IsEven) to avoid using Select() first. This alternative is closer to the previous one with Pairs() and All().

Since we don’t have MapAdjancent() and And(), here are their C# versions,

public static class Enumerable
{
    public static IEnumerable<TResult> MapAdjacent<TSource, TResult>(
            this IEnumerable<TSource> source,
            Func<TSource, TSource, TResult> func)
        => source.Zip(source.Skip(1), (first, second) => func(first, second));

    public static bool And(this IEnumerable<bool> source)
        => source.All(x => x);
}

MapAdjacent() is a wrapper of Zip() and And() is a wrapper of All().

This is one of the talks to replay multiple times if you want to grasp its concepts, since they’re not that common. Also, it covers the same example in other less known languages that I don’t even remember their names.

Since you won’t use those obscure languages, learn the composition part of the talk. It’s mind blowing. In any case, if you want to start with Functional Programming, you’ll need simpler concepts.