Ask HN: What are the best programming tricks you know?

86 points by kacperlukawski ↗ HN
During my studies, I was surprised that even a variable swap might be optimized to work in place by using XOR. Since then, I got surprised at least a few times more. For example when I was working a lot with SQL and found some engine-specific optimizations.

What were the simplest, or the most clever neat tricks you faced in your career? I mean both things simple and elegant, but also some really dirty hacks it's hard to believe can work.

182 comments

[ 1.3 ms ] story [ 237 ms ] thread
Understand the problem and solution before coding.

It sounds simple, but it's hard to have perfect discipline. Especially if you have a strong bias towards action like I do.

It's shocking how applicable this is! Don't code an algorithm before having a firm grasp of the solution. Don't write code in an interview before you're sure you've uncovered everything your interviewer wants. Don't slam out a UI before understanding how all of the edge cases should act. Unless the point of the code is exploratory, the code should be a foregone conclusion by the time you start.

The more you practice this, the more you'll uncover bugs and underspecifications before you've spent days going down the wrong path.

I am biased toward over-planning. I have found your approach to work best for work I expect to take several days. In that case, actually putting together a lucidchart / draw.io diagram about what service will get refactored where is a huge help. It also keeps data flowing uni-directionally (yes, compiler will prevent circular dependencies, but there are ways to make code dirty without ever hitting that).

On the other hand, this diagramming does take hours and some people do fine with it. I can also do ok without it, but find it to generally be worth it, especially because I get interrupted all the time, so it's good to have a clear picture of the end goal.

I am on the opposite extreme, I get so wound up designing stuff that I never write any code. I basically force myself to write something even it's imperfect and messy because it can be easily changed. And writing the code helps me understand the problem.

As with all things, moderation is good here.

I guess this really depends on how complicated the problem really is and can you really know everything before you code.

I have very limited memory, so I often write out pseudocode/notes about some properties to help my reasoning. For some problems, I will write scripts to try out some idea, verify my assumptions, while thinking about the problem.

As I have aged, I have done this more and more.

I take inspiration from the famous pianist, Glenn Gould. He would study a piece of music for weeks or more. Taking all kinds of notes, truly studying and understand each measure, etc.. He did all of this without touching a piano.

Once he was satisfied, then he'd play the piece.

OTOH, I find that writing some prototype code is one of the best ways to understand the problem and solution before starting to write the real program.

If you’re worried about lacking the discipline to throw away the prototype, do it in another language.

Great advice, that's why I bought https://runjs.app/ (not affiliated), I can quickly prototype pretty much anything with this and then port it to my actual target lang/framework after. Not hampered by the browser, npm, project setup or privacy concerns.
Your approach feels similar to mine, actually! I write lots of exploratory code (and mention it in my comment). I just view it as trying to understand the problem and solution instead of coding, since it's not usually the production code I'm going to commit.
Hear hear! The generalization of this is figuring out how to prioritize multiple problems. Deciding which problems to solve in what order, and how much time to spend on each, is a difficult and important and never solved problem. But it’s quite valuable to practice and budget your time. This might sound different from understating the problem before coding, but it’s really almost the same thing, taking time to think about the approach carefully before diving in, making sure the requirements will be met, etc. Prioritizing multiple problems is very similar to planning the steps of solving a single problem. Maybe the main difference with the higher level prioritizing is that prioritizing has to be revisited periodically and/or continuously. Working in a software company, this is what the best PMs do really well, they figure out which features and bugs to work on that make the most difference, and they understand when to listen to the programmers and let them refactor things without making public-facing progress, and also when not to.
how do you go about doing this? sometimes you dont know what will go wrong until you try it out and get feedback
Maybe not a 'trick' but writing comments before I dive into the code helps me structure my thoughts and then piecemeal the work.
yep, I use them as a blueprint for what I'm going to write...
That's not necessarily a trick I meant, but definitely a good piece of advice. Do you keep them in the codebase after implementation or is it just for you while developing to sort things out?
I remove them unless they explain some of the trickiest part.
I write comments and create lists for what should happen.

As I go through the list, I take the line and place it above the code.

Besides helping me go through things, it also helps anyone that comes in months after to work on it.

Yep, I write comments to figure out what I'm doing, and then the code writes itself. It's gotten to the point where it's hard to code without writing the comment first, and usually the comment gets rewritten multiple times before I write a single line of code (thus saving me rewriting dozens of lines of code multiple times.)
The simplest trick that every programmer learns early: when parenthesising, write both parentheses first: (), {}, [], etc. Then fill them in.

Even with modern IDEs this one neat trick saves a ton of annoyned confusion and swearing.

I don't like to do this, I like having an editor setup to highlight the open parent that matches the one you are closing. Then I can confirm that my structure matches my intention.
You could harden a Linux server by killing the init process. That would cause a kernel panic, and prevent new processes running (e.g. shells). Existing processes like Web servers would continue to function, so you could put this at the end of the boot script :)
Wow. Does this have a name?
if not any I will give one: self-destructive immutable server pattern
How do you recover control of the server, if remote?
You can't; it's so secure that nobody can control it. Except via the reboot switch :)
*Groans*

...OK, after having a bit of a poke around I would respectfully ask for a demonstration/citation of this insanity.

My own experience is that systems that panic get rather warm, which I blindly assumed was because the kernel had entered an infinite loop. This turns out to be the case: https://github.com/torvalds/linux/blob/cbe232ab07ab7a1c7743c...

There are references to interacting with kgdb, booting a crash kernel, and delaying before reboot above the highlighted spot in the previous link. This suggests that panicking is very much a "everything is torn to shreds and never comes back again" event.

Annoyingly I can't SIGKILL, -TERM, or -SEGV `init` easily (PID 1 is special-cased by the kernel), and don't remember exactly how to fire up my kernel initramfs test harness, so I can't properly test this easily at the moment. Am very curious though.

I was able to crash a systemd version 245 init on an ubuntu machine via gdb a few minutes ago as a test. It did max out the virtual CPUs on the ESXI host. And while it did not cause the machine to reboot, it seems to have taken down all the processes with it, as SSH and NGINX also crashed and froze the console as well...
Can’t shell the computer if I break the OS ;)
Uh huh. And when the web server process crashes, how do you restart it?
Second server with a relay that controls the power strip for the first server.
Try and talk to your users directly and if you're attempting to improve their workflow, try and watch their existing workflow and ask them questions about why they do things as they are now. Also use lists and hash-maps first, be wary of exotic features.
Defunctionalisation.

Instead of writing code that does what you want to do, have it return a description of what you want to do.

Then write an simple executor for such descriptions.

Why do this? Well it allows you to manipulate, test, store and inspect the description.

What? How?
You write a simple interpreter basicly. Instead of directly manipulating the data , you build an ast of how to manipulate the data then you execute it. It also allows for nifty optimizations later on.
Here's a "functionalised" API:

    GET https://example.com/users?postprocess=result.sort("signup-date").reverse
This is easy to implement: just send the 'postprocess' parameter through 'eval()'. It's completely general, allowing the data to be sorted, filtered, transformed, etc. in any way the caller likes.

It's also completely insecure, since it executes arbitrary code. It's also hard to refactor, since there's always a chance that some caller's postprocess parameter is relying on e.g. a particular variable name, etc.

Here's a "defunctionalised" alternative:

    GET https://example.com/users?sort=signup-date&order=descending
Instead of allowing arbitrary code, we only accept a description of what to do (sort by "signup-date" in descending order). This requires we implement all the logic ourselves, as well as parsing and branching-on the input to determine what was requested.

Defunctionalised APIs are the norm on the Web (except for those who accept raw SQL...); whilst functionalised APIs are the norm inside applications. For example, `myList.map(myArbitraryFunction)`. Defunctionalising is a good way to break systems into modules, with the descriptions acting as the interface.

Would you please provide an example or direct towards some explanation? I can't seem to grasp what you mean exactly.
A simple example might be a function that makes an HTTP request to a server. You could separate out a function that builds the request from a function that sends the request to the server. Then it's easy to unit test that the request is what you expect, without coding up a realistic fake for the server.
You can't send a function/closure like data over the wire. You have to limit yourself to what kind of code you want executed in the future, but you can send or store it limitless.
Suppose I have a procedure that plays a piano:

    const playTwinkleTwinkleLittleStar = (piano) => {
      piano.play('c');
      piano.play('c');
      piano.play('g');
      piano.play('g');
      piano.play('a');
      piano.play('a');
      piano.play('g');
    };
Now I want to have every note played twice. How can I do that?

Usual OOP solution is to have a wrapper on piano:

    const playItTwicePiano = (piano) => {
      return {
        play: (note) => {
          piano.play(note);
          piano.play(note);
        },
      };
    };

    playTwinkleTwinkleLittleStar(playItTwicePiano(piano));
However, if we instead return a description of what to do, such manipulations compose far more easily:

    const twinkleTwinkleLittleStar = () => {
      return [ 'c', 'c', 'g', 'g', 'a', 'a', 'g' ]; 
    };

    const playItTwice = (sequence) => {
      const xs = [];


      for (const x of sequence) {
        xs.push(x);
        xs.push(x);
      }

      return xs;
    };
And it's easier to test:

    it('should start with c', () => {
      const xs = twinkleTwinkleLittleStar();

      assert(xs[0] === 'c');
    });
Wherever you are building a complex mock for testing consider defunctionalization instead.
This is a great example, and feel the concept has "clicked" with me because of it. Thank you!
Ahh I see. So if I may use an analogy:

I have an html page with all my details, etc. Then say I want to create a markdown version of it, or a pdf version, or a docx version.

The first approach would be to manually rewrite my details to each format, as markdown, as pdf, as docx, essentially duplicating work each time.

The second approach, which is akin to defunctionalisation, would be to separate my details (data) from the format. So that would mean I would store my data in json format, then use a cli tool/program to generate it in html format, in pdf format, or in docx format.

I hope I got the basic idea right.

I also recommend this! I usually use the error handling of the language of choice for that purpose and implement the entire functionality returning errors/throwing Exceptions for the not implemented parts, and asserting upon in on unit tests to implement each pending functionality. I write the description on the assert message itself.

Edit: example

     use Psr\Http\Message\ResponseInterface;
    use Psr\Http\Message\ServerRequestInterface;

    class TransferHandler extends BaseHandler {

      public function __construct(Generator $faker, DataStorage $storage) {
       parent::__construct($faker, $storage);
      }

      public function handle(ServerRequestInterface $http_request): ResponseInterface {
       $resource_token = $http_request->getHeader('X-Resource-Token')[0];
       $account_id = $this->storage->account_id_by_resource_token[$resource_token];
       if (empty($account_id)) {
        return $this->responseInvalidResourceToken($http_request);
       }

       throw new \Exception('WIP -> store transaction data here for subsequent query, also issue notification');
      }
     }
With the following test:

public function testTransfer() { $juno = JunoAPISimulator::initialize($this->faker); $repo = AccountCreationRepoResolver::resolve();

     $account_id = $this->createAccountAndGetId(
      $this->merchant,
      fn(DigitalAccountRequestOperator $op) => $op->withProvider(DigitalAccountProviderSelector::JUNO())
     );

     try {
      Executor::execute(new IssueTransferByBankDetails(
       $account_id->toHex(),
       "BRL 12.42",
       $_ispb = '21018182',
       $_agencyNumber = '0001',
       $_accountNumber = '10000368021',
       $_accountType = 'CHECKING',
      ));
      $this->fail('should have thrown exception');
     } catch (\Throwable $e) {
      $this->assertEquals('WIP -> store transaction data here for subsequent query, also issue notification', $e->getPrevious()->getMessage());
     }
  }
So much this, really helps breakup complex tasks. Some examples off the top of my head:

If you have a function handling user sign up it often needs to build and write to multiple db tables. Split this into two functions a pure one that outputs the entities, and a second which takes these and handles writing them to a db inside a transaction. First the code is super simple to read, but more importantly it's easy to unit test since you don't need to mock anything, just make a list of input combinations paired with a list of expected outputs. Spend more time testing your business logic, less time verifying that postgres is working.

Transforming HTML into a pdf is trivial if you first iterate over the html and output a list of instructions that then get fed into a writer function one at a time, similar to how 3d printer instructions work.

Anytime I've done analysis work on a state that gets updated by a complex series of events it's always been worthwhile to build a discrete handler for each event type, then feed the events into it like a woodchipper. Complex problem solved with simple functions and a case statement.

Yeah I love this pattern. I'm using it for the data loading for my search engine. Instead of having the code that processes website data insert the data into the database and index directly, it essentially writes a program to a file, which is interpreted later to actually create the desired effects.

Means I can just grab some random subset of the search engine index, copy it over to my workstation, and load it up over there. It's also amazing for testing and inspection.

I'll add this: If you have to deal with state (e.g.: say you write something akin to `git checkout` that should rearrange the file system): Combine this strategy with the question of "can I roll this step back?".

I.e. for a checkout:

- Make a temporary folder on the target disk (rollback: delete it)

- Create new files for those you have to change (rollback: delete them)

- Fill these files with their target content

- Move old files away into the temp folder

- Move new files into place

- If everything worked, delete the leftovers (the only thing that can't be rolled back)

Your disk fills up during checkout? Roll back!

You're on Windows and some file is blocked by another process? Roll back!

…etc. :)

Can you give an example of this?
If you are interested in these kinds of things I can recommend the book "Hacker's Delight".
Great book indeed. By Henry S. Warren, ISBN 0-321-84268-5.

Another classic book, a bit less down to the metal than Hacker's Delight (which deals with bit fiddling a lot):

Programming Pearls by Jon Bentley.

When implementing something:

Figure out enough to get started. Then start. Don't think you can get a perfect view of something before wading into it, you'll just be wasting time. Likely your first attempt will suck so with your new found experience from starting and trying you can improve for your next attempt.

KISS = Keep It Simply Stupid
Uh... If that was intentional, then great joke!

If not, "keep it simplE, Stupid!". The original is an exhortation to keep things simple, calling the recipient "Stupid" as a somewhat derogatory nickname (implying they would be, if they don't follow this advice).

With the -y turning the adjective "simple" into an adverb, this becomes an attribute of the only available adjective (well, only available word left in the sentence), "stupid", specifying in which manner said adjective is to be applied: simply stupid. (Like, plainly moronic.)

No, please don't keep things simply stupid. Keep them simple, Stupid!

Great advice! I call that the "Mad Dash" - just try to get something working in a quick and dirty fashion. I often don't understand the problem until I work on the solution.
Interesting how seemingly-at-odds this is from another highly upvoted trick: the one that begins "Understand the problem and solution before coding." Hard to find that balance sometimes between "just dive right in" and perfectionism.
I think you balance the two tricks by defining "Figure out enough to get started" as "Understanding the problem (without overthinking it)."

For me this means walking through use-cases of an idea "on paper" while resisting the temptation to actually design the solution. Once you've defined the verbs/nouns of the system you're much better prepared to "dive in" and build something. That, in turn, will uncover things your paper system forgot or burst your illusions about certain use-cases causing you to rethink them. Rinse, repeat.

This is a better explanation of what I meant.

I've been the "overthink it" person, the person to spend way too much time on preparation only to start the implementation and realise one or many of the reasonable assumptions I've made are wrong and then have those affect everything else. I could have done a new on-paper solution for every single fork like that in the preparation phase (greatly extending it); or, I could have learned enough to _get started_ and come back to the drawing board to re-learn each time these (before: assumptions) are met.

It's a careful balance of preparedness and real-world discovery. Neither gives a complete picture without the other. Neither is even possible without the other.

Spending too long preparing is as bad as spending too little time. Too long and you account for possibilities that may not even exist, things you cannot know until you enter the system. Too little time and you're a bull in a china shop.

Writing functions that return side effects as a message instead of running them within the function must be one of the most underrated things.
Can you elaborate? This in the context of a message-passing architecture? Or do you mean as callbacks?
Can you give an example?
If your function is supposed to perform some side effects, and you want to test complex cases where the effects are difficult to verify, than changing the function from

   function foo() : void {
      doBar();
      if (...) {
       doBaz(12);
      }
   }
to something like:

   function fooEffects(): Effects[] {
     results = [ { type: 'bar' }]
     if (....) {
       results.push({ type: 'baz', value: 12 })
     }
     return result
   }

   function executeEffects(effects: Effects) {
     if (effects.type === 'bar') {
       ....
     }
     if (effects.type === 'baz') {
       ...
     }   
   }

Then you can easily test the "fooEffects" function (it just return values that you can compare to the expected effects").

The `executeEffects` is still hard to test (you must mock / fake / stub things), but at least you only have to setup the mocking once.

Obviously, all hells break loose if you need to test some effects that depend on the result of other effects, which limits the technique to "single flow" of effects (printing stuff, sending commands, etc...)

That structure is infinitely more important than algorithms for the great majority of the cases you have to deal with when building a product.

Being jealous of keeping good Separation of Concerns comes to mind strongly regarding to this.

Change the requirements. Understanding the requirements and adding your domain knowledge, often makes it possible to slightly change the requirements (in agreement with stakeholders) in a way that seriously reduces implementation time.
What you are really saying is that people should develop influencing skills. OP starts by asking about programming tricks, but then mentions career, so it's hard to discern whether they just want some trivia. A programmer learning any soft skill will pay off exponentially in work and their personal life. The payoffs will be much bigger than any technical skill once two years into a career.
We were working on an accelerator board for a graphics system. It had a memory for textures to map onto shapes. The way the microcode was written, it wanted the texture memory to be "rectangular" - you would move the same distance in the source memory and the destination memory to start the next row.

Well, that meant that we had to manage the texture memory as 2D regions, rather than as a 1D linear space. We spent two days thinking about creating a malloc for rectangular blocks. Then we went back and told the microcode people to fix the microcode. I think it was a one-line change for them.

So, yeah. Sometimes it's massively easier to push back on a stupid requirement than it is to implement it.

Get really good at googling and bias to do it in more situations than you think you need and always earlier than you think you need. If you're working remotely do it live in meetings / planning as well and use it as an extension of your brain. There's no advantage in discussing a problem and only investigating it after you speak to someone if you can do it in parallel right away. If you treat the search engine as an extension of your brain rather than a crutch that means every google is you admiting you don't know something, you'll reap many rewards.

I've definitely gotten way more value of learning to google better and faster than being good at some editor or IDE shortcuts.

This also includes getting very familiar with the docs of the tools you use, knowing the ins and outs of them. Something you learn when you study for Red Hat / AWS / k8s certifications where you can consult docs is that the best way to be efficient during the exam is to be efficient looking at docs. Apply that to everything.

>do it in more situations than you think you need and always earlier than you think you need.

I can't even count how many this I should just apply that. It's an obvious thing but it takes time to getting used to clearly formulating your problems. Search engines are so sofisticated nowadays that you don't even necessarily need to know what you're looking for and it just pops up. I think of it as a sort of hive mind thing, internet became a natural extension of our brains.

> Get really good at googling

Those moments when you are sitting there trying to figure out how to explain in search engine friendly terms the problem you have.

Nowadays I think you will find that if you speak to the search engine in the form of a question and in as plain terms as possible you'll have better results than by formulating it in keywords and google-fu like in the early days. I find that developers who "try to think in search engine" have gotten increasingly worse results over time as the search engine adapted to the general population's way of using it. This is all personal anecdata though, so you might want to try different ways that work for you.
This makes sense - however the search engine is just a proxy to the content on websites. I should have probably been more clear in my original comment. What I was really referring to, is when you are sitting there trying to think how other people might ask a question when the way you are originally asking it is not turning up good results.
Many times someone essentially solved their problem while in the act of formulating the question to ask me what to do about it.

"glad to help, any time!"

Being able to use search engines is a definite skill that certainly cannot be understated. Once upon a time we'd have shelves of $100 books to pore over, and that would take time in itself. Being able to use the correct terminology and the correct search engine logic will help in delivering software faster.
The most clever trick I've learned as a programmer is not to use clever tricks.

Write code that is obvious, clearly readable by future programmers maintaining the code. Don't do clever things that are hard to read and reason about until there is a strong motivating reason to do so.

Honestly most of the clever things you're about to do, the compiler is already going to do for you, but better and without the bugs you'll introduce when you do it wrong.

Reminds me of this quote by Brian W. Kernighan

>Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it

Well, by assertion not by definition. The first half is a bald assertion. Never the less I love this.
Yeah it's more wordplay than argument.

Reminds me of a similar 'wordplay as argument' favourite quote by Terry Pratchett:

> You know how dumb the average person is? Well, statistically, half of them are even dumber than that.

(source: https://www.youtube.com/watch?v=zLkg-S_FfXc&t=3720s )

(comment deleted)
> Write code that is obvious, clearly readable by future programmers maintaining the code

I agree, with the caveat that does not mean low-level/unabstracted/verbose code. Whilst each line of such code might be "obvious" on its own, the big-picture behaviour and inter-dependencies can be difficult to follow across multiple pages.

As an extreme example, machine code is completely "obvious" (e.g. "increment register RAX"), whilst being notoriously hard to follow (often requiring disassemblers, etc.)

> Write code that is obvious, clearly readable by future programmers maintaining the code.

This is something I've come to realise more and more strongly as time's gone by. The code that I write isn't for me, it's for everyone coming after me. Including future-me. Gotta look after future-me.

> Honestly most of the clever things you're about to do, the compiler is already going to do for you, but better and without the bugs you'll introduce when you do it wrong.

I don't think this is actually the case, it's fairly easy to outsmart most compilers. A more honest argument is that it, in most cases, is a bad trade to make the code 3% faster by making it 10x more difficult to read.

Sometimes it is worth that effort, but that's the exception and you need to be able to motivate why it actually makes a difference. Show me a benchmark that demonstrates this is actually much faster (as a whole, not just the method), and then we can talk. "Clever" is in most cases another word for premature optimization.

There are so many things that can be called a "clever trick" that it makes no sense to say that you should not use clever tricks.

Like, one thing that can be called a clever trick is to align the code vertically to make it easier to edit using vertical selections and macros in a text editor. That can actually make the code more readable and maintainable.

Then you have a strong motivating reason to do it.

Honestly, I mostly mean things like "if we xor the two variables we can save one variable during the swap", etc. Clever code is not a great thing.

Hansen's Second Law: Clever is the opposite of maintainable

There is nothing that will stop a code review quicker than "I discovered a clever way to..." A dozen engineers are going to have to try to reverse engineer your cleverness in order to safely make any change to your code, so you'd better make double-sure that the performance you're buying with your cleverness is worth the total maintenance cost going forward.

There are some times it's worth it (the Fast InvSqrt hack and many others), but most of the time, it's just tickling our intellectual curiosity for our own benefit, and that's a bad trade.

Test-driven development. You write the unit tests before the code, then you write code until tests stop failing.

It makes writing code so much faster it's not even funny, and with much less mental strain. Debugging sucks but testing rocks. You basically always save time.

I don't disagree, but it is best if you know the problem already. And it won't change too substantially, in which case you have to rework the code and the tests.
The XOR swap trick is just a trick; it shouldn't be used in practice. Modern architectures can do a register swap almost for free, compared to three XORs. The biggest problem is when your two values are equal: they'll get zeroed out and good luck tracking that down!
That’s not correct: Equal value isn’t an issue. Having both variables point to the same memory is.
Oops, my bad! I knew it was something to do with equality, but I forgot the specifics… Another good reason not to rely on tricks, I suppose!
Agreed. Had to think about that one too a moment, but something felt wrong. XOR is bitwise, so if your statement was true, it would mean that two 1 bits or two 0 bits would both end up producing a 0 bit result. Clearly that would make the whole thing pretty useless :)
> The biggest problem is when your two values are equal: they'll get zeroed out and good luck tracking that down!

  (a,b) = (5,5)
  a = a ^ b   # a = 0, b = 5
  b = a ^ b   # a = 0, b = 5
  a = a ^ b   # a = 5, b = 5
Hmm?
As I understand it (... haven't tested recently) you're right that it's not usually a win on modern architectures, and the compiler could do it wherever there's enough register pressure that it might actually help (if anywhere). It's still neat, but I don't think there's any reason to be writing it unless you are on a particularly constrained system with a limited compiler.

I assume you're being downvoted for your slightly incorrect caveat.

Take notes of what you've tried/what your results were when debugging. Simple, but massively effective for eliminating duplicate work.
That's a massive game-changer, I agree. I really like the approach described by my former colleague: https://softwarephilosopher.com/2022/03/07/personal-worklog/ but I haven't manage to fully implement it
There are many ways to do it, but almost anything is massively better than nothing. There are few things more wasteful in a long debugging session than running down a long, unfruitful debug path multiple times because you weren't sure you checked that hypothesis before. If you keep a log, you'll KNOW, or at least be able to check.
Most of the programming tasks do not need optimization for time or memory. Instead optimize for readability of code.

Also, write tests.

The command design pattern never ceases to amaze me. Package up a complicated process in an "input, process, output" package. Turn it into a black box. The input should be as small as possible, just the details and data you need to "do the needful". The output should be as little as possible to explain how things went. I usually let my code grow organically, but once it gets to a certain point, using this technique really helps clean things up.
Isn't that just a function?
Yes. There's some nuance to it, but at the end of the day, yes it is just a function call. Usually the input and output are complex types which express the desired behavior or parameters of the process. There's some conceptual distance between a function that takes two integers and returns an integer, and one that takes a complex series of mutually related arguments, some possible business objects with child objects attached, etc.
A 'trick' that I've grown fond of is for iterating over a circular list where the current and previous items form an edge, with a special case for the first edge, that is composed of the first and last items of the list (making it circular).

The 'normal' way to write a loop over this is something like:

for (let i = 0; i < ls.length; i++) {

    const j = i > 0 ? i - 1 : ls.length - 1;

    ...
}

A trick I got from sean barretts website (https://nothings.org/), is to use the for-loop initialization clause for the special case:

for (let i = 0, j = ls.length - 1; i < ls.length; j = i++) {

  ...
}

The work I do usually does not care about the performance improvement if there is any, but it feels good to avoid that per-iteration check.

Can you expand on your first trick? What is the problem being solved and what is a practical use case? I'm not sure I follow.
console.log({ variable }) when debugging
Not sure if this counts as a "programming trick", but for maneuvering around the shell: learning readline.

https://readline.kablamo.org/emacs.html

^This one cool trick will increase your productivity by 1000%!

I've had sr. engineers see me using ctrl-r and go "woah, how did you do that?"

Yeah I heard of people grepping their history file. Ctrl+R is so much faster.
Most of the time I know the starting characters of the command I want, so I use up/down arrow instead of Ctrl+r. You'll need this in `.inputrc`

    # use up and down arrow to match search history based on typed starting text
    "\e[A": history-search-backward
    "\e[B": history-search-forward
use of linters is godsent too, eslint for javascript fixes a lot of compile time problems which avoids runtime problems
Being proficient and fluent in your language and tooling of choice is a superpower. What are the guarantees you get with different kinds of async behavior, how are null or maybe values handled and passed around, what are the default values of things and why. Not just knowing, but understanding questions like these will allow you to write code that is robust and has fewer unexpected side effects. In short, the trick is putting in the hours and hard work. Or, to quote MtG: "There is no shortcut to work done true and well."