add pairs with a rule to a list

 Join @@ Developer`PartitionMap[If[Length@# === 1, #, {#[[1]], {#[[2, 1]], #[[1, 2]]}}] &, 
 list1, 2, 1, 1, {}]  == list2

True

Join @@ (If[Length@# === 1, #, {#[[1]], {#[[2, 1]], #[[1, 2]]}}] & /@ 
    Partition[list1, 2, 1, 1, {}]) == list2

True

Steps:

Partition list into parts of length up to 2 with offset 1 (take 2 elements move 1 step):

l1 = Partition[list1, 2, 1, 1, {}]

{{{-2.25, 24}, {-2.24, 0}},
{{-2.24, 0}, {-2.23, 0}},
{{-2.23, 0}, {-2.22, 10}},
{{-2.22, 10}, {-2.21, 32}},
{{-2.21, 32}, {-2.2, 50}},
{{-2.2, 50}}}

The function

func = If[Length@# === 1, #, {#[[1]], {#[[2, 1]], #[[1, 2]]}}] &;

leaves objects with Length 1 untouched, and, for two-element arguments modifies the second element to make its last entry equal to that of the first. Applied to the first element of l1 it gives

func @ l1[[1]]

{{-2.25, 24}, {-2.24, 24}}

Mapped to each element of l1 it gives

func /@ l1

{{{-2.25, 24}, {-2.24, 24}},
{{-2.24, 0}, {-2.23, 0}},
{{-2.23, 0}, {-2.22, 0}},
{{-2.22, 10}, {-2.21, 10}},
{{-2.21, 32}, {-2.2, 32}},
{{-2.2, 50}}}

Flattening this gives list2.

Developer`PartititionMap[func, list, other arguments] is equivalent to func /@ Partition[list, other arguments], that is, it applies the function to each element of the partition.


You can also use ReplaceAll

list = {{-2.25, 24}, {-2.24, 0}, {-2.23, 0}, {-2.22, 10}, {-2.21, 32}, {-2.20, 50}};

Append[Partition[list, 2, 1] /. {p : {_, a_}, {b_, _}} :> 
   Sequence[p, {b, a}], Last@list]

{{-2.25, 24}, {-2.24, 24},
{-2.24, 0}, {-2.23, 0},
{-2.23, 0}, {-2.22,0},
{-2.22, 10}, {-2.21, 10},
{-2.21, 32}, {-2.2, 32},
{-2.2, 50}}

Or

Most@Catenate@MapThread[{#1, {First@#2, Last@#1}} &, {list, RotateLeft@list}]

J. M.'s method is much faster than others provided, and deserves its own answer.

The champion:

fastJM[a_] := Riffle[a, {Rest[#1], Most[#2]}\[Transpose] & @@ (a\[Transpose])] 

The contenders:

kglr[a_] :=
  Join @@ Developer`PartitionMap[
    If[Length@# === 1, #, {#[[1]], {#[[2, 1]], #[[1, 2]]}}] &, a, 2, 1, 1, {}]

eldo1[list_] :=
  Append[Partition[list, 2, 1] /. {p : {_, a_}, {b_, _}} :> 
    Sequence[p, {b, a}], Last@list];

eldo2[list_] :=
  Most@Catenate@MapThread[{#1, {First@#2, Last@#1}} &, {list, RotateLeft@list}]

My own contribution, a variation of J.M.'s Riffle code:

JMmod[a_] := Riffle[a, {a[[2 ;;, 1]], a[[;; -2, 2]]}\[Transpose]]

Benchmark

With a packed array of reals:

Needs["GeneralUtilities`"]

BenchmarkPlot[{fastJM, kglr, eldo1, eldo2, JMmod}, RandomReal[9, {#, 2}] &, 10]

enter image description here

With unpackable Strings:

BenchmarkPlot[{fastJM, kglr, eldo1, eldo2, JMmod},
  RandomChoice[Alphabet[], {#, 2}] &, 10]

enter image description here

It appears that my variation manages a slight edge on the original in the case of unpackable data, but it falls behind on a packed array. None of the other methods come close in either test.